1

I have this DataFrame:

      Lat       Lon
 29.39291 -98.50925
 29.39923 -98.51256
 29.40147 -98.51123
 29.38752 -98.52372
 29.39291 -98.50925
 29.39537 -98.50402
 29.39343 -98.49707
 29.39291 -98.50925
 29.39556 -98.53148

I want to convert these lat/lon rows to ZIP/Postal codes (for each row), using Python.

2
  • Not sure what your exact error/problem is, but pygeocoder hasn't seen an update in a while, maybe checkout geocoder. Note, that you need to set env variables with an API key you've created from a Google Account. I haven't tried the Bing provider, but that is also an option with geocoder.
    – Ryan
    Mar 4, 2020 at 19:35
  • I am not a dev, but am wondering if anyone has tried to add the Plus-Codes to this calculation? Plus Codes/P Codes are used by starlink for Lat Long Coordinates of a subscriber terminal. Google earth will provide P-Codes when you search a Lat/Long but looking to do this in python. Some community reported benefits to P-Codes: Every location on earth can have a mailing address The encoding of 10 characters plus “+” character is much more efficient in terms of number of bytes than latitude and longitude, which to represent the same level of specificity would need to consist of two floating point
    – Mudpump
    Dec 2, 2022 at 15:00

4 Answers 4

11

You can use geopy and its Nominatim geocoder.

Here is an example using the DataFrame you provided:

import geopy
import pandas as pd


def get_zipcode(df, geolocator, lat_field, lon_field):
    location = geolocator.reverse((df[lat_field], df[lon_field]))
    return location.raw['address']['postcode']


geolocator = geopy.Nominatim(user_agent='my-application')

df = pd.DataFrame({
    'Lat': [29.39291, 29.39923, 29.40147, 29.38752, 29.39291, 29.39537, 29.39343, 29.39291, 29.39556],
    'Lon': [-98.50925, -98.51256, -98.51123, -98.52372, -98.50925, -98.50402, -98.49707, -98.50925, -98.53148]
})
zipcodes = df.apply(get_zipcode, axis=1, geolocator=geolocator, lat_field='Lat', lon_field='Lon')
>>> zipcodes
0    78204
1    78204
2    78204
3    78225
4    78204
5    78204
6    78204
7    78204
8    78225
dtype: object
1
  • Thank you for the answer. I got an error of ConfigurationError: Using Nominatim with default or sample `user_agent` "my-application" is strongly discouraged, as it violates Nominatim's ToS https://operations.osmfoundation.org/policies/nominatim/ and may possibly cause 403 and 429 HTTP errors. Please specify a custom `user_agent` with `Nominatim(user_agent="my-application")` or by overriding the default `user_agent`: `geopy.geocoders.options.default_user_agent = "my-application"`.. Is there any update you could add here?
    – ay__ya
    Jul 15, 2022 at 19:46
3

For geocoding with ArcGIS, credentials have to be provided gis = GIS("http://www.arcgis.com", "username", "password").


In terms of pandas, this code should do the work (adapted from @Marcelo Villa's answer)

from arcgis.geocoding import reverse_geocode
from arcgis.geometry import Geometry
from arcgis.gis import GIS
import pandas as pd

gis = GIS("http://www.arcgis.com", "***", "***")

def get_zip(df, lon_field, lat_field):
    location = reverse_geocode((Geometry({"x":float(df[lon_field]), "y":float(df[lat_field]), "spatialReference":{"wkid": 4326}})))
    return location['address']['Postal']

df = pd.DataFrame({
    'Lat': [29.39291, 29.39923, 29.40147, 29.38752, 29.39291, 29.39537, 29.39343, 29.39291, 29.39556],
    'Lon': [-98.50925, -98.51256, -98.51123, -98.52372, -98.50925, -98.50402, -98.49707, -98.50925, -98.53148]
})

zipcodes = df.apply(get_zip, axis=1, lat_field='Lat', lon_field='Lon')

result


Probably not as smart as pandas but using Python's internal module csv and ArcGIS's API for Python Reverse Geocoding.

from arcgis.geocoding import reverse_geocode
from arcgis.geometry import Geometry
from arcgis.gis import GIS
import csv

gis = GIS("http://www.arcgis.com", "***", "***")

coords = [
(29.39291, -98.50925),
(29.39923, -98.51256),
(29.40147, -98.51123),
(29.38752, -98.52372),
(29.39291, -98.50925),
(29.39537, -98.50402),
(29.39343, -98.49707),
(29.39291, -98.50925),
(29.39556, -98.53148)
]

result = []

for lat, lon in coords:
    pt = Geometry({
        "x": float(lon),
        "y": float(lat),
        "spatialReference": {
            "wkid": 4326
        }
    })
    try:
        result.append({'lat': lat, 'lon': lon, 'geocoded': reverse_geocode(pt)})
    except:
        pass

result_zip = []

for item in result:
    result_item = {
        'lat': item['lat'],
        'lon': item['lon'],
        'zip': item['geocoded']['address']['Postal']
    }
    result_zip.append(result_item)

keys = result_zip[0].keys()

with open('output.csv', 'w', encoding='utf8', newline='') as output_file:
    dict_writer = csv.DictWriter(output_file, keys, delimiter=';', quoting=csv.QUOTE_NONE, lineterminator='\r')
    dict_writer.writeheader()
    dict_writer.writerows(result_zip)

Output csv-file looks as following

output

1

Simplifying Marcelo's answer, using only python and geopy (No Pandas):

import geopy
geo_locator = geopy.Nominatim(user_agent='1234')
                        # Latitude, Longitude
r = geo_locator.reverse((43.509004, -79.628853))
print(r.raw['address']['postcode'])
# L5J 2Y4
print(r.raw)
# {'place_id': 110711915, 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. https://osm.org/copyright', 'osm_type': 'way', 'osm_id': 33302111, 'lat': '43.50878025', 'lon': '-79.62891074069033', 'display_name': 'Canadian Tire, 900, Southdown Road, Clarkson, Mississauga, Peel Region, Golden Horseshoe, Ontario, L5J 2Y4, Canada', 'address': {'shop': 'Canadian Tire', 'house_number': '900', 'road': 'Southdown Road', 'suburb': 'Clarkson', 'city': 'Mississauga', 'county': 'Peel Region', 'state_district': 'Golden Horseshoe', 'state': 'Ontario', 'postcode': 'L5J 2Y4', 'country': 'Canada', 'country_code': 'ca'}, 'boundingbox': ['43.5083103', '43.5093261', '-79.6295956', '-79.6282259']}
# 
1

The error you're getting is due to the default user agent. Change it to something other than 'my-application', such as:

geolocator = geopy.Nominatim(user_agent='1234')

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.