GeoNames API in Python#

by Michael T. Moen

Please see the following resources for more information on API usage:

These recipe examples were tested on March 7, 2025.

NOTE: The GeoNames API limits users to a maximum of 10000 credits per day and 1000 credits per hour. See here for a list of how many credits a request to each endpoint uses.

Setup#

Import Libraries#

The following external libraries need to be installed into your enviornment to run the code examples in this tutorial:

import requests
import os
from dotenv import load_dotenv

Import Username#

Users must register with GeoNames before accessing the GeoNames API. Sign up can be found here.

We keep our username in a .env file and use the dotenv library to access it. If you would like to use this method, create a file named .env in the same directory as this notebook and add the following line to it:

GEONAMES_API_USERNAME=PUT_YOUR_USERNAME_HERE
load_dotenv()
try:
    USERNAME = os.environ["GEONAMES_API_USERNAME"]
except KeyError:
    print("API key not found. Please set 'GEONAMES_API_USERNAME' in your .env file.")

1. Searching with a ZIP Code#

This example uses the postalCodeSearchJSON endpoint to find the coordinates of the the ZIP code 35401.

BASE_URL = f'https://secure.geonames.org/'

endpoint = 'postalCodeSearchJSON'
params = {
    'postalcode': 35401,    # Postal code to search
    'countryBias': 'US',    # Moves US results to the top of the results list
    'username': USERNAME    # Must include GeoNames username in all API calls
}

response = requests.get(f"{BASE_URL}{endpoint}", params=params)

# Status code 200 indicates success
response.status_code
200
top_result = response.json()['postalCodes'][0]
top_result
{'adminCode2': '125',
 'adminCode1': 'AL',
 'adminName2': 'Tuscaloosa',
 'lng': -87.562666,
 'countryCode': 'US',
 'postalCode': '35401',
 'adminName1': 'Alabama',
 'ISO3166-2': 'AL',
 'placeName': 'Tuscaloosa',
 'lat': 33.196891}
latitude = top_result['lat']
longitude = top_result['lng']
latitude, longitude
(33.196891, -87.562666)

2. Searching with Queries#

Queries allow users to search for location at several different levels.

Searching for a City#

In this example, we search for a location using the query “Tuscaloosa.”

endpoint = 'searchJSON'
params = {
    'q': 'Tuscaloosa',      # Search query
    'countryBias': 'US',    # Moves US results to the top of the results list
    'maxRows': 10,          # Limit results to top 10
    'username': USERNAME    # Must include GeoNames username in all API calls
}

response = requests.get(f"{BASE_URL}{endpoint}", params=params)

# Status code 200 indicates success
response.status_code
200
# Display top result
response.json()['geonames'][0]
{'adminCode1': 'AL',
 'lng': '-87.56917',
 'geonameId': 4094455,
 'toponymName': 'Tuscaloosa',
 'countryId': '6252001',
 'fcl': 'P',
 'population': 98332,
 'countryCode': 'US',
 'name': 'Tuscaloosa',
 'fclName': 'city, village,...',
 'adminCodes1': {'ISO3166_2': 'AL'},
 'countryName': 'United States',
 'fcodeName': 'seat of a second-order administrative division',
 'adminName1': 'Alabama',
 'lat': '33.20984',
 'fcode': 'PPLA2'}

Seaching for a Building#

In this example, we search for a location using the query “Bruno Business Library.”

endpoint = 'searchJSON'
params = {
    'q': 'Bruno Business Library',  # Search query
    'countryBias': 'US',            # Moves US results to the top of the results list
    'maxRows': 10,                  # Limit results to top 10
    'username': USERNAME            # Must include GeoNames username in all API calls
}

response = requests.get(f'{BASE_URL}{endpoint}', params=params)

# Status code 200 indicates success
response.status_code
200
# Display top result
response.json()['geonames'][0]
{'adminCode1': 'AL',
 'lng': '-87.54925',
 'geonameId': 11524498,
 'toponymName': 'Angelo Bruno Business Library',
 'countryId': '6252001',
 'fcl': 'S',
 'population': 0,
 'countryCode': 'US',
 'name': 'Angelo Bruno Business Library',
 'fclName': 'spot, building, farm',
 'adminCodes1': {'ISO3166_2': 'AL'},
 'countryName': 'United States',
 'fcodeName': 'library',
 'adminName1': 'Alabama',
 'lat': '33.2111',
 'fcode': 'LIBR'}

Searching for an Island#

In this example, we use the query “Martha’s Vineyard.”

endpoint = 'searchJSON'
params = {
    'q': "Martha's Vineyard",   # Search query
    'countryBias': 'US',        # Moves US results to the top of the results list
    'maxRows': 10,              # Limit results to top 10
    'username': USERNAME        # Must include GeoNames username in all API calls
}

response = requests.get(f'{BASE_URL}{endpoint}', params=params)

# Status code 200 indicates success
response.status_code
200
# Display top result
response.json()['geonames'][0]
{'adminCode1': 'MA',
 'lng': '-70.61265',
 'geonameId': 4943237,
 'toponymName': "Martha's Vineyard Airport",
 'countryId': '6252001',
 'fcl': 'S',
 'population': 0,
 'countryCode': 'US',
 'name': "Martha's Vineyard Airport",
 'fclName': 'spot, building, farm',
 'adminCodes1': {'ISO3166_2': 'MA'},
 'countryName': 'United States',
 'fcodeName': 'airport',
 'adminName1': 'Massachusetts',
 'lat': '41.39016',
 'fcode': 'AIRP'}

Note that the result above is the data for Matha’s Vineyard Airport. If we wish to find the data associated with the island, we can look at the fcodeName of the locations in the response:

for location in response.json()['geonames']:
    print(f'{location['toponymName']:<40}{location['fcodeName']}')
Martha's Vineyard Airport               airport
Martha's Vineyard Island                island
Vineyard Haven                          populated place
Martha's Vineyard Hospital              hospital
Martha's Vineyard Regional High School  school
Marthas Vineyard Campground             camp(s)
Martha's Vineyard Aero Light            
Martha's Vineyard State Forest          forest(s)
Martha's Vineyard Agricultural Society  vineyard
Martha's Vineyard State Forest          forest(s)

3. Reverse Geocoding#

The findNearbyPostalCodesJSON endpoint can be used to find the ZIP code of a pair of coordinates.

endpoint = 'findNearbyPostalCodesJSON'
params = {
    'lat': 38.625189,       # Search latitude
    'lng': -90.187330,      # Search longitude
    'maxRows': 10,          # Limit results to top 10
    'username': USERNAME    # Must include GeoNames username in all API calls
}

response = requests.get(f'{BASE_URL}{endpoint}', params=params)

# Status code 200 indicates success
response.status_code
200
# Print 10 nearest ZIP codes
print('ZIP   | Distance (km)')
for zip in response.json()['postalCodes']:
    print(f'{zip['postalCode']} | {zip['distance']}')
ZIP   | Distance (km)
63102 | 0
63188 | 0.94603
63197 | 0.94603
63180 | 0.94603
63155 | 0.94603
63169 | 0.94603
63182 | 0.94603
63150 | 0.94603
63101 | 1.1038
62202 | 2.64737