├── README.md ├── kml_to_json.py ├── README_googlemaps.txt ├── json_to_omnigraffle.py └── googlemaps.py /README.md: -------------------------------------------------------------------------------- 1 | Here are two Python scripts to generate what I call a "centered time-distance map" (see below). 2 | 3 | The idea is to consider the observer's location as the center of the map. Places are then shown in the same direction as on a usual map, but at a distance proportional to the travel time needed to reach them. 4 | 5 | The process to generate such a map can be broken down this way: 6 | 7 | 1. enter the places you're interested in a Google Map 8 | 2. export the Google map as a KML file 9 | 3. use retrieve the durations needed to reach each point, store them in a JSON file 10 | 4. compute (x,y) position for each point 11 | 5. generate Apple Script code to draw the points in OmniGraffle 12 | 13 | First, enter the places in a Google map and export it as a KML file 14 | 15 | $ curl "http://maps.google.com/maps/ms?msa=0&msid=210545173139138797518.0004a6dcfefdc6b4ae2bc&output=kml" -o map.kml 16 | 17 | Then, extract the coordinates and get the travel time to them, with the kml_to_json.py script. 18 | 19 | $ python kml_to_json.py \ 20 | --start "Place de la Gare 1, Lausanne" \ 21 | --places map.kml \ 22 | > map.json 23 | 24 | The kml_to_json.py script converts the KML file into an array of destinations with GPS coordinates and adds the time needed to reach them from the start address. 25 | 26 | [ 27 | { 28 | "lat": 46.537136, 29 | "minutes": 10, 30 | "lon": 6.639862, 31 | "name": "Sauvabelin" 32 | }, 33 | { 34 | "lat": 46.4533, 35 | "minutes": 34, 36 | "lon": 6.942905, 37 | "name": "Les Avants" 38 | } 39 | ] 40 | 41 | The json_to_omnigraffle.py script computes the (x, y) coordinates in the Mercator projection and generates Apple Script code to draw these places in [OmniGraffle](http://www.omnigroup.com/products/omnigraffle/). 42 | 43 | $ python json_to_omnigraffle.py --center_lat 46.515533 --center_lon 6.6297428 --show_radius True --places map.json > map.txt 44 | 45 | Finally, open a new document in Omni Graffle and execute the script. 46 | 47 | $ osascript map.txt 48 | 49 | You'll end up with a nice map such as this one. You can then add roads and, and tweak the points position manually. 50 | 51 | You can find other home made maps on [http://seriot.ch/maps.php](http://seriot.ch/maps.php). 52 | 53 | 54 | 55 | 56 | -------------------------------------------------------------------------------- /kml_to_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | """ 4 | from google kml to json with time and travel distance 5 | 6 | $ curl "http://maps.google.com/maps/ms?msa=0&msid=210545173139138797518.0004a6dcfefdc6b4ae2bc&output=kml" > map.kml 7 | 8 | $ python kml_to_json.py --start "place de la gare 1, lausanne" --places map.kml > map.json 9 | """ 10 | 11 | import xml.dom.minidom 12 | import re 13 | import json 14 | import sys 15 | import argparse 16 | 17 | from googlemaps import GoogleMaps 18 | 19 | api_key = "ABQIAAAAyj5pErQ2rt4sNoP8_DBVcxQqgwkBz2GMttiD5juD_pXeJYXxeBRiygJvZWY_J3-nSAR3nvEmuLIi3Q" 20 | gmaps = GoogleMaps(api_key) 21 | 22 | def name_lon_lat_list_in_kml_generator(kml_file): 23 | 24 | doc = xml.dom.minidom.parse(kml_file) 25 | 26 | for placemark_dom in doc.getElementsByTagName('Placemark'): 27 | 28 | point = placemark_dom.getElementsByTagName("Point")[0] 29 | coord_node = point.getElementsByTagName("coordinates")[0] 30 | coord = coord_node.firstChild.nodeValue 31 | 32 | name_node = placemark_dom.getElementsByTagName("name")[0] 33 | name = name_node.firstChild.nodeValue 34 | 35 | m = re.match(r"(\S+),(\S+),\S+", coord) 36 | 37 | if not m: 38 | sys.stderr.write("-- skip coord: %s, name: %s\n" % (coord, name)) 39 | continue 40 | 41 | lat, lon = float(m.group(1)), float(m.group(2)) 42 | 43 | yield (name, lat, lon) 44 | 45 | def name_lon_lat_time_dictionaries(start_address, kml_file): 46 | 47 | ll = gmaps.address_to_latlng(start_address) 48 | 49 | sys.stderr.write("\nstart from: %s %s\n" % (start_address, ll)) 50 | 51 | l = [] 52 | 53 | for (name, lon, lat) in name_lon_lat_list_in_kml_generator(kml_file): 54 | sys.stderr.write("\n%s\n" % name) 55 | sys.stderr.write("%f %f\n" % (lon, lat)) 56 | 57 | destination = "%f,%f" % (lat, lon) 58 | 59 | try: 60 | directions = gmaps.directions(start_address, destination) 61 | except Exception, e: 62 | print e 63 | continue 64 | 65 | minutes = directions['Directions']['Duration']['seconds'] / 60 66 | sys.stderr.write("-- time [min]: %d\n" % minutes) 67 | 68 | d = {'name':name, 'lon':lon, 'lat':lat, 'minutes':minutes} 69 | 70 | l.append(d) 71 | 72 | return l 73 | 74 | if __name__ == "__main__": 75 | 76 | parser = argparse.ArgumentParser() 77 | 78 | parser.add_argument('-s', '--start', required=True, help="toto") 79 | parser.add_argument('-p', '--places', required=True, type=file) 80 | args = parser.parse_args() 81 | 82 | l = name_lon_lat_time_dictionaries(args.start, args.places) 83 | 84 | print json.dumps(l, indent = 4) 85 | -------------------------------------------------------------------------------- /README_googlemaps.txt: -------------------------------------------------------------------------------- 1 | googlemaps 1.0.2 2 | 16 Oct 2009 3 | README 4 | ================= 5 | 6 | This is a Python 2 module that provides access to the Google Maps and Local 7 | Search AJAX APIs. It has the following features: 8 | 9 | * Geocoding: convert a postal address to latitude and longitude 10 | * Reverse Geocoding: convert a latitude and longitude to a postal address 11 | * Driving Directions: turn-by-turn instructions for getting from one point to 12 | another, distance and duration of travel 13 | * Local Search: find businesses and other points of interest near a given 14 | location 15 | 16 | You will need a Google Maps API key and/or URL of the website where the 17 | information will be used (a referrer URL) to comply with Google's terms 18 | of service. Google also imposes additional restrictions on their 19 | service, including limiting the number of queries per day. This 20 | software is not related to Google Inc. in any way. See the included HTML 21 | documentation in doc/html or the interactive help for more. 22 | 23 | 24 | Dependencies 25 | ------------ 26 | This module should work with Python 2.3 - 2.6+, and with Python 3+ via 2to3. 27 | Its only dependency is the json module, included with Python versions 2.6 and 28 | later and available for download as simplejson for earlier versions. 29 | 30 | 31 | Installation 32 | ------------ 33 | You can install this package with easy_install using: 34 | 35 | easy_install googlemaps 36 | 37 | Or if you have already downloaded the .tar.gz archive: 38 | 39 | tar xzvf googlemaps-1.0.2.tar.gz 40 | cd googlemaps-1.0.2 41 | python setup.py install 42 | 43 | 44 | Contact Information 45 | ------------------- 46 | Author: John Kleint 47 | Internet: http://py-googlemaps.sourceforge.net 48 | E-mail: py-googlemaps-general@lists.sourceforge.net 49 | 50 | 51 | Copyright and Licensing 52 | ----------------------- 53 | This is free software, Copyright 2009 John Kleint, licensed under what is 54 | effectively a "Lesser Affero General Public License." It is identical to 55 | the GNU LGPLv3 but refers to the GNU Affero GPL v3 instead of the standard 56 | GNU GPL v3. 57 | 58 | In plain English, the intent of this license is the following: 59 | 60 | 1. You are free to use this library unmodified, without obligation, for any 61 | purpose. 62 | 63 | 2. If you modify this library and distribute software using the modified 64 | library, you must make your modified version of this library freely available 65 | under the same terms as this library. 66 | 67 | 3. If you modify this library and use it to provide a network service, 68 | you must make your modified version of this library freely available under 69 | the same terms as this library. 70 | 71 | 4. In any case, software using this library can be licensed however you like. 72 | 73 | See the files LICENSE.txt and AGPLv3.txt for details. 74 | 75 | 76 | Changelog 77 | --------- 78 | Version 1.0.2: 79 | Refactored GoogleMapsError to contain status codes. 80 | 81 | Version 1.0.1: 82 | Fixed local search throwing an exception if Google returned fewer results 83 | than requested. 84 | 85 | Version 1.0: 86 | Initial Release 87 | 88 | -------------------------------------------------------------------------------- /json_to_omnigraffle.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | 4 | import sys 5 | reload(sys) 6 | sys.setdefaultencoding('utf-8') 7 | 8 | """ 9 | - json_to_omnigraffle.py creates applescript code to show places in omnigraffle 10 | - the places are spread according to their travel time from center 11 | 12 | $ python places_to_applescript.py --center_lat 46.516765 --center_lon 6.630904 --show_radius True --places map.json > map.txt 13 | 14 | $ osascript map.txt 15 | """ 16 | 17 | from math import * 18 | import json 19 | import argparse 20 | 21 | offset_x = 538 22 | offset_y = 677 23 | 24 | place_radius = 1 25 | scale_factor = 7 26 | 27 | text_w = 30.0 28 | text_h = 14.0 29 | 30 | def bearing(lat1, lon1, lat2, lon2): 31 | lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) 32 | 33 | delta_lon = lon2 - lon1 34 | 35 | y = sin(delta_lon) * cos(lat2) 36 | x = cos(lat1) * sin(lat2) - sin(lat1) * cos(lat2) * cos(delta_lon) 37 | 38 | return atan2(y, x) 39 | 40 | if __name__ == '__main__': 41 | 42 | parser = argparse.ArgumentParser() 43 | 44 | parser.add_argument('-p', '--places', required=True, type=file) 45 | parser.add_argument('-lat', '--center_lat', required=True, type=float) 46 | parser.add_argument('-lon', '--center_lon', required=True, type=float) 47 | parser.add_argument('-r', '--show_radius', required=False, type=bool) 48 | args = parser.parse_args() 49 | 50 | places = json.load(args.places) 51 | 52 | print "tell application \"OmniGraffle Professional 5\"" 53 | print "\ttell canvas of front window" 54 | 55 | if args.show_radius: 56 | 57 | x = offset_x 58 | y = offset_y 59 | 60 | print "make new line at end of graphics with properties {point list: {{%f, %f}, {%f, %f}}}" % (x-20, y, x+20, y) 61 | print "make new line at end of graphics with properties {point list: {{%f, %f}, {%f, %f}}}" % (x, y-20, x, y+20) 62 | 63 | for radius in range(15, 120, 15): 64 | 65 | radius = radius * scale_factor 66 | 67 | x = offset_x - radius 68 | y = offset_y - radius 69 | 70 | print "\t\tmake new shape at end of graphics with properties {textPosition: {0.100000, 0.150000}, fill: no fill, draws shadow: false, size: {%f, %f}, flipped vertically: true, name: \"Circle\", origin: {%f, %f}, textSize: {0.800000, 0.700000}, flipped horizontally: true}" % (radius * 2, radius * 2, x, y) 71 | 72 | for p in places: 73 | 74 | b = bearing(args.center_lat, args.center_lon, p['lat'], p['lon']) 75 | angle = degrees(b) 76 | 77 | x = ((sin(b) * p['minutes'] * scale_factor) * 1) + offset_x 78 | y = ((cos(b) * p['minutes'] * scale_factor) * 1 * -1) + offset_y 79 | 80 | print "\t\tmake new shape at end of graphics with properties {textPosition:{0.1, 0.15}, draws shadow:false, size:{%f, %f}, name:\"Circle\", origin:{%f, %f}, textSize:{0.8, 0.7}}" % (place_radius * 2, place_radius * 2, x - place_radius, y - place_radius) 81 | print "\t\tmake new shape at end of graphics with properties {fill:no fill, draws shadow:false, size:{%f, %f}, side padding:0, autosizing:full, vertical padding:0, origin:{%f, %f}, text:{text:\"%s\", size: 9, alignment: left}, draws stroke:false}" % (text_w, text_h, x+10, y - place_radius - text_h/2.0, p['name']) 82 | print "" 83 | 84 | print "\tend tell" 85 | print "end tell" 86 | -------------------------------------------------------------------------------- /googlemaps.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | # Copyright 2009 John Kleint 4 | # 5 | # This is free software, licensed under the Lesser Affero General 6 | # Public License, available in the accompanying LICENSE.txt file. 7 | 8 | 9 | """ 10 | An easy-to-use Python wrapper for the Google Maps and Local Search APIs. 11 | 12 | * **Geocoding**: convert a postal address to latitude and longitude 13 | * **Reverse Geocoding**: find the nearest address to (lat, lng) 14 | * **Local Search**: find places matching a query near a given location 15 | * **Directions**: turn-by-turn directions, distance, time, etc. from A to B 16 | 17 | """ 18 | 19 | 20 | import urllib 21 | import urllib2 22 | try: 23 | import json 24 | except ImportError: 25 | import simplejson as json # pylint: disable-msg=F0401 26 | 27 | 28 | VERSION = '1.0.2' 29 | __all__ = ['GoogleMaps', 'GoogleMapsError'] 30 | 31 | 32 | def fetch_json(query_url, params={}, headers={}): # pylint: disable-msg=W0102 33 | """Retrieve a JSON object from a (parameterized) URL. 34 | 35 | :param query_url: The base URL to query 36 | :type query_url: string 37 | :param params: Dictionary mapping (string) query parameters to values 38 | :type params: dict 39 | :param headers: Dictionary giving (string) HTTP headers and values 40 | :type headers: dict 41 | :return: A `(url, json_obj)` tuple, where `url` is the final, 42 | parameterized, encoded URL fetched, and `json_obj` is the data 43 | fetched from that URL as a JSON-format object. 44 | :rtype: (string, dict or array) 45 | 46 | """ 47 | encoded_params = urllib.urlencode(params) 48 | url = query_url + encoded_params 49 | request = urllib2.Request(url, headers=headers) 50 | response = urllib2.urlopen(request) 51 | return (url, json.load(response)) 52 | 53 | 54 | class GoogleMapsError(Exception): 55 | """Base class for errors in the :mod:`googlemaps` module. 56 | 57 | Methods of the :class:`GoogleMaps` raise this when something goes wrong. 58 | 59 | """ 60 | #: See http://code.google.com/apis/maps/documentation/geocoding/index.html#StatusCodes 61 | #: for information on the meaning of these status codes. 62 | G_GEO_SUCCESS = 200 63 | G_GEO_SERVER_ERROR = 500 64 | G_GEO_MISSING_QUERY = 601 65 | G_GEO_UNKNOWN_ADDRESS = 602 66 | G_GEO_UNAVAILABLE_ADDRESS = 603 67 | G_GEO_BAD_KEY = 610 68 | G_GEO_TOO_MANY_QUERIES = 620 69 | 70 | _STATUS_MESSAGES = { 71 | G_GEO_SUCCESS : 'G_GEO_SUCCESS', 72 | G_GEO_SERVER_ERROR : 'G_GEO_SERVER_ERROR', 73 | G_GEO_MISSING_QUERY : 'G_GEO_MISSING_QUERY', 74 | G_GEO_UNKNOWN_ADDRESS : 'G_GEO_UNKNOWN_ADDRESS', 75 | G_GEO_UNAVAILABLE_ADDRESS : 'G_GEO_UNAVAILABLE_ADDRESS', 76 | G_GEO_BAD_KEY : 'G_GEO_BAD_KEY', 77 | G_GEO_TOO_MANY_QUERIES : 'G_GEO_TOO_MANY_QUERIES', 78 | } 79 | 80 | def __init__(self, status, url=None, response=None): 81 | """Create an exception with a status and optional full response. 82 | 83 | :param status: Either a ``G_GEO_`` code or a string explaining the 84 | exception. 85 | :type status: int or string 86 | :param url: The query URL that resulted in the error, if any. 87 | :type url: string 88 | :param response: The actual response returned from Google, if any. 89 | :type response: dict 90 | 91 | """ 92 | Exception.__init__(self, status) # Exception is an old-school class 93 | self.status = status 94 | self.response = response 95 | self.url = url 96 | 97 | def __str__(self): 98 | """Return a string representation of this :exc:`GoogleMapsError`.""" 99 | if self.status in self._STATUS_MESSAGES: 100 | if self.response is not None and 'responseDetails' in self.response: 101 | retval = 'Error %d: %s' % (self.status, self.response['responseDetails']) 102 | else: 103 | retval = 'Error %d: %s' % (self.status, self._STATUS_MESSAGES[self.status]) 104 | else: 105 | retval = str(self.status) 106 | return retval 107 | 108 | def __unicode__(self): 109 | """Return a unicode representation of this :exc:`GoogleMapsError`.""" 110 | return unicode(self.__str__()) 111 | 112 | 113 | STATUS_OK = GoogleMapsError.G_GEO_SUCCESS 114 | 115 | 116 | class GoogleMaps(object): 117 | """ 118 | An easy-to-use Python wrapper for the Google Maps and Local Search APIs. 119 | 120 | **Geocoding**: convert a postal address to latitude and longitude 121 | 122 | >>> from googlemaps import GoogleMaps 123 | >>> gmaps = GoogleMaps(api_key) 124 | >>> address = 'Constitution Ave NW & 10th St NW, Washington, DC' 125 | >>> lat, lng = gmaps.address_to_latlng(address) 126 | >>> print lat, lng 127 | 38.8921021 -77.0260358 128 | 129 | **Reverse Geocoding**: find the nearest address to (lat, lng) 130 | 131 | >>> destination = gmaps.latlng_to_address(38.887563, -77.019929) 132 | >>> print destination 133 | Independence and 6th SW, Washington, DC 20024, USA 134 | 135 | **Local Search**: find places matching a query near a given location 136 | 137 | >>> local = gmaps.local_search('cafe near ' + destination) 138 | >>> print local['responseData']['results'][0]['titleNoFormatting'] 139 | Vie De France Bakery & Cafe 140 | 141 | **Directions**: turn-by-turn directions, distance, time, etc. from 142 | point A to point B 143 | 144 | >>> directions = gmaps.directions(address, destination) 145 | >>> print directions['Directions']['Distance']['meters'] 146 | 1029 147 | >>> print directions['Directions']['Duration']['seconds'] 148 | 106 149 | >>> for step in directions['Directions']['Routes'][0]['Steps']: 150 | ... print step['descriptionHtml'] 151 | Head east on Constitution Ave NW toward 9th St NW 152 | Take the 2nd right onto 7th St NW 153 | Turn left at Independence Ave SW 154 | 155 | This software is in no way associated with or endorsed by Google Inc. 156 | Use of the Google Maps API is governed by its Terms of Service: 157 | http://code.google.com/apis/maps/terms.html. Note in particular that 158 | you will need your own Google Maps API key to use this service, 159 | and that there are rate limits to the number of requests you can 160 | make. 161 | 162 | """ 163 | 164 | _GEOCODE_QUERY_URL = 'http://maps.google.com/maps/geo?' 165 | _DIRECTIONS_QUERY_URL = 'http://maps.google.com/maps/nav?' 166 | _LOCAL_QUERY_URL = 'http://ajax.googleapis.com/ajax/services/search/local?' 167 | _LOCAL_RESULTS_PER_PAGE = 8 168 | MAX_LOCAL_RESULTS = 32 169 | 170 | def __init__(self, api_key='', referrer_url=''): 171 | """ 172 | Create a new :class:`GoogleMaps` object using the given `api_key` and 173 | `referrer_url`. 174 | 175 | :param api_key: Google Maps API key 176 | :type api_key: string 177 | :param referrer_url: URL of the website using or displaying information 178 | from this module. 179 | :type referrer_url: string 180 | 181 | Google requires API users to register for an API key before using the 182 | geocoding service; this can be done at 183 | http://code.google.com/apis/maps/signup.html. 184 | If you are not geocoding, you do not need an API key. 185 | 186 | Use of Google Local Search requires a referrer URL, generally the website 187 | where the retrieved information will be used. If you are not using 188 | Local Search, you do not need a referrer URL. 189 | 190 | """ 191 | self.api_key = api_key 192 | self.referrer_url = referrer_url 193 | 194 | def geocode(self, query, sensor='false', oe='utf8', ll='', spn='', gl=''): # pylint: disable-msg=C0103,R0913 195 | """ 196 | Given a string address `query`, return a dictionary of information about 197 | that location, including its latitude and longitude. 198 | 199 | Interesting bits: 200 | 201 | >>> gmaps = GoogleMaps(api_key) 202 | >>> address = '350 Fifth Avenue New York, NY' 203 | >>> result = gmaps.geocode(address) 204 | >>> placemark = result['Placemark'][0] 205 | >>> lng, lat = placemark['Point']['coordinates'][0:2] # Note these are backwards from usual 206 | >>> print lat, lng 207 | 40.6721118 -73.9838823 208 | >>> details = placemark['AddressDetails']['Country']['AdministrativeArea'] 209 | >>> street = details['Locality']['Thoroughfare']['ThoroughfareName'] 210 | >>> city = details['Locality']['LocalityName'] 211 | >>> state = details['AdministrativeAreaName'] 212 | >>> zipcode = details['Locality']['PostalCode']['PostalCodeNumber'] 213 | >>> print ', '.join((street, city, state, zipcode)) 214 | 350 5th Ave, Brooklyn, NY, 11215 215 | 216 | More documentation on the format of the return value can be found at 217 | Google's `geocoder return value`_ reference. (Note: Some places have 218 | a `'SubAdministrativeArea'` and some don't; sometimes a `'Locality'` 219 | will have a `'DependentLocality'` and some don't.) 220 | 221 | .. _`geocoder return value`: http://code.google.com/apis/maps/documentation/geocoding/index.html#JSON 222 | 223 | :param query: Address of location to be geocoded. 224 | :type query: string 225 | :param sensor: ``'true'`` if the address is coming from, say, a GPS device. 226 | :type sensor: string 227 | :param oe: Output Encoding; best left at ``'utf8'``. 228 | :type oe: string 229 | :param ll: `lat,lng` of the viewport center as comma-separated string; 230 | must be used with `spn` for Viewport Biasing 231 | :type ll: string 232 | :param spn: The "span" of the viewport; must be used with `ll`. 233 | :type spn: string 234 | :param gl: Two-character ccTLD_ for country code biasing. 235 | :type gl: string 236 | :returns: `geocoder return value`_ dictionary 237 | :rtype: dict 238 | :raises GoogleMapsError: if there is something wrong with the query. 239 | 240 | More information on the types and meaning of the parameters can be found 241 | at the `Google HTTP Geocoder`__ site. 242 | 243 | __ http://code.google.com/apis/maps/documentation/geocoding/index.html 244 | .. _ccTLD: http://en.wikipedia.org/wiki/Country_code_top-level_domain 245 | 246 | """ 247 | if (ll is None and spn is not None) or (ll is not None and spn is None): 248 | raise GoogleMapsError('Both ll and spn must be provided.') 249 | params = { 250 | 'q': query, 251 | 'key': self.api_key, 252 | 'sensor': sensor, 253 | 'output': 'json', 254 | 'oe': oe, 255 | 'll': ll, 256 | 'spn': spn, 257 | 'gl': gl, 258 | } 259 | url, response = fetch_json(self._GEOCODE_QUERY_URL, params=params) 260 | status_code = response['Status']['code'] 261 | if status_code != STATUS_OK: 262 | raise GoogleMapsError(status_code, url, response) 263 | return response 264 | 265 | def reverse_geocode(self, lat, lng, sensor='false', oe='utf8', ll='', spn='', gl=''): # pylint: disable-msg=C0103,R0913 266 | """ 267 | Converts a (latitude, longitude) pair to an address. 268 | 269 | Interesting bits: 270 | 271 | >>> gmaps = GoogleMaps(api_key) 272 | >>> reverse = gmaps.reverse_geocode(38.887563, -77.019929) 273 | >>> address = reverse['Placemark'][0]['address'] 274 | >>> print address 275 | Independence and 6th SW, Washington, DC 20024, USA 276 | >>> accuracy = reverse['Placemark'][0]['AddressDetails']['Accuracy'] 277 | >>> print accuracy 278 | 9 279 | 280 | :param lat: latitude 281 | :type lat: float 282 | :param lng: longitude 283 | :type lng: float 284 | :return: `Reverse geocoder return value`_ dictionary giving closest 285 | address(es) to `(lat, lng)` 286 | :rtype: dict 287 | :raises GoogleMapsError: If the coordinates could not be reverse geocoded. 288 | 289 | Keyword arguments and return value are identical to those of :meth:`geocode()`. 290 | 291 | .. _`Reverse geocoder return value`: 292 | http://code.google.com/apis/maps/documentation/geocoding/index.html#ReverseGeocoding 293 | 294 | """ 295 | return self.geocode("%f,%f" % (lat, lng), sensor=sensor, oe=oe, ll=ll, spn=spn, gl=gl) 296 | 297 | def address_to_latlng(self, address): 298 | """ 299 | Given a string `address`, return a `(latitude, longitude)` pair. 300 | 301 | This is a simplified wrapper for :meth:`geocode()`. 302 | 303 | :param address: The postal address to geocode. 304 | :type address: string 305 | :return: `(latitude, longitude)` of `address`. 306 | :rtype: (float, float) 307 | :raises GoogleMapsError: If the address could not be geocoded. 308 | 309 | """ 310 | return tuple(self.geocode(address)['Placemark'][0]['Point']['coordinates'][1::-1]) 311 | 312 | def latlng_to_address(self, lat, lng): 313 | """ 314 | Given a latitude `lat` and longitude `lng`, return the closest address. 315 | 316 | This is a simplified wrapper for :meth:`reverse_geocode()`. 317 | 318 | :param lat: latitude 319 | :type lat: float 320 | :param lng: longitude 321 | :type lng: float 322 | :return: Closest postal address to `(lat, lng)`, if any. 323 | :rtype: string 324 | :raises GoogleMapsError: if the coordinates could not be converted 325 | to an address. 326 | 327 | """ 328 | return self.reverse_geocode(lat, lng)['Placemark'][0]['address'] 329 | 330 | def local_search(self, query, numresults=_LOCAL_RESULTS_PER_PAGE, **kwargs): 331 | """ 332 | Searches Google Local for the string `query` and returns a 333 | dictionary of the results. 334 | 335 | >>> gmaps = GoogleMaps(api_key) 336 | >>> local = gmaps.local_search('sushi san francisco, ca') 337 | >>> result = local['responseData']['results'][0] 338 | >>> print result['titleNoFormatting'] 339 | Sushi Groove 340 | >>> print result['streetAddress'] 341 | 1916 Hyde St 342 | >>> print result['phoneNumbers'][0]['number'] 343 | (415) 440-1905 344 | 345 | For more information on the available data, see Google's documentation on 346 | `AJAX result structure`_ and `local result properties`_. 347 | 348 | The return value of this method is slightly different than that 349 | documented by Google; it attempts to stuff as many results as 350 | possible, from several queries (up to `numresults`), into the 351 | ``['responseData']['results']`` array. As a result, fields of 352 | the results referencing this array (such as ``'cursor'``, 353 | ``'currentPageIndex'``, ``'moreResultsUrl'``) may not make 354 | complete sense. 355 | 356 | This method may return fewer results than you ask for; Google Local 357 | returns a maximum of :data:`GoogleMaps.MAX_LOCAL_RESULTS` results. 358 | 359 | :param query: String containing a search and a location, such as 360 | ``'Sushi San Francisco, CA'``. 361 | :type query: string 362 | :param numresults: Number of results to return, up to a maximum of :data:`MAX_LOCAL_RESULTS`. 363 | :type numresults: int 364 | :param kwargs: You can pass additional `AJAX search arguments`_ and they 365 | will be tacked on to the query. 366 | :return: A Google `AJAX result structure`_. 367 | :rtype: dict 368 | :raises GoogleMapsError: If the query was malformed. 369 | 370 | .. _AJAX result structure: http://code.google.com/apis/ajaxsearch/documentation/#fonje 371 | .. _local result properties: http://code.google.com/apis/ajaxsearch/documentation/reference.html#_class_GlocalResult 372 | .. _AJAX search arguments: http://code.google.com/apis/ajaxsearch/documentation/reference.html#_intro_fonje 373 | 374 | """ 375 | params = { 376 | 'q': query, 377 | 'v': '1.0', 378 | 'rsz': 'large', # Return 8 results per page instead of 4 379 | #'key': self.api_key, # Google Local seems not to like empty keys 380 | } 381 | params.update(kwargs) 382 | 383 | start = 0 384 | results = None 385 | while start < numresults and start < self.MAX_LOCAL_RESULTS: 386 | params['start'] = start 387 | url, response = fetch_json(self._LOCAL_QUERY_URL, params=params, headers={'Referer': self.referrer_url}) 388 | status_code = response['responseStatus'] 389 | if status_code != STATUS_OK: 390 | raise GoogleMapsError(status_code, url=url, response=response) 391 | if results is None: 392 | results = response 393 | else: 394 | results['responseData']['results'].extend(response['responseData']['results']) 395 | 396 | # If we didn't get a full page of results, Google has run out; don't try again 397 | if len(response['responseData']['results']) < self._LOCAL_RESULTS_PER_PAGE: 398 | break 399 | start += len(response['responseData']['results']) 400 | 401 | if results is not None: 402 | results['responseData']['results'] = results['responseData']['results'][:numresults] 403 | return results 404 | 405 | def directions(self, origin, destination, **kwargs): 406 | """ 407 | Get driving directions from `origin` to `destination`. 408 | 409 | Interesting bits: 410 | 411 | >>> gmaps = GoogleMaps(api_key) 412 | >>> start = 'Constitution Ave NW & 10th St NW, Washington, DC' 413 | >>> end = 'Independence and 6th SW, Washington, DC 20024, USA' 414 | >>> dirs = gmaps.directions(start, end) 415 | >>> time = dirs['Directions']['Duration']['seconds'] 416 | >>> dist = dirs['Directions']['Distance']['meters'] 417 | >>> route = dirs['Directions']['Routes'][0] 418 | >>> for step in route['Steps']: 419 | ... print step['Point']['coordinates'][1], step['Point']['coordinates'][0] 420 | ... print step['descriptionHtml'] 421 | 38.8921 -77.02604 422 | Head east on Constitution Ave NW toward 9th St NW 423 | 38.89208 -77.02191 424 | Take the 2nd right onto 7th St NW 425 | 38.88757 -77.02191 426 | Turn left at Independence Ave SW 427 | 428 | :param origin: Starting address 429 | :type origin: string 430 | :param destination: Ending address 431 | :type destination: string 432 | :param kwargs: You can pass additional URL parameters as keyword arguments, 433 | but this functionality is not documented. 434 | :return: Dictionary containing driving directions. 435 | :rtype: dict 436 | :raises GoogleMapsError: If Google Maps was unable to find directions. 437 | 438 | """ 439 | params = { 440 | 'q': 'from:%s to:%s' % (origin, destination), 441 | 'output': 'json', 442 | 'oe': 'utf8', 443 | 'key': self.api_key, 444 | } 445 | params.update(kwargs) 446 | 447 | url, response = fetch_json(self._DIRECTIONS_QUERY_URL, params=params) 448 | status_code = response['Status']['code'] 449 | if status_code != STATUS_OK: 450 | raise GoogleMapsError(status_code, url=url, response=response) 451 | return response 452 | 453 | 454 | if __name__ == "__main__": 455 | import sys 456 | 457 | def main(argv): 458 | """ 459 | Geocodes a location given on the command line. 460 | 461 | Usage: 462 | googlemaps.py "1600 amphitheatre mountain view ca" [YOUR_API_KEY] 463 | googlemaps.py 37.4219720,-122.0841430 [YOUR_API_KEY] 464 | 465 | When providing a latitude and longitude on the command line, ensure 466 | they are separated by a comma and no space. 467 | 468 | """ 469 | 470 | if len(argv) < 2 or len(argv) > 4: 471 | print main.__doc__ 472 | sys.exit(1) 473 | 474 | query = argv[1] 475 | if len(argv) == 3: 476 | api_key = argv[2] 477 | else: 478 | api_key = raw_input("Google Maps API key: ") 479 | 480 | gmap = GoogleMaps(api_key) 481 | try: 482 | result = gmap.geocode(query) 483 | except GoogleMapsError, err: 484 | sys.stderr.write('%s\n%s\nResponse:\n' % (err.url, err)) 485 | json.dump(err.response, sys.stderr, indent=4) 486 | sys.exit(1) 487 | json.dump(result, sys.stdout, indent=4) 488 | sys.stdout.write('\n') 489 | 490 | main(sys.argv) 491 | --------------------------------------------------------------------------------