среда, августа 20, 2025

Roll Your Own Geocoder

One of my favorite recent side-projects has been Meet Cute, a playful web map that generates tiny “micro-romance” stories whenever you click on a location. The conceit is simple: click on a map, and out comes a love story set in the nearest town.

But behind that simple experience was a not-so-romantic technical problem: finding the name of the nearest town.

At first, I leaned on the Overpass API, which is built on top of OpenStreetMap. Every time a user clicked somewhere on the map, my code would:

  1. Send a query to Overpass.

  2. Ask it to return the nearest place=city|town|village node.

  3. Use the closest name to plug into my romance grammar.

This worked beautifully… sometimes.

Overpass is a shared resource, and as many OSM developers know, it can be slow when overloaded. On bad days, a single click could stall Meet Cute for several seconds. Worse, I didn’t want to keep hammering the Overpass servers every time someone got carried away clicking for story after story.


Building My Own Geocoder

So I decided to roll my own lightweight client-side geocoder.

I realised that instead of calling Overpass for every click, I could pre-build a list of towns and cities and serve it as a static file. In fact I knew that TripGeo has just such a list of over 11,000 towns and cities around the world, that it uses for its daily Scrambled Maps Challenge

Here’s what I did:

  • I grabbed the TripGeo dataset of world cities (latitude, longitude, name).

  • I saved it into a cities.json file and hosted it on GitHub Pages.

  • I wrote a little Geocoder class that loads this JSON into memory and, given a latitude/longitude (defined by a user map click), finds the closest matching city by brute force distance calculation.

const geocoder = new CityGeocoder('https://mapsmania.github.io/geocoder/cities.json'); const town = geocoder.reverse(lat, lon);

The entire geocoder lives in a single JavaScript class which is loaded into Meet Cute and then called when the map needs to find a new location.

Now, when the user clicks on the map, my code doesn’t need to make a network call at all. It just looks up the nearest city locally. The result is that Meet Cute is suddenly very snappy. Users no longer have to wait for Overpass to respond and every click instantly produces a new love story.


You Can Use the Geocoder Too

Because the code is hosted on GitHub you can also drop my lightweight geocoder straight into your own map projects. All you need to do is include the script and point it to the cities.json file hosted on GitHub Pages. For example:

<script src="https://mapsmania.github.io/geocoder/geocoder.js"></script>

<script>

  const geocoder = new CityGeocoder("https://mapsmania.github.io/geocoder/cities.json");

  const nearest = geocoder.reverse(52.517, 13.388);

  console.log(nearest.name); // "Berlin"

</script>

That’s it - no API keys, no server calls, and no waiting on external services. Just a single JavaScript class, a static JSON file of cities, and you have a ready-to-go reverse geocoder that works instantly inside any JavaScript mapping library.

⚠️ One thing to note: the dataset is limited to just over 11,000 cities and towns worldwide. That makes it lightweight and fast, but it also means it’s not as detailed as commercial geocoders. It works perfectly if you only need to identify the nearest major town or city, but it won’t return smaller villages, streets, or individual house or business addresses.

Комментариев нет: