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
$ 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
$ 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}]}
$ 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
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_typeorosm_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.