Using the Census Geocoder API

While doing some preliminary work for a recent mapping project, I had the need for showing data on map. Since the data I had only contained street addresses, I needed access to a service which would give me back latitude and longitude coordinates for each address (this is called geocoding). I had the additional requirement to not be tied to any proprietary mapping library as a result of using that service. Unfortunately, though not suprisingly, that meant using the Google Maps Platform was off the table:

You can display Geocoding API results on a Google Map, or without a map. If you want to display Geocoding API results on a map, then these results must be displayed on a Google Map. It is prohibited to use Geocoding API data on a map that is not a Google map.

Google Geocoding API Policy

Long story short, I discovered that one of my favorite sources of interesting data, the good ol’ U.S. Census Bureau has a free geocoding API that met all my requirements. It doesn’t have the greatest documentation but it was plenty flexible for my needs and wasn’t painfully slow either which is more than I could ask for. Anyway, on with the API description.

Requesting Lat/Long For a Single Address

All you need to do is pop this in an address bar to get a geocoding response:

https://geocoding.geo.census.gov/geocoder/locations/onelineaddress?address=9 E Fort Ave, Baltimore, MD&benchmark=Public_AR_Current&format=json

Or, programmatically:

import requests

# Build request
base_url = 'https://geocoding.geo.census.gov/geocoder'
return_type = '/locations'
search_type = '/onelineaddress'
url = base_url + return_type + search_type
params = {
  'address': '9 E Fort Ave, Baltimore, MD',
  'benchmark': 'Public_AR_Current',
  'format': 'json'
}

# Get geocoding response
response = requests.get(url, params)

# There may be multiple matches but I only care about the first
matches = response.json()['result']['addressMatches']
if len(matches) != 0:
  lat = matches[0]['coordinates']['y']
  long = matches[0]['coordinates']['x']

Batch Geocoding

The batch part of the API feels a little archaic because it requires you send a CSV file of addresses. Each line needs to include a unique id of your choosing so you can resolve the response (also comma delimited). This part of the API also differs in how it tells you whether there were more than one possible match.

Luckily for you, I’ve already written the code:

import requests
# Test addresses
addresses = [
  {
    'street': '9 E Fort Ave',
    'city': 'Baltimore',
    'state': 'MD'
  },
  {
    'street': '413 N Patterson Park Ave',
    'city': 'Baltimore',
    'state': 'MD'
  }
]

# Build the request
url = 'https://geocoding.geo.census.gov/geocoder/locations/addressbatch'
address_lines = []
for index, address in enumerate(addresses):
  address_lines.append(
    '%s,%s,%s,%s,' % (
      index,
      address['street'],
      address['city'],
      address['state']
    )
  )

# Building the CSV file to POST (what is this, the 90's?)
file_contents = '\n'.join(address_lines)
data = {
    'benchmark': 'Public_AR_Current'
}
files = {
    'addressFile': ('addresses.csv', file_contents)
}

The CSV we built above:

"0","9 E Fort Ave, Baltimore, MD, ","Match","Exact","9 E FORT AVE,BALTIMORE, MD, 21230","-76.61341,39.272636","206421214","R"
"1","413 N Patterson Park Ave, Baltimore, MD, ","Match","Exact","413 N PATTERSON PARK AVE, BALTIMORE, MD, 21231","-76.58495,39.29573","206423994","R"

Getting and parsing the response:

# Get the response
response = requests.post(url, data = data, files = files)

# Parse the response (again, CSV)
for line in response.text.splitlines():
    parts = line.strip('"').split('","')
    address_id = int(parts[0])
    match_indicator = parts[2]
    if match_indicator == 'Match':
        lat_long = parts[5].split(',')
        addresses[address_id]['lat'] = lat_long[1]
        addresses[address_id]['long'] = lat_long[0]
    elif match_indicator == 'Tie':
        addresses[address_id]['lat'] = 'Tie'

The resulting gecoded array:

[
  {
    'street':'9 E Fort Ave',
    'city':'Baltimore',
    'state':'MD',
    'lat':'39.272636',    
    'long':'-76.61341'
  },
  {
    'street':'413 N PattersonPark Ave',
    'city':'Baltimore',
    'state':'MD',
    'lat':'39.29573',
    'long':'-76.58495'
  }
]   

There you have it, the U.S. Census Bureau’s Gecoder API. There’s more you can do with it and if you’re so inclined, check out the documentation. If you’d like to see what cool things you can do with geocoded addresses, check out my property sales heat map showing property values in Baltimore City over the last couple years. I’ll be creating more maps along this line in the future so be sure to check back.