Skip to content

Commit fa4c2fa

Browse files
committed
Merge remote-tracking branch 'upstream/master'
Conflicts: googlemaps/__init__.py
2 parents 00371f8 + 488a3d1 commit fa4c2fa

12 files changed

Lines changed: 363 additions & 43 deletions

‎.gitignore‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,7 @@ Thumbs.db
1515
# Sphinx documentation #
1616
########################
1717
docs/_build/
18+
19+
20+
# vim
21+
*.swp

‎README.md‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,10 +2,9 @@ Generating documentation:
22

33
$ sphinx-build -b html docs docs/html
44

5-
65
Installing dependencies:
76

8-
$ [sudo] pip install requests
7+
$ pip install requests responses
98

109
Running tests:
1110

‎googlemaps/__init__.py‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from googlemaps.common import Context
22
from googlemaps.directions import directions
3+
from googlemaps.distance_matrix import distance_matrix
4+
from googlemaps.elevation import elevation
5+
from googlemaps.elevation import elevation_along_path
36
from googlemaps.geocoding import geocode
47
from googlemaps.geocoding import reverse_geocode
58
from googlemaps.timezone import timezone
69

710
# Allow sphinx to pick up these symbols for the documentation.
8-
__all__ = ['Context', 'geocode', 'reverse_geocode', 'directions', 'timezone']
11+
__all__ = ['Context', 'geocode', 'reverse_geocode', 'directions',
12+
'distance_matrix', 'elevation', 'elevation_along_path',
13+
'timezone']

‎googlemaps/common.py‎

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,9 @@
66
import requests
77

88

9+
_VERSION = "0.1"
10+
_USER_AGENT = "GoogleGeoApiClientPython/%s" % _VERSION
11+
912
class Context(object):
1013
"""Holds state between requests, such as credentials (API key), timeout
1114
settings"""
@@ -27,7 +30,10 @@ def __init__(self, key=None, client_id=None, client_secret=None,
2730
secret (base64 encoded).
2831
:type client_secret: basestring
2932
"""
30-
# TODO(lukem): simple key validation.
33+
if not key.startswith("AIza"):
34+
raise Exception("Must provide API key or enterprise credentials "
35+
"with context object.")
36+
3137
self.key = key
3238
self.timeout = timeout
3339
self.client_id = client_id
@@ -46,12 +52,12 @@ def _get(ctx, url, params):
4652

4753
# TODO(mdr-eng): implement rate limiting, retries, etc.
4854
# TODO(mdr-eng): implement enterprise key signing
49-
# TODO(mdr-eng): enforce use of API keys/credentials
5055
# TODO(mdr-eng): add jitter (might not be necessary since most uses will be
5156
# single threaded)
5257
params["key"] = ctx.key
5358
resp = requests.get(
5459
"https://maps.googleapis.com" + url,
60+
headers={"User-Agent": _USER_AGENT},
5561
verify=True, # NOTE(cbro): verify SSL certs.
5662
params=params)
5763

@@ -63,7 +69,6 @@ def _get(ctx, url, params):
6369

6470
body = resp.json()
6571

66-
# TODO(mdr-eng): NOT_FOUND for directions?
6772
if body["status"] == "OK" or body["status"] == "NO_RESULTS":
6873
return body
6974

‎googlemaps/directions.py‎

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
def directions(ctx, origin, destination,
88
mode=None, waypoints=None, alternatives=False, avoid=None,
99
language=None, units=None, region=None, departure_time=None,
10-
arrival_time=None):
10+
arrival_time=None, optimize_waypoints=False):
1111
"""Get directions between an origin point and a destination point.
1212
1313
:param ctx: Shared googlemaps.Context
@@ -22,7 +22,7 @@ def directions(ctx, origin, destination,
2222
:type destination: basestring or dict or tuple
2323
2424
:param mode: Specifies the mode of transport to use when calculating
25-
directions.
25+
directions. One of "driving", "walking", "bicycling" or "transit"
2626
:type mode: basestring
2727
2828
:param waypoints: Specifies an array of waypoints. Waypoints alter a
@@ -54,24 +54,30 @@ def directions(ctx, origin, destination,
5454
directions.
5555
:type arrival_time: int or datetime.datetime
5656
57+
:param optimize_waypoints: Optimize the provided route by rearranging the
58+
waypoints in a more efficient order.
59+
:type optimize_waypoints: bool
60+
5761
:rtype: list of routes
5862
"""
59-
# TODO(mdr-eng): Add optimize_waypoints=True.
60-
61-
origin = _convert_waypoint(origin)
62-
destination = _convert_waypoint(destination)
6363

6464
params = {
65-
"origin": origin,
66-
"destination": destination
65+
"origin": _convert_waypoint(origin),
66+
"destination": _convert_waypoint(destination)
6767
}
6868

6969
if mode:
70+
if mode not in ["driving", "walking", "bicycling", "transit"]:
71+
raise Exception("Invalid travel mode.")
7072
params["mode"] = mode
7173

7274
if waypoints:
7375
waypoints = convert.as_list(waypoints)
7476
waypoints = [_convert_waypoint(waypoint) for waypoint in waypoints]
77+
78+
if optimize_waypoints:
79+
waypoints = ["optimize:true"] + waypoints
80+
7581
params["waypoints"] = convert.join_list("|", waypoints)
7682

7783
if alternatives:

‎googlemaps/distance_matrix.py‎

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
"""Performs requests to the Google Maps Distance Matrix API."""
2+
3+
from googlemaps import convert
4+
from googlemaps import common
5+
from googlemaps.convert import as_list
6+
7+
8+
def distance_matrix(ctx, origins, destinations,
9+
mode=None, language=None, avoid=None, units=None,
10+
departure_time=None):
11+
""" Gets travel distance and time for a matrix of origins and destinations.
12+
13+
:param ctx: Shared googlemaps.Context
14+
:type ctx: googlemaps.Context
15+
16+
:param origins: One or more addresses and/or latitude/longitude values,
17+
from which to calculate distance and time. If you pass an address
18+
as a string, the service will geocode the string and convert it to
19+
a latitude/longitude coordinate to calculate directions.
20+
:type origins: list of basestrings, dicts or tuples
21+
22+
:param destinations: One or more addresses and/or lat/lng values, to
23+
which to calculate distance and time. If you pass an address as a
24+
string, the service will geocode the string and convert it to a
25+
latitude/longitude coordinate to calculate directions.
26+
:type destinations: list of basestrings, dicts or tuples
27+
28+
:param mode: Specifies the mode of transport to use when calculating
29+
directions. Valid values are "driving", "walking" or "bicycling".
30+
:type mode: basestring
31+
32+
:param language: The language in which to return results.
33+
:type language: basestring
34+
35+
:param avoid: Indicates that the calculated route(s) should avoid the
36+
indicated features. Valid values are "tolls", "highways" or "ferries"
37+
:type avoid: basestring
38+
39+
:param units: Specifies the unit system to use when displaying results.
40+
Valid values are "metric" or "imperial"
41+
:type units: basestring
42+
43+
:param departure_time: Specifies the desired time of departure as seconds
44+
since midnight, January 1, 1970 UTC. The departure time may be
45+
specified by Google Maps API for Work customers to receive trip
46+
duration considering current traffic conditions. The departure_time
47+
must be set to within a few minutes of the current time.
48+
:type departure_time: int or datetime.datetime
49+
50+
:rtype: matrix of distances. Results are returned in rows, each row
51+
containing one origin paired with each destination.
52+
"""
53+
54+
params = {
55+
"origins": _convert_path(origins),
56+
"destinations": _convert_path(destinations)
57+
}
58+
59+
if mode:
60+
if mode not in ["driving", "walking", "bicycling"]:
61+
raise Exception("Invalid travel mode.")
62+
params["mode"] = mode
63+
64+
if language:
65+
params["language"] = language
66+
67+
if avoid:
68+
if avoid not in ["tolls", "highways", "ferries"]:
69+
raise Exception("Invalid route restriction.")
70+
params["avoid"] = avoid
71+
72+
if units:
73+
params["units"] = units
74+
75+
if departure_time:
76+
params["departure_time"] = convert.time(departure_time)
77+
78+
return common._get(ctx, "/maps/api/distancematrix/json", params)
79+
80+
81+
def _convert_path(waypoints):
82+
# Handle the single-tuple case
83+
if type(waypoints) is tuple:
84+
waypoints = [waypoints]
85+
else:
86+
waypoints = as_list(waypoints)
87+
88+
return convert.join_list("|",
89+
[(k if isinstance(k, basestring) else convert.latlng(k))
90+
for k in waypoints])

‎googlemaps/elevation.py‎

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
"""Performs requests to the Google Maps Elevation API."""
2+
from googlemaps import common
3+
from googlemaps import convert
4+
5+
def elevation(ctx, locations):
6+
"""
7+
Provides elevation data for locations provided on the surface of the
8+
earth, including depth locations on the ocean floor (which return negative
9+
values)
10+
11+
:param ctx: Shared googlemaps.Context
12+
:type ctx: googlemaps.Context
13+
14+
:param locations: A single latitude/longitude tuple or dict, or a list of
15+
latitude/longitude tuples or dicts from which you wish to calculate
16+
elevation data.
17+
:type locations: list or tuple
18+
19+
:rtype: list of elevation data responses
20+
"""
21+
params = {}
22+
if type(locations) is tuple:
23+
locations = [locations]
24+
25+
params["locations"] = convert.join_list("|",
26+
[convert.latlng(k) for k in convert.as_list(locations)])
27+
28+
return common._get(ctx, "/maps/api/elevation/json", params)["results"]
29+
30+
def elevation_along_path(ctx, path, samples):
31+
"""
32+
Provides elevation data sampled along a path on the surface of the earth.
33+
34+
:param ctx: Shared googlemaps.Context
35+
:type ctx: googlemaps.Context
36+
37+
:param path: A encoded polyline string, or a list of
38+
latitude/longitude tuples from which you wish to calculate
39+
elevation data.
40+
:type path: str or list
41+
42+
:param samples: The number of sample points along a path for which to
43+
return elevation data.
44+
:type samples: int
45+
46+
:rtype: list of elevation data responses
47+
"""
48+
49+
if type(path) is str:
50+
path = "enc:%s" % path
51+
else:
52+
path = convert.join_list("|",
53+
[convert.latlng(k) for k in convert.as_list(path)])
54+
55+
params = {
56+
"path": path,
57+
"samples": samples
58+
}
59+
60+
return common._get(ctx, "/maps/api/elevation/json", params)["results"]

‎samples.py‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ def main():
88
key="AIzaSyDyZdCabN8GKh786tdj16gq80xalbbfqDM",
99
timeout=5)
1010

11+
locations = [(40.714728, -73.998672), (-34.397, 150.644)]
12+
print googlemaps.elevation(c, locations)
13+
print googlemaps.elevation(c, (40.714728, -73.998672))
14+
15+
print googlemaps.elevation_along_path(c, locations, 3)
16+
1117
geocoded = googlemaps.geocode(c, "48 Pirrama Rd")
1218

1319
print geocoded

‎test/test_common.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
"""Tests across modules (or common module)."""
2+
3+
import googlemaps
4+
import unittest
5+
import responses
6+
7+
class CommonTest(unittest.TestCase):
8+
9+
def test_no_api_key(self):
10+
with self.assertRaises(Exception):
11+
ctx = googlemaps.Context()
12+
googlemaps.directions(ctx, "Sydney", "Melbourne")
13+
14+
def test_invalid_api_key(self):
15+
with self.assertRaises(Exception):
16+
ctx = googlemaps.Context(key="Invalid key.")
17+
googlemaps.directions(ctx, "Sydney", "Melbourne")
18+
19+
@responses.activate
20+
def test_ua_sent(self):
21+
responses.add(responses.GET,
22+
"https://maps.googleapis.com/maps/api/geocode/json",
23+
body='{"status":"OK","results":[]}',
24+
status=200,
25+
content_type='application/json')
26+
27+
ctx = googlemaps.Context(key="AIzaasdf")
28+
googlemaps.geocode(ctx, "Sesame St.")
29+
30+
self.assertEquals(1, len(responses.calls))
31+
user_agent = responses.calls[0].request.headers["User-Agent"]
32+
self.assertTrue(user_agent.startswith("GoogleGeoApiClientPython"))

0 commit comments

Comments
 (0)