Getting OpenStreetMap geodata with Nominatim API

Living in Berlin, I see a lot of organic shops around - probably more than in any other city I’ve visited so far. In an attempt to visualise just how many of them there are here, I decided to make a map showing all organic shops in Berlin from the biggest organic supermarket chains.

After going through the supermarket brand websites and extracting initial location data, I was still missing certain data I needed. For instance, in some cases I had the latitude and longitude coordinates, but not the actual address of a location. Or I had the address, but not the district in which a shop was located.

Nominatim API

So I started looking for a way to obtain the missing data. It turns out that it’s possible to query geodata from OpenStreetMap by using the Nominatim API. Using Nominatim allows searching for geographic objects by submitting a name or an address. It’s also possible to perform reverse geocoding, whereby instead an address is found by submitting geo coordinates. This is exactly what I needed, since I already had the coordinates and wanted additional data about locations found at these coordinates.

I ended up writing a Node.js script which accepts a list of geo coordinates (latitude and longitude), sends an API request to Nominatim for each one and extracts the required data. Using this script, I got the name of the city district for each location.

Here is the complete version of the script, but I’ll also cover the noteworthy parts here:

Constructing URL for the API request

After reading in a file with a list of pairs of geo coordinates, I want to construct a URL to be sent as a request to the Nominatim API. For reverse geocoding, Nominatim provides the reverse API. The base URL is https://nominatim.openstreetmap.org/reverse and since I want to have the response in JSON format, I include the parameter format=jsonv2 as well as the lat and lon coordinates:

https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=${item[0]}&lon=${item[1]}

When working with an API, it’s a good idea to first try to send a request before including it in a script, to ensure that you get your desired response. For example, if you open https://nominatim.openstreetmap.org/reverse?format=jsonv2&lat=52.520856&lon=13.413779 in your browser, you’ll see that the information is returned in JSON format and that district information can be found in address.city_district.

Adhering to Nominatim Usage Policy

After constructing URLs for each location, I move on to sending the requests by calling the sendRequest function and passing in the array of all URLs. I wrap each API call in a setTimeout function with a timeout of 1500 milliseconds, which ensures that there is a delay of 1.5 seconds between each API call. The reason for this is because Nominatim Usage Policy states that it allows a maximum of 1 request per second. Nominatim API can be accessed freely by anyone and does not require an access token or registration. So if you use it, please make sure you don’t abuse it and respect the usage policy!

To send the requests I use the request package. Nominatim Usage Policy requires providing a valid non-stock User-Agent with each request, which I define here:

const customHeaderRequest = request.defaults({
headers: { 'User-Agent': 'Reverse geocode search' }
});

Getting the district name

If I get a successful response (response code 200), I parse the response and extract the district name for this location by looking it up in either address.city_district or address.town (for some reason, locations in certain districts had the district name saved in the latter key).

Using a recursive function to send all requests

Once the district name is saved, I remove the URL that we just processed from the front of the URLs array (urls.shift()) and then call the same function (sendRequest) again. A function which calls itself is a recursive function - using these can be useful when working with lists of items where you need to perform the same action for each item in the list.

The important thing is to only continue calling this function as long as there are still URLs to be processed. Otherwise in theory this loop will continue being executed forever, as the function would keep calling itself (in practice the script would at some point run out of memory).

This is why on every iteration I check if there are still URLs left in the array, and if not I exit the loop to write output to file: if (urls.length === 0) return writeOutput().

Now I have a full list of districts that I can use!


It is possible to adapt this script in case you want to obtain a different kind of geo data, or even if you want to search by providing an address instead of geo coordinates. Here is a list of available Nominatim API endpoints.

And here is the map, made with Datawrapper, which makes use of the data that I acquired using this script:

I also created a bar chart (also with Datawrapper) using this data, which shows the distribution of organic supermarkets per district:

Let me know on Twitter if you found this useful or if you have used the Nominatim API in some other way!