gazetteer #
A self-hosted, offline geocoder. Free-form address search and postal-code centroids for the United States and Canada, served from Postgres + PostGIS on hardware with plenty of disk and little RAM.
Data comes from GeoNames postal codes, OpenAddresses, and OpenStreetMap (Geofabrik extracts with replication-diff updates). The OSM import derives streets, places, address points, and landmarks: named shops, amenities, tourism, and leisure features. After import, no query ever leaves your machine.
See REQUIREMENTS.md for what it must do and DESIGN.md for how it
is shaped.
Quick start #
docker compose up -d # PostGIS for development
go build -o gazetteer .
./gazetteer migrate
./gazetteer import geonames-postal
./gazetteer import osm --region maine
./gazetteer import osm --region new-brunswick
./gazetteer serve
Downloads land in ./data (override with DATA_DIR). The database
URL comes from DATABASE_URL and defaults to the compose database.
OpenAddresses downloads need an API token from an account at
batch.openaddresses.io (create one on your profile page). Set
OPENADDRESSES_TOKEN and the importer resolves the latest statewide
output for the region and downloads it itself:
export OPENADDRESSES_TOKEN=oa....
./gazetteer import openaddresses --region maine
A file you downloaded yourself still works and skips the API:
./gazetteer import openaddresses --region maine --file data/me-statewide.geojson.gz
gazetteer update re-downloads the small sources and applies OSM
replication diffs from the stored sequence cursor. When
OPENADDRESSES_TOKEN is set it also refreshes OpenAddresses for every
region imported from it; without the token it prints a notice and
skips them. A region whose latest batch job still matches the stored
cursor is skipped without a download, and its imported_at does not
move, so that timestamp always means when the data last changed:
./gazetteer update # everything
./gazetteer update osm --region maine
Endpoints #
GET /search?q=210+state+st+augusta+meranked candidates as JSON, with coordinates, structured components, scores, and sources. Each result has atype:address,street,place,landmark, orpostal. Landmark results add acategory(the OSM tag key, likeshop) and akind(its value, likesupermarket), and answer only queries without a house number.GET /postal?country=US&code=04330one postal centroid, or 404. Canadian codes resolve at full precision where the data has the code, and fall back to the three-character FSA.GET /statusthe binary version, the schema version, row totals per source, and one entry per imported dataset with its region name, cursor, row count, and when its data last changed. The counts come from import bookkeeping, so the endpoint never counts the big tables.GET /healthzliveness.
Tests #
go test ./... # unit tests
go test -tags integration ./... # needs the compose database
Integration tests create their own gazetteer_test_* databases on the
compose server, so they never touch imported data.
License #
MIT for the code. The imported data keeps its own licenses: GeoNames postal codes are CC BY 4.0, OpenStreetMap data is ODbL, and each OpenAddresses source has its own terms, recorded at import.