GeoNames API in Python#

by Michael T. Moen

GeoNames API documentation: https://www.geonames.org/export/web-services.html

GeoNames API license: https://www.geonames.org/export/

The GeoNames API 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 4, 2024.

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#

User Registration#

Users must register with GeoNames before accessing the GeoNames API. Sign up can be found here: https://www.geonames.org/login

Add your username below:

username = ''

Alternatively, you can save the above data in a separate python file and import it:

from username import username

Import Libraries#

This tutorial uses the following libraries:

import requests                     # Manages API requests
from time import sleep              # Allows staggering of API requests to conform to rate limits

1. Searching with a ZIP code#

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

endpoint = 'postalCodeSearchJSON'
parameters = '&'.join([
    'postalcode=35401',     # Postal code to search
    'countryBias=US',       # Moves US results to the top of the results list
    f'username={username}'  # Must include GeoNames username in all API calls
])

url = f'https://secure.geonames.org/{endpoint}?{parameters}'
response = requests.get(url)

# 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'
parameters = '&'.join([
    'q=Tuscaloosa',         # Search query
    'countryBias=US',       # Moves US results to the top of the results list
    'maxRows=10',           # Limit results to top 10
    f'username={username}'  # Must include GeoNames username in all API calls
])

url = f'https://secure.geonames.org/{endpoint}?{parameters}'
response = requests.get(url)

# 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 buidling#

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

endpoint = 'searchJSON'
parameters = '&'.join([
    '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
    f'username={username}'      # Must include GeoNames username in all API calls
])

url = f'https://secure.geonames.org/{endpoint}?{parameters}'
response = requests.get(url)

# 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'
parameters = '&'.join([
    "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
    f'username={username}'      # Must include GeoNames username in all API calls
])

url = f'https://secure.geonames.org/{endpoint}?{parameters}'
response = requests.get(url)

# 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 State Forest          forest(s)
Martha's Vineyard Agricultural Society  vineyard

3. Reverse Geocoding#

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

endpoint = 'findNearbyPostalCodesJSON'
parameters = '&'.join([
    'lat=38.625189',            # Search latitude
    'lng=-90.187330',           # Search longitude
    'maxRows=10',               # Limit results to top 10
    f'username={username}'      # Must include GeoNames username in all API calls
])

url = f'https://secure.geonames.org/{endpoint}?{parameters}'
response = requests.get(url)

# 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
63180 | 0.94603
63188 | 0.94603
63197 | 0.94603
63169 | 0.94603
63155 | 0.94603
63150 | 0.94603
63182 | 0.94603
63101 | 1.1038
62202 | 2.64737