GeoNames API in Python#
by Michael T. Moen
Please see the following resources for more information on API usage:
Documentation
Terms of Use
Data Reuse
The GeoNames API Data is licensed under the Creative Commons’ CC 4.0 license, allowing users to share and adapt the API’s data for any purpose, as long as appropriate attribution is given.
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