Guide

A local geocoding server

One command turns a country file into an HTTP geocoder on your machine: its own JSON API and the Nominatim paths that existing clients already call. No database, no import, and no address leaves the machine.

Start

terminalCLI
$ gridpin-fetch us                          # United States, 1.45 GB, checked; prints the path
$ gridpin serve "$(gridpin-fetch us)" --port 8080
listening on http://127.0.0.1:8080
ready: sheet read and fallback tables built in 3.6 s

gridpin is the command-line program from the release archive (0.7.0 or newer); gridpin-fetch comes with pip install gridpin-mcp and prints the path of the checked file. The server listens on 127.0.0.1 unless you pass --host; --port 0 picks a free port and prints it on the first line. GET /health names the sheet and says ok once the start-up tables are built: a few seconds when the file is in the disk cache, up to about 20 seconds for the United States file on a cold start.

Its own API

terminalCLI
$ curl "http://127.0.0.1:8080/v1/geocode?q=1600+Pennsylvania+Ave+NW,+Washington,+DC+20500"
{"results":[{"commune":"Washington","confidence":0.949999988079071,"flags":["street_exact","commune_exact","house_rep","pc_exact"],"housenumber":"1600","lat":38.8976751,"lon":-77.036547,"postcode":"20500","precision":"house","score":7.2687788009643555,"street":"PENNSYLVANIA Avenue Northwest","weak":false}]}
terminalCLI
$ curl -X POST http://127.0.0.1:8080/v1/geocode -H "Content-Type: application/json" \
    -d '{"queries": ["233 S Wacker Dr, Chicago, IL 60606", "Nowhere Street 99999, Atlantis"]}'
{"results":[[{"commune":"Chicago","confidence":0.949999988079071,"flags":["street_exact","commune_exact","house_rep","pc_exact"],"housenumber":"233","lat":41.878658,"lon":-87.6358684,"postcode":"60606","precision":"house","score":7.2687788009643555,"street":"South WACKER Drive","weak":false}],[]]}

GET /v1/geocode?q=… answers {"results": [...]}, the same JSON as one line of gridpin batch; k asks for more candidates. POST /v1/geocode takes up to 10,000 queries and answers one list per query, in order; an address the file cannot place gets an empty list, not a guess. GET /v1/reverse?lat=…&lon=… gives the nearest address with distance_m. Output from gridpin serve 0.7.0 on the United States file.

Clients written for Nominatim

geocode.pygeopy
from geopy.geocoders import Nominatim

g = Nominatim(user_agent="my-app", domain="127.0.0.1:8080", scheme="http")

loc = g.geocode("233 S Wacker Dr, Chicago, IL 60606")
print(loc.address, loc.latitude, loc.longitude)
# 233 South WACKER Drive, Chicago, 60606 41.878658 -87.6358684

print(g.reverse((41.8787, -87.6359)).address)
# 231 South WACKER Drive, Chicago, 60606

/search, /reverse and /status answer in the Nominatim formats json, jsonv2 and geojson, with limit and addressdetails=1, so a client written for Nominatim works once its base URL points at the server. Output from geopy 2.5.0.

What is not the same

  • No osm_type or osm_id: the addresses come from Overture Maps and national registries, not from OpenStreetMap.
  • No polygons and no XML output.
  • One country per process; run one server per country on its own port.
  • Addresses only: no search for shops or landmarks by name.

In a container: offline geocoding in Docker. All paths and fields: documentation.