
# Python Geocoding Tutorial

## OpenCage Geocoding API Python Tutorial

Tutorial for using the OpenCage Geocoding API in Python - An API for reverse and forward geocoding using open geo data

This is a tutorial for using the [OpenCage geocoding API](https://opencagedata.com/api) in Python.

### Topics covered in this tutorial

- General Background
- Agent Skill
- Video tutorials
- installing the OpenCage Python module
- Reverse geocoding
- Forward geocoding
- Geocoding a list of places
- Making requests in parallel
- SSL issues
- Alternatives
- Further reading

### Background

The code examples below will use your geocoding API key once you [log in](https://opencagedata.com/users/sign_in).

#### Before we dive in to the tutorial

1. [Sign up](https://opencagedata.com/users/sign_up) for an OpenCage geocoding API key.
2. Play with the [demo page](https://opencagedata.com/demo), so that you see the actual response the API returns.
3. Browse the [API reference](https://opencagedata.com/api), so you understand the [optional parameters](https://opencagedata.com/api#optional-params), [best practices](https://opencagedata.com/api#bestpractices), [possible response codes](https://opencagedata.com/api#codes), and the [rate limiting](https://opencagedata.com/api#rate-limiting) on free trial accounts.

Working with AI?

We offer an [Agent Skill](https://github.com/OpenCageData/opencage-skills) to make it easy for AI to quickly learn about our geocoding API.

### Agent Skill / Working with AI

Are you developing with AI?

We offer [an Agent Skill](https://github.com/OpenCageData/opencage-skills/) that includes [a reference file specifically about developing code to access our API via Python](https://github.com/OpenCageData/opencage-skills/blob/master/skills/opencage-geocoding-api/references/python.md).

### Video Tutorials

If you prefer video, over on YouTube we have two 10 minute tutorial videos showing how to geocode using Python, one [tutorial video focused on reverse geocoding](https://www.youtube.com/watch?v=u-kkE4yA-z0), the other [explaining forward geocoding](https://www.youtube.com/watch?v=9bXu8-LPr5c).

### Install the OpenCage Python module

Compatible with Python version 3.9 and newer.

#### Install using pip

    pip3 install opencage

Conda users: use pip inside your conda environment.

#### Install using uv

    uv pip install opencage

Add the module to your pyproject.toml:

    uv add opencage

[on GitHub](https://github.com/opencagedata/python-opencage-geocoder) [on PyPi](https://pypi.python.org/pypi/opencage/)

### Convert coordinates to location (reverse geocoding)

    from opencage.geocoder import OpenCageGeocode
    from pprint import pprint
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey)
    
    results = geocoder.reverse_geocode(44.8303087, -0.5761911)
    pprint(results)
    # [{'components': {'ISO_3166-1_alpha-2': 'FR',
    # 'ISO_3166-1_alpha-3': 'FRA',
    # 'ISO_3166-2': ['FR-NAQ', 'FR-33'],
    # '_category': 'building',
    # '_normalized_city' : 'Bordeaux',
    # '_type': 'building',
    # 'city': 'Bordeaux',
    # 'continent': 'Europe',
    # 'country': 'France',
    # 'country_code': 'fr',
    # 'county': 'Gironde',
    # 'house_number': '11',
    # 'local_authority': 'Bordeaux Métropole',
    # 'municipality': 'Bordeaux',
    # 'political_union': 'European Union',
    # 'postcode': '33000',
    # 'region': 'Metropolitan France',
    # 'road': 'Rue Sauteyron',
    # 'state': 'New Aquitaine',
    # 'state_code': 'NAQ',
    # 'suburb': 'Victoire'},
    # 'confidence': 10,
    # 'formatted': '11 Rue Sauteyron, 33800 Bordeaux, France',
    # 'geometry': {'lat': 44.8303087, 'lng': -0.5761911}}]
    
    # print just the suburb
    print(results[0]['components']['suburb'])
    # safer with .get() in case the field is missing:
    print(results[0]['components'].get('suburb'))
    # Victoire

### Set output language, error handling

    from opencage.geocoder import OpenCageGeocode
    from opencage.geocoder import InvalidInputError, RateLimitExceededError, UnknownError
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey)
    
    try:
      results = geocoder.reverse_geocode(44.8303087, -0.5761911, language='de', no_annotations='1')
      if results and len(results):
        print(results[0].get('formatted'))
        # 11 Rue Sauteyron, 33800 Bordeaux, Frankreich
      else
        print('No result found.')
    except RateLimitExceededError as ex:
      print(ex)
      # You have used the requests available on your plan.
    except InvalidInputError as ex:
      # this happens for example with invalid unicode in the input data

### Lookup coordinates from address (forward geocoding)

    from opencage.geocoder import OpenCageGeocode
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey)
    
    query = u'Bosutska ulica 10, Trnje, Zagreb, Croatia'
    
    # no need to URI encode query, module does that for you
    results = geocoder.geocode(query)
    
    print(u'%f;%f;%s' % (results[0]['geometry']['lat'],
                        results[0]['geometry']['lng'],
                        results[0]['components']['country_code']))
    # 45.797095;15.982453;hr
    
    # annotations might not present (for example when no_annotations='1')
    timezone = results[0].get('annotations', {}).get('timezone', {}).get('name')
    if timezone:
      print(timezone)
      # Europe/Belgrade

### Improving accuracy with countrycode and bounds

For ambiguous queries, use `countrycode ` to restrict results to a specific country, or ` bounds` to limit results to a bounding box (min longitude, min latitude, max longitude, max latitude). Both can be combined.

    from opencage.geocoder import OpenCageGeocode
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey)
    
    # restrict to United States by country code
    results = geocoder.geocode('Berlin')
    print(results[0]['formatted'])
    # Berlin, Germany
    results = geocoder.geocode('Berlin', countrycode='us')
    # Berlin, New Hampshire, United States of America
    
    # restrict to a bounding box covering New Hampshire
    results = geocoder.geocode('Berlin', bounds='-73.63,42.52,-69.52,45.60')

### Batch geocode a file of addresses

Create a file containing addresses, or use one of our [example address lists](https://opencagedata.com/tools/address-lists).

    Madrid, Spain
    Milan, Italy
    Berlin, Germany
    München, Deutschland
    Pappelallee 78/79, 10437 Berlin, Germany

    import sys
    from opencage.geocoder import OpenCageGeocode, RateLimitExceededError
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey)
    addressfile = 'addresses.txt'
    
    try:
      with open(addressfile,'r') as f:
        for line in f:
          address = line.strip()
    
          results = geocoder.geocode(address, no_annotations='1')
    
          if results and len(results):
            longitude = results[0]['geometry']['lng']
            latitude = results[0]['geometry']['lat']
            print(u'%f;%f;%s' % (latitude, longitude, address))
            # 40.416705;-3.703582;Madrid, Spain
            # 45.466797;9.190498;Milan, Italy
            # 52.517037;13.388860;Berlin, Germany
            # 48.1371079;11.5753822;München, Deutschland
            # 52.5432379;13.4142133;Pappelallee 78/79, 10437 Berlin, Germany
          else:
            sys.stderr.write("not found: %s\n" % address)
    except IOError:
      print('Error: File %s does not appear to exist.' % addressfile)
    except RateLimitExceededError as ex:
      print(ex)
      # You have used the requests available on your plan.

### Running many parallel queries

By default the Python [Request HTTP library](https://docs.python-requests.org/en/latest/index.html) will only run one HTTP request, even if you wrap the logic inside multipthreading or asynchronous calls. At low level it will block other HTTP requests.

Instead you should use the [asyncio](https://docs.python.org/3/library/asyncio.html) library for HTTP requests. We prepared a more complex code example of [python parallel batch requests](https://github.com/OpenCageData/python-opencage-geocoder/blob/master/examples/batch.py) using a queue and task workers. We've used variations of the script for lists of 10s of millions of addresses.

We also offer a separate command line interface (CLI) tool for geocoding files, see our full [geocoding CLI tutorial](https://opencagedata.com/tutorials/geocode-commandline).

 ![Commandline example of using batch.py](https://assets.opencagedata.com/assets/tutorials/cli-b65521f61dbc1549db9a4421d546021a6d11c8cadd6aff8d2e0f8c5b29806dd2.gif)

Before you start geocoding at high volume, please read our [guide to geocoding large datasets](https://opencagedata.com/guides/how-to-geocode-large-datasets) where we explain various strategies and points to consider.

### Storing your API key securely

It's best practice to avoid putting your API key directly in source code. Read it from an environment variable instead:

    import os
    from opencage.geocoder import OpenCageGeocode
    
    apikey = os.environ.get('OPENCAGE_API_KEY')
    geocoder = OpenCageGeocode(apikey)

### SSL work-around

If you have trouble accessing the OpenCage API with https, e.g. issues with OpenSSL libraries in your environment, then you can set the 'http' protocol instead. Please understand that the connection to the OpenCage API will no longer be encrypted.

    from opencage.geocoder import OpenCageGeocode
    
    apikey = 'YOUR-API-KEY'
    geocoder = OpenCageGeocode(apikey, 'http')

### Alternative Python modules

- [Jupyter Notebooks](https://jupyter.org/) can execute Python. Our [Jupyter Notebook geocoding tutorial](https://nbviewer.jupyter.org/gist/sbscully/c024c31c49ecfea8579f20bf025b50db/opencage-jupyter-notebook.ipynb) explains how to use the OpenCage geocoding API in Jupyter.
- Denis Carriere's [Python Geocoder library](https://github.com/DenisCarriere/geocoder) has a sub-library and command line interface for [OpenCage API.](https://geocoder.readthedocs.io/providers/OpenCage.html) Here is a further [code sample](https://github.com/OpenCageData/opencagedata-misc-docs/blob/master/code-samples/python-geocoder.md).
- [geopy](https://pypi.org/project/geopy/) has an [OpenCage method](https://geopy.readthedocs.io/en/stable/#opencage) and supports advanced proxy configuration, logging, asynchronous (parallel) querying and distance calculations.

### Further Reading

- [OpenCage geocoding API Reference](https://opencagedata.com/api)
- [Comparing geocoding services](https://opencagedata.com/guides/how-to-compare-and-test-geocoding-services)
- [Cleaning / formatting your forward geocoding query](https://opencagedata.com/guides/how-to-format-your-geocoding-query)
- [Geocoding more quickly](https://opencagedata.com/guides/how-to-geocode-more-quickly)
- [Geocoding large datasets](https://opencagedata.com/guides/how-to-geocode-large-datasets)
- [Geocoding and preserving privacy](https://opencagedata.com/guides/how-to-preserve-privacy-by-showing-only-an-imprecise-location)
- [Sample address and coordinate lists for testing](https://opencagedata.com/tools/address-lists)

