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 Platform
  • proxy: 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 pair
  • end: 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"&nbsp;", "", 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:


pinaddress



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!