When working with location-based data in Python, you often need to convert city names into geographic coordinates - latitude and longitude.

The Real Use case

This article explores several Python solutions for geocoding city names, inspired by the need to convert the City into coordinates.

I was testing the SerpAPI for extracting Google maps results. I had a list of cities but the API expected geo coordinates like latitude and longitude:

Example of data needed - "ll": "@51.5030319,-0.3008878,11.31z",:

from serpapi import GoogleSearch

params = {
  "engine": "google_maps",
  "q": "pizza"
  "ll": "@51.5030319,-0.3008878,11.31z",
  "type": "search",
  ...

1. Geopy

Installation

Install using pip with:

pip install geopy

Code

The most recommended solution is geopy, a Python client for several popular geocoding web services:

from geopy.geocoders import Nominatim

geolocator = Nominatim(user_agent="linux")
location = geolocator.geocode("Chicago, IL")

print((location.latitude, location.longitude))

Output: (41.8781136, -87.6297982)

Errors

There are rate limit errors like:

  • ConfigurationError: Using Nominatim with default or sample user_agent "geopy/2.4.1" is strongly discouraged, as it violates Nominatim's ToS
  • GeocoderInsufficientPrivileges: Non-successful status code 403

I found two solution for this error:

  • use user agent like - Nominatim(user_agent="linux")
  • use different service - Photon
from geopy.geocoders import Photon

geolocator = Photon(user_agent="geoapiExercises")
location = geolocator.geocode("Chicago, IL")

print((location.latitude, location.longitude))

Output: (41.8781136, -87.6297982)

Notes

  • Pros:

    • Supports multiple geocoding services (Nominatim, Photon, Google Maps, Bing, etc.)
    • Simple, clean API
    • Handles ambiguous queries well
  • Cons:

    • May have usage limits

2. US Census Geocoder

For US-specific geocoding, the US Census Bureau provides a free API:

BASE_URL = 'https://geocoding.geo.census.gov/geocoder/'
return_type = 'locations'
search_type = 'address'

params = {
    'street': '425 Stadium Dr',
    'city': 'Tuscaloosa',
    'state': 'AL',
    'zip': 35401,
    'benchmark': 'Public_AR_Current',
    'format': 'json'
}

response = requests.get(f'{BASE_URL}{return_type}/{search_type}', params=params)

response.status_code

Output:

{'x': -87.549700416257, 'y': 33.21105403378}

Errors:

{"errors":["Street address cannot be empty and cannot exceed 100 characters","Specify House number and Street name along with City and State and/or ZIP Code"],"status":"400"}

if you face error like this it means that your request is incomplete. You can check the resources at the end of this post.

Notes

  • Pros:

    • Free to use
    • No API key required
    • Official US government data
  • Cons:

    • US only
    • Less precise than commercial services
    • Require additional input

Choosing the Right Approach

  1. For most projects: Use geopy with Nominatim (free) or Google Maps (more precise but may require API key)
  2. For US-only projects: Consider the Census geocoder for simple needs

Complete Example with Error Handling

from geopy.geocoders import Nominatim
from geopy.exc import GeocoderTimedOut, GeocoderServiceError

def get_coordinates(city, state, country="US"):
    geolocator = Nominatim(user_agent="city_locator")
    try:
        location = geolocator.geocode(f"{city}, {state}, {country}")
        if location:
            return (location.latitude, location.longitude)
        return (None, None)
    except (GeocoderTimedOut, GeocoderServiceError) as e:
        print(f"Geocoding error: {e}")
        return (None, None)

# Usage
lat, lon = get_coordinates("Chicago", "IL")
print(f"Latitude: {lat}, Longitude: {lon}")

Notes

Remember that free geocoding services often have rate limits, so for production applications consider:

  • Implementing caching
  • Using a paid service if you need high volume
  • Respecting the terms of service for any API you use

Resources