How to Get Geolocation in Python

Learn how to use GeoPy library to geocode physical addresses into latitude and longitude and vice versa; getting latitude and longitude from addresses, towns, cities and more in Python.
  · 5 min read · Updated jan 2023 · General Python Tutorials

Step up your coding game with AI-powered Code Explainer. Get insights like never before!

Disclosure: This post may contain affiliate links, meaning when you click the links and make a purchase, we receive a commission.

Geocoding is the process of transforming a description of a location (such as a physical address or a name of a place) into a pair of latitude and longitude on the Earth's surface for that place.

It also refers to converting geographic coordinates to a description of a location (such as an address); this is often called reverse geocoding. In this tutorial, we will learn how to do both with the help of the GeoPy library in Python.

However, if you want to geolocate IP addresses, then this tutorial is for you.

GeoPy is a Python client that provides several popular geocoding web services; it makes it easy for Python developers to locate the coordinates of an address, a city, or a country, and vice-versa.

To get started, let's install it:

pip3 install geopy

GeoPy provides many geocoding service wrappers, such as OpenStreetMap Nominatim, Google Geocoding API V3, Bing Maps, and more. In this tutorial, we gonna stick with OpenStreetMap Nominatim.

Here is what we'll be covering:

Getting Latitude and Longitude from an Address (Geocoding)

In this section, we will be using OpenStreetMap Nominatim API to get latitude and longitude from a physical address, city, or any location name.

Let's import the library first:

from geopy.geocoders import Nominatim
import time
from pprint import pprint

Notice we chose Nominatim geocoder, now creating a new instance of it:

# instantiate a new Nominatim client
app = Nominatim(user_agent="tutorial")

Now let's try to get geographic data from an address:

# get location raw data
location = app.geocode("Nairobi, Kenya").raw
# print raw data
pprint(location)

Output:

{'boundingbox': ['-1.444471', '-1.163332', '36.6509378', '37.1038871'],
 'class': 'place',
 'display_name': 'Nairobi, Kenya',
 'icon': 'https://nominatim.openstreetmap.org/images/mapicons/poi_place_city.p.20.png',
 'importance': 0.845026759433763,
 'lat': '-1.2832533',
 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '
            'https://osm.org/copyright',
 'lon': '36.8172449',
 'osm_id': 9185096,
 'osm_type': 'relation',
 'place_id': 273942566,
 'type': 'city'}

Awesome, we have latitude at lat attribute (in which we can access by location['lat']) and longitude at lon attribute, we also have access to the bounding box of the address at the boundingbox attribute.

As you can see, the Nominatim API does not require a full address (that consists of street, house number, and city), you can also pass business addresses and points of your interests, it supports that!

However, if you call this function repeatedly (such as iterating over a list of addresses), you will encounter a timed-out error, and that's because if you read the Nominatim Usage Policy, it requires you to use a maximum of 1 request per second, and that's absolutely acceptable since it's a free service.

As a result, the below function respects that requirement and sleeps for one second before making a request:

def get_location_by_address(address):
    """This function returns a location as raw from an address
    will repeat until success"""
    time.sleep(1)
    try:
        return app.geocode(address).raw
    except:
        return get_location_by_address(address)

So whenever a timed-out error is raised, we catch that and call the function recursively, and this function will sleep for another second and hopefully, retrieves the result:

address = "Makai Road, Masaki, Dar es Salaam, Tanzania"
location = get_location_by_address(address)
latitude = location["lat"]
longitude = location["lon"]
print(f"{latitude}, {longitude}")
# print all returned data
pprint(location)

Output:

-6.7460493, 39.2750804
{'boundingbox': ['-6.7467061', '-6.7454602', '39.2741806', '39.2760514'],
 'class': 'highway',
 'display_name': 'Makai Road, Masaki, Msasani, Dar es-Salaam, Dar es Salaam, '
                 'Coastal Zone, 2585, Tanzania',
 'importance': 0.82,
 'lat': '-6.7460493',
 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '
            'https://osm.org/copyright',
 'lon': '39.2750804',
 'osm_id': 23347726,
 'osm_type': 'way',
 'place_id': 89652779,
 'type': 'residential'}

Getting Address from Latitude and Longitude (Reverse Geocoding)

Now to retrieve an address, city, country, and various other information, only from latitude and longitude, we simply use the reverse() method instead of geocode(), which accepts coordinates (latitude and longitude) as a string separated by a comma.

The following function reverses the coordinates along with respecting Nominatim usage policy:

def get_address_by_location(latitude, longitude, language="en"):
    """This function returns an address as raw from a location
    will repeat until success"""
    # build coordinates string to pass to reverse() function
    coordinates = f"{latitude}, {longitude}"
    # sleep for a second to respect Usage Policy
    time.sleep(1)
    try:
        return app.reverse(coordinates, language=language).raw
    except:
        return get_address_by_location(latitude, longitude)

So this function expects latitude and longitude as parameters and returns the raw geographic data, here is an example usage:

# define your coordinates
latitude = 36.723
longitude = 3.188
# get the address info
address = get_address_by_location(latitude, longitude)
# print all returned data
pprint(address)

Output:

{'address': {'country': 'Algeria',
             'country_code': 'dz',
             'county': 'Dar El Beida District',
             'postcode': '16110',
             'state': 'Algiers',
             'town': 'Bab Ezzouar'},
 'boundingbox': ['36.7231765', '36.7242661', '3.1866439', '3.1903998'],
 'display_name': 'Bab Ezzouar, Dar El Beida District, Algiers, 16110, Algeria',
 'lat': '36.72380363740118',
 'licence': 'Data © OpenStreetMap contributors, ODbL 1.0. '
            'https://osm.org/copyright',
 'lon': '3.188236679492425',
 'osm_id': 42812185,
 'osm_type': 'way',
 'place_id': 98075368}

So this will return all address data, including state, town, postcode, districts, and more. If you wish to return this information in a specific language, you can set language parameter to the language you desire, or you can set it to False for the default language for that specific location.

Conclusion

As always, we only saw simple examples of what GeoPy can do, I highly suggest you read the documentation if you're interested in more advanced utilities.

Finally, if you're a beginner and want to learn Python, I suggest you take the Python For Everybody Coursera course, in which you'll learn a lot about Python. You can also check our resources and courses page to see the Python resources I recommend on various topics!

Learn also: How to Get Domain Name Information in Python.

Happy Coding ♥

Want to code smarter? Our Python Code Assistant is waiting to help you. Try it now!

View Full Code Auto-Generate My Code
Sharing is caring!



Read Also



Comment panel

    Got a coding query or need some guidance before you comment? Check out this Python Code Assistant for expert advice and handy tips. It's like having a coding tutor right in your fingertips!