The Google Maps Platform offers a number of Web Services all things map
related: End users can retrieve geographic coordinates from addresses, lookup
an address from a geographic coordinate pair, retrieve elevation data, travel
distance and time and directions in a programmatic way. In this post, we
demonstrate how to query and retrieve various geogrpahic data using the Web
Services API directly and without the aid of any 3rd-party Python packages
(the one exception being requests
).
Note that in order to access the Web Services API, you need to obtain an API key. Details can be found here.
Also note that although we do not rely on 3rd-party packages in this post,
packages do exist that help simplify interacting with the Google Maps
Platform. One such package is googlemaps
.
In the remainder of this post, we demonstrate how to perform the following:
- Determine latitude and longitude from a street address
- Address lookup from (latitude, longitude) coordinate pair
- Compute the distance between two sets of coordinate pairs
- Compute the elevation of an address or coordinate pair
- Retrieve directions between two locations (addresses or coordinates)
- Place a pin corresponding to a particular address or coordinate pair on a map
In addition task-specific parameters, each of the functions introduced in this article will include four additional parameters:
authkey
: Required to access the Google Maps Platformproxy
: Proxy server specific details (Defaults to None)full_response
: Return unmodified json response as opposed to parsed response (Defaults to False)dryrun
: If True, return encoded url then exit (Defaults to False)
If proxy
is specified, it should be a dict, similar to the following:
proxydict = {
"http" :"http://USERNAME:PASSWORD@proxy.address.com:PORT/",
"https":"https://USERNAME:PASSWORD@proxy.address.com:PORT/",
}
Determine Latitude and Longitude from a Street Address
For this example, we introduce get_latlng
. Task-specific arguments include:
address
: String address for which to lookup (lat, lng)
The implementation:
"""
Retrieve coordinates for specified address using Google Maps API.
"""
import re
import sys
import os
import requests
API_KEY = "API_KEY"
def get_latlng(address, authkey, proxy=None, full_response=False, dryrun=False):
"""
Obtain geographic coordinates corresponding to address.
Returns
-------
dict
Either dict of parsed values or full json response as dict.
References
----------
https://developers.google.com/maps/documentation/geocoding/start
"""
baseurl = "https://maps.googleapis.com/maps/api/geocode/json?address="
addrstr = re.sub("\s+", "+", address.strip())
fullurl = baseurl + addrstr + "&key=" + authkey
if dryrun:
result = {"url":fullurl, "lat":None, "lng":None}
else:
response = requests.get(fullurl, proxies=proxy).json()
if full_response:
result = response
else:
coords = response["results"][0]["geometry"]["location"]
result = {"url":fullurl, "lat":coords["lat"], "lng":coords["lng"]}
return(result)
We can test get_latlng
with the address of CNA’s home office, 151 N.Randolph, Chicago, IL 60606
:
>>> addr = "151 N. Franklin Chicago IL 60606"
>>> resp = get_latlng(addr, authkey=API_KEY)
>>> lat, lng = resp["lat"], resp["lng"]
>>> print(f"(Latitude, Longitude): {lat}, {lng}")
(Latitude, Longitude): 41.8845877, -87.6352125
get_latlng
returns a dict with three keys:url
, corresponding to the
encoded url submitted to the API, lat
representing the latitude of the
address and lng
the longitude of the address.
Address Retrieval from (latitude, longitude) Coordinate Pair
Next we introduce get_address
. The call signature is identical to
get_latlng
, except address
is replaced with coords
:
coords
: Coordinate pair for location of interest
def get_address(coords, authkey, proxy=None, full_response=False, dryrun=False):
"""
Reverse geocode lookup. For a given coordinate pair, return the
associated address, or most proximate address to the location.
Returns
-------
dict
Either dict of parsed values or full json response as dict.
References
----------
https://developers.google.com/maps/documentation/geocoding/start
"""
baseurl = "https://maps.googleapis.com/maps/api/geocode/json?latlng="
locstr = locstr1 = ",".join(str(i) for i in coords)
fullurl = baseurl + locstr + "&key=" + authkey
if dryrun:
result = {"url":fullurl, "address":None}
else:
response = requests.get(fullurl, proxies=proxy).json()
if full_response:
result = response
else:
address = response["results"][0]["formatted_address"].strip()
result = {"url":fullurl, "address":address}
return(result)
We test get_address
with the coordinates returned by get_latlng
,
(41.8845877, -87.6352125). We anticipate the response to be at or near
151 N.Randolph, Chicago, IL 60606
:
>>> coords = (41.8845877,-87.6352125)
>>> resp = get_address(coords, authkey=API_KEY)
>>> resp["address"]
'151 N Franklin St, Chicago, IL 60606, USA'
Determine Travel Distance Between Two Sets of Coordinate Pairs
In a previous post, we introduced a method to determine the air distance between two non-antipodal locations on the Earth’s surface using the Haversine formula. Alternatively, we can use the Google Maps Distance Matrix API to compute the distance between two addresses or sets coordinate pairs, but the distance will be the driving distance, not the air distance.
Task specific arguments for get_dist
include:
- loc1: List of geographic coordinates (lat, lng) or address as str
- loc2: List of geographic coordinates (lat, lng) or address as str
def get_dist(loc1, loc2, authkey, proxy=None, full_response=False,
dryrun=False):
"""
Compute distance between two addresses or coordinate pairs.
Returns
-------
dict
Either dict of parsed values or full json response as dict.
References
----------
https://developers.google.com/maps/documentation/distance-matrix/start
"""
baseurl = "https://maps.googleapis.com/maps/api/distancematrix/json?units=metric"
# locs are either a sequence of coordinates or string address.
if isinstance(loc1, (list, tuple)):
locstr1 = ",".join(str(i) for i in loc1)
else:
locstr1 = re.sub("\s+", "+", loc1.strip())
if isinstance(loc2, (list, tuple)):
locstr2 = ",".join(str(i) for i in loc2)
else:
locstr2 = re.sub("\s+", "+", loc2.strip())
initstr = "&origins=" + locstr1
deststr = "&destinations=" + locstr2 + "&key=" + authkey
fullurl = baseurl + initstr + deststr
if dryrun:
result = {"url":fullurl, "distance":None}
else:
response = requests.get(fullurl, proxies=proxy).json()
if full_response:
result = response
else:
distance = int(response["rows"][0]["elements"][0]["distance"]["value"]) / 1000
result = {"url":fullurl, "distance":distance}
return(result)
Let’s retrieve the unprocessed response first by setting full_response=True
,
then the processed response after. To test get_dist
, we request the distance
between London (51.509865, -0.118092) and Berlin (52.520008, 13.404954):
>>> coords1 = (51.509865, -0.118092)
>>> coords2 = (52.520008, 13.404954)
>>> get_dist(coords1, coords2, authkey=API_KEY, full_response=True)
{'destination_addresses': ['B2 7, 10178 Berlin, Germany'],
'origin_addresses': ['1 Waterloo Bridge, London WC2R 2AB, UK'],
'rows': [{'elements': [{'distance': {'text': '1,095 km', 'value': 1094974},
'duration': {'text': '12 hours 2 mins', 'value': 43318},
'status': 'OK'}]}],
'status': 'OK'}
And the processed response:
>>> coords1 = (51.509865, -0.118092)
>>> coords2 = (52.520008, 13.404954)
>>> get_dist(coords1, coords2, authkey=API_KEY, full_response=False)["distance"]
1094.974
Determine the Elevation of an Address or Coordinate Pair
get_elevation
takes one task-specific argument:
loc
: The target location for elevation determination as coordinate pair or address as string
def get_elevation(loc, authkey, proxy=None, full_response=False,
dryrun=False):
"""
Return the elevation of the specified location.
Returns
-------
dict
Either dict of parsed values or full json response as dict.
References
----------
.. [1] https://developers.google.com/maps/documentation/elevation/start
"""
baseurl = "https://maps.googleapis.com/maps/api/elevation/json?locations="
if isinstance(loc, (list, tuple)):
locstr = ",".join(str(i) for i in loc)
else:
locstr = re.sub("\s+", "+", loc.strip())
fullurl = baseurl + locstr + "&key=" + authkey
if dryrun:
result = {"url":fullurl, "elevation":None}
else:
response = requests.get(fullurl, proxies=proxy).json()
if full_response:
result = response
else:
elevation = response["results"][0]["elevation"]
result = {"url":fullurl, "elevation":elevation}
return(result)
Determining the elevation of Atlanta, Georgia (33.753746, -84.386330):
>>> coords = (33.753746, -84.386330)
>>> get_elevation(coords, authkey=API_KEY)["elevation"]
312.0538635253906
Retrieve Directions Between Two Locations
get_directions
returns the directions between two locations, provided as a
coordinate pair or as string addresses. Task specific arguments are:
start
: Starting address as string or coordinate pairend
: Ending address as string or coordinate pair
def get_directions(start, end, authkey, proxy=None, full_response=False,
dryrun=False):
"""
Generate directions from start location to end location.
Returns
-------
dict
Either dict of parsed values or full json response as dict.
References
----------
https://developers.google.com/maps/documentation/directions/start
"""
baseurl = "https://maps.googleapis.com/maps/api/directions/json?"
if isinstance(start, (list, tuple)):
initstr = ",".join(str(i) for i in start)
else:
initstr = re.sub("\s+", "+", start.strip())
if isinstance(end, (list, tuple)):
deststr = ",".join(str(i) for i in end)
else:
deststr = re.sub("\s+", "+", end.strip())
fullurl = \
baseurl + "origin=" + initstr + "&destination=" + deststr + "&key=" + authkey
if dryrun:
result = {"url":fullurl, "directions":None}
else:
response = requests.get(fullurl, proxies=proxy).json()
if full_response:
result = response
else:
# Parse html instructions for plain-text display.
stepslist = response["routes"][0]["legs"][0]["steps"]
directions = [i["html_instructions"] for i in stepslist]
directions = [re.sub(r"</?b>", "", i) for i in directions]
directions = [re.sub(r"<div.*?>", " ", i) for i in directions]
directions = [re.sub(r"</div>", "", i) for i in directions]
directions = [re.sub(r" ", "", i) for i in directions]
result = {"url":fullurl, "directions":directions}
return(result)
To test, we request directions between Fermilab in Batavia, Illinois and Argonne National Laboratory in Lemont, Illinois:
>>> addr1 = "Kirk Road and Pine Street Batavia IL 60510-5011"
>>> addr2 = "9700 Cass Avenue, Lemont, IL 60439"
>>> get_directions(addr1, addr2, authkey=API_KEY)["directions"]
['Head west on Pine St toward Kirk Rd',
'Turn left onto Woodland Hills Rd',
'Turn left onto Chillem Dr',
'Turn right onto Kirk Rd',
'Continue onto N Farnsworth Ave',
'Take the Route 88 E ramp to Chicago Toll road',
'Merge onto I-88 E Toll road',
'Take exit 131 toward Joliet Toll road',
'Merge onto I-355 S Toll road',
'Keep left at the fork to stay on I-355 S Toll road',
'Take exit 12B to merge onto I-55 N toward Chicago',
'Take exit 271A for S Lemont Rd',
'Merge onto Lemont Rd',
'Turn left onto Westgate Rd Partial restricted usage road',
'Turn right onto Outer Cir Restricted usage road',
'Turn right onto Bluff Rd/Southwood Dr Restricted usage road Destination will be on the right']
Place a Pin Corresponding to a Particular Address or Coordinate Pair on a Map
pin_address
takes a single task-specific argument loc
, representing the target address.
import webbrowser
def pin_address(loc, authkey):
"""
Set pin at location given by address using google maps API.
"""
baseurl = r"https://www.google.com/maps/search/?api=1&query="
if isinstance(loc, (list, tuple)):
locstr = ",".join(str(i) for i in loc)
else:
locstr = re.sub("\s+", "+", loc.strip())
fullurl = baseurl + locstr + "&key=" + authkey
webbrowser.open(fullurl, new=2)
return
We test pin_address
by placing a pin at Argonne National Laboratory:
>>> addr = "9700 Cass Avenue, Lemont, IL 60439"
>>> pin_address(addr, authkey=API_KEY)
Renders the following:
Conclusion
In this post, we introduced how to access the Google Maps API directly, without the aid of 3rd-party utilities. There are other use cases in which the Google Maps API can be leveraged, and I encourage you to explore them. Until next time, happy coding!