88import base64
99import hashlib
1010import hmac
11- import urllib
12- import urllib2
13- import urlparse
11+ import sys
12+
13+ if sys .version_info > (3 , 0 ):
14+ from urllib .parse import urlparse , urlencode
15+ from urllib .request import urlopen
16+ else :
17+ from urllib import urlencode
18+ from urllib2 import urlopen
19+ from urlparse import urlparse
1420
1521try :
1622 import json
1723except ImportError :
1824 import simplejson as json
1925
2026
21- VERSION = '1.0.2'
27+ VERSION = '2.0.0'
28+
29+ __all__ = ['Geocoder' , 'GeocoderResult' , 'GeoResult' , 'GeocodeError' ]
2230
23- __all__ = ['Geocoder' , 'GeocoderResult' , 'GeoResult' , 'GeocodeError' ,]
2431
2532class GeocodeError (Exception ):
2633 """
2734 Base class for errors in the :mod:`ggeocoder` module.
28-
35+
2936 Methods of the :class:`Geocoder` raise this when the Google Maps API
3037 returns a status of anything other than 'OK'.
31-
32- See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes
38+
39+ See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes # noqa: E501
3340 for status codes and their meanings.
3441 """
3542 G_GEO_OK = "OK"
3643 G_GEO_ZERO_RESULTS = "ZERO_RESULTS"
3744 G_GEO_OVER_QUERY_LIMIT = "OVER_QUERY_LIMIT"
3845 G_GEO_REQUEST_DENIED = "REQUEST_DENIED"
3946 G_GEO_MISSING_QUERY = "INVALID_REQUEST"
40-
47+
4148 def __init__ (self , status , url = None , response = None ):
4249 """Create an exception with a status and optional full response.
43-
50+
4451 :param status: Either a ``G_GEO_`` code or a string explaining the
4552 exception.
4653 :type status: int or string
4754 :param url: The query URL that resulted in the error, if any.
4855 :type url: string
4956 :param response: The actual response returned from Google, if any.
5057 :type response: dict
51-
5258 """
5359 super (GeocodeError , self ).__init__ (status )
5460 self .status = status
5561 self .url = url
5662 self .response = response
57-
63+
5864 def __str__ (self ):
5965 """Return a string representation of this :exc:`GeocoderError`."""
6066 return 'Error: {0}\n Query: {1}' .format (self .status , self .url )
61-
62- def __unicode__ (self , * args , ** kwargs ):
63- """Return a unicode representation of this :exc:`GeocoderError`."""
64- return unicode (self .__str__ ())
67+
6568
6669class GeoResult (object ):
6770 """
@@ -84,11 +87,8 @@ def __eq__(self, other):
8487 return False
8588
8689 def __str__ (self ):
87- return unicode (self ).encode ('utf-8' )
88-
89- def __unicode__ (self ):
9090 return self .formatted_address
91-
91+
9292 def __getattr__ (self , name ):
9393 attr , prop = self .get_property_components (name )
9494
@@ -142,7 +142,6 @@ def raw(self):
142142 return self .data
143143
144144
145-
146145class GeocoderResult (object ):
147146 """
148147 Helps process all the results returned from Google Maps API
@@ -156,7 +155,6 @@ class GeocoderResult(object):
156155 print result.formatted_address, result.coordinates
157156
158157 You can also customize the result_class created if you'd like.
159-
160158 """
161159
162160 def __init__ (self , data , result_class ):
@@ -172,17 +170,17 @@ def __getitem__(self, idx):
172170class Geocoder (object ):
173171 """
174172 Interface for interacting with Google's Geocoding V3's API.
175- http ://code .google.com/apis/ maps/documentation/geocoding/
173+ https ://developers .google.com/maps/documentation/geocoding/start?csw=1
176174
177175 If you have a Google Maps Premier account, you can supply your
178176 client_id and private_key and the :class:`Geocoder` will make
179177 the request with a properly signed url
180178 """
181- PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account."
182- GOOGLE_API_URL = 'http ://maps.googleapis.com/maps/api/geocode/json?'
179+ PREMIER_CREDENTIALS_ERROR = "You must provide both a client_id and private_key to use Premier Account." # noqa: E501
180+ GOOGLE_API_URL = 'https ://maps.googleapis.com/maps/api/geocode/json?'
183181 TIMEOUT_SECONDS = 3
184182
185- def __init__ (self , client_id = None , private_key = None ):
183+ def __init__ (self , client_id = None , private_key = None , api_key = None ):
186184 """
187185 Google Maps API Premier users can provide credentials to make 100,000
188186 requests a day vs the standard 2,500 requests a day without
@@ -194,23 +192,27 @@ def __init__(self, client_id=None, private_key=None):
194192 :type client_id: str
195193 :param private_key: private key used to sign urls
196194 :type private_key: str
197-
195+ :param api_key: Google Maps API key.
196+ this will trump if it is present. Google has moved away from the
197+ "premier accounts" and now just requires an API key
198+ :type api_key: str
198199 """
199200 self .credentials = (client_id , private_key )
201+ self .api_key = api_key
200202 if any (self .credentials ) and not all (self .credentials ):
201203 raise GeocodeError (self .PREMIER_CREDENTIALS_ERROR )
202204
203205 def geocode (self , address , ** params ):
204206 """
205207 | Params may be any valid parameter accepted by Google's API.
206- | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
208+ | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501
207209 """
208210 return self ._get_geocoder_result (address = address , ** params )
209211
210212 def reverse_geocode (self , lat , lng , ** params ):
211213 """
212214 | Params may be any valid parameter accepted by Google's API.
213- | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests
215+ | http://code.google.com/apis/maps/documentation/geocoding/#GeocodingRequests # noqa: E501
214216 """
215217 return self ._get_geocoder_result (latlng = "%s,%s" % (lat , lng ), ** params )
216218
@@ -219,13 +221,11 @@ def use_premier_key(self):
219221 return all (self .credentials )
220222
221223 def _get_geocoder_result (self , result_class = GeoResult , ** params ):
222- geo_params = {'sensor' : 'false' } # API says sensor must always have a value
223- geo_params .update (params )
224- return GeocoderResult (self ._get_results (params = geo_params ), result_class )
224+ return GeocoderResult (self ._get_results (params = params ), result_class )
225225
226226 def _get_results (self , params = None ):
227227 url = self ._get_request_url (params or {})
228- response = urllib2 . urlopen (url , timeout = self .TIMEOUT_SECONDS )
228+ response = urlopen (url , timeout = self .TIMEOUT_SECONDS )
229229 return self ._process_response (response , url )
230230
231231 def _process_response (self , response , url ):
@@ -235,9 +235,11 @@ def _process_response(self, response, url):
235235 return j ['results' ]
236236
237237 def _get_request_url (self , params ):
238- encoded_params = urllib . urlencode (params )
238+ encoded_params = urlencode (params )
239239 url = self .GOOGLE_API_URL + encoded_params
240- if self .use_premier_key :
240+ if self .api_key :
241+ url = url + "&key={}" .format (self .api_key )
242+ elif self .use_premier_key :
241243 url = self ._get_premier_url (url )
242244 return url
243245
@@ -252,52 +254,54 @@ def _generate_signature(self, base_url, private_key):
252254 """
253255 http://code.google.com/apis/maps/documentation/webservices/index.html#PythonSignatureExample
254256 """
255- url = urlparse . urlparse (base_url )
257+ url = urlparse (base_url )
256258 url_to_sign = url .path + '?' + url .query
257259 decoded_key = base64 .urlsafe_b64decode (private_key )
258- signature = hmac .new (decoded_key , url_to_sign , hashlib .sha1 )
259- return base64 .urlsafe_b64encode (signature .digest ())
260+ signature = hmac .new (decoded_key , url_to_sign . encode () , hashlib .sha1 )
261+ return base64 .urlsafe_b64encode (signature .digest ()). decode ( 'utf-8' )
260262
261263
262264if __name__ == "__main__" :
263265 import sys
264266 from optparse import OptionParser
265-
267+
266268 def main ():
267269 """
268270 Geocodes a location given on the command line.
269-
271+
270272 Usage:
271273 ggeocoder.py "1600 amphitheatre mountain view ca" [YOUR_API_KEY]
272274 ggeocoder.py 37.4218272,-122.0842409 [YOUR_API_KEY]
273-
275+
274276 When providing a latitude and longitude on the command line, ensure
275277 they are separated by a comma and no space.
276-
278+
277279 """
278280 usage = "usage: %prog [options] address"
279281 parser = OptionParser (usage , version = VERSION )
280282 parser .add_option ("-c" , "--client_id" ,
281- dest = "client_id" , help = "Your Google Maps Client Id key" )
283+ dest = "client_id" ,
284+ help = "Your Google Maps Client Id key" )
282285 parser .add_option ("-k" , "--private_key" ,
283- dest = "private_key" , help = "Your Google Maps Private Signature key" )
286+ dest = "private_key" ,
287+ help = "Your Google Maps Private Signature key" )
284288 (options , args ) = parser .parse_args ()
285-
289+
286290 if len (args ) != 1 :
287291 parser .print_usage ()
288292 sys .exit (1 )
289-
293+
290294 query = args [0 ]
291295 gcoder = Geocoder (options .client_id , options .private_key )
292-
296+
293297 try :
294298 result = gcoder .geocode (query )
295- except GeocodeError , err :
299+ except GeocodeError as err :
296300 sys .stderr .write ('%s\n %s\n Response:\n ' % (err .url , err ))
297301 json .dump (err .response , sys .stderr , indent = 4 )
298302 sys .exit (1 )
299-
303+
300304 for r in result :
301- print r
302- print r .coordinates
305+ print ( r )
306+ print ( r .coordinates )
303307 main ()
0 commit comments