├── 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 |
--------------------------------------------------------------------------------