Reverse geocode a coordinate
Outcome. You can turn a WGS-84 coordinate into the chain of places that contain it, and you know from the result whether that chain was confirmed against a real boundary or inferred from the closest centroid.
Reverse geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. here resolves to administrative places — neighbourhood, locality, county,
region, country, the placetypeplacetypeThe Who's On First hierarchical classification of places: planet → continent → country → region → county → locality → neighborhood. The resolver uses placetype to rank candidates — an exact locality match outranks a county-level match. names the transcripts below print. It does not resolve to a streetstreetThe named linear feature along which house numbers are ordered. Decomposes into a name plus street affixes; one of the Tier 2 fine labels.
address. There is no rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few meters — the highest tier of the geocode cascade. Sourced from address-point and situs data. reverse at headattention headOne of several parallel attention computations in a layer, each free to focus on a different kind of relationship between tokens. Their outputs are concatenated — 'multi-head attention'. Mailwoman uses 4 heads., and the sections below say what that rules out.
Prerequisites
-
The library install from Install and first parse, plus
@mailwoman/resolver-wof-sqlite. -
A full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution carrying the
place_bboxR*TreeR*TreeSQLite's spatial index of bounding boxes, enabling fast geographic range and nearest-neighbour queries in the resolver.. This is a different database from thecandidategazetteergazetteerA geographical index that maps place names and postcodes to real-world coordinates. Mailwoman uses a custom-built Who's On First (WOF) SQLite database as its gazetteer — the 'atlas' half of the grammar/atlas architecture. the geocode pages use, and it is not in the published bundleevidence bundleThe pair of retrieval-augmented input channels (street-type + locality-surface) that feed lexicon membership as soft per-token evidence alongside the text. Shipped in 6.7.0; trained natively from step 0 in the from-scratch base line. set —mailwoman data pullshipscandidate,us,frandpoi, and none of them is it. You build it:mailwoman gazetteer build adminmailwoman gazetteer build fts <path-to-admin.db>Build the planet is the walkthrough, including what the source data costs. Point
$MAILWOMAN_WOF_ADMIN_DBat the result. -
Optionally, a polygon sidecar (
polygons(id, geom)), built bymailwoman gazetteer polygons. Without it, every result iscontainment: "approximate"by construction, because the geocoder never attempts a point-in-polygon test. Point$MAILWOMAN_WOF_POLYGONS_DBat it.
Every transcript below ran against a locally-built full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution and a US-only polygon sidecar.
1. Run it from the command line
mailwoman reverse 44.5588 -72.5778 --format text
containment: polygon
locality Morrisville [wof:101728307]
localadmin Morristown [wof:404527157]
county Lamoille [wof:102080785]
region Vermont [wof:85688763]
country United States [wof:85633793]
The hierarchy is deepest-first: the winning place, then its ancestors up to countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.. --format json
emits the same chain as an object with lat, lon, containment and a hierarchy array carrying
id, name, placetype, country, lat, lon and — when the place was picked by distance rather
than by boundary — distanceKm.
Both database paths can come from flags instead of the environment: --admin-db and --polygons-db.
2. Read the containment field
containment describes how the deepest place in the chain was confirmed, and it is the field that
decides how much the answer is worth:
mailwoman reverse 40.7128 -74.0060 --format text
containment: approximate
neighbourhood City Hall Area [wof:85865569] (~0.3 km from centroid)
borough Manhattan [wof:421205771]
locality New York [wof:85977539]
county New York [wof:102081863]
region New York [wof:85688543]
country United States [wof:85633793]
That chain is correct, and it still reads approximate — because the deepest entry is a neighbourhood
with no polygon in the sidecar, so it won on nearest centroid at 0.3 km. The county, regionregionThe first-level administrative subdivision of a country — a US state, a French region, a province. The component between country and locality. and countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.
above it are not in question. approximate is a statement about the last step, not about the whole
chain.
The mechanism: the geocoder pulls every place whose bounding box contains the point from the
place_bbox R*TreeR*TreeSQLite's spatial index of bounding boxes, enabling fast geographic range and nearest-neighbour queries in the resolver., smallest-area-first, then ray-casts each one's real boundary against the polygon
sidecar. The first polygon that contains the point wins, and that is polygon. A candidate whose
polygon exists but rejects the point is dropped as a bounding-box false positive. If nothing is
polygon-confirmed, the nearest centroid among the polygon-less candidates wins, and that is
approximate.
3. Use it from the library
WOFReverseGeocoder is a root export of @mailwoman/resolver-wof-sqlite:
import { WOFReverseGeocoder } from "@mailwoman/resolver-wof-sqlite"
const geocoder = new WOFReverseGeocoder({
adminDBPath: process.env.MAILWOMAN_WOF_ADMIN_DB,
polygonDBPath: process.env.MAILWOMAN_WOF_POLYGONS_DB,
})
try {
for (const [lat, lon] of [
[44.5588, -72.5778],
[40.7128, -74.006],
[36.0, -45.0],
]) {
const { hierarchy, containment } = await geocoder.reverseGeocode(lat, lon)
const chain = hierarchy.map((p) => `${p.placetype}:${p.name}`).join(" ← ")
console.log(`${lat}, ${lon} ${containment.padEnd(11)} ${chain || "(nothing — outside gazetteer coverage)"}`)
}
} finally {
geocoder.close()
}
44.5588, -72.5778 polygon locality:Morrisville ← localadmin:Morristown ← county:Lamoille ← region:Vermont ← country:United States
40.7128, -74.006 approximate neighbourhood:City Hall Area ← borough:Manhattan ← locality:New York ← county:New York ← region:New York ← country:United States
36, -45 approximate country:France
reverseGeocode is async sugar over reverseGeocodeSync, which does the same work on the calling
threadthreadA parallel workstream within a release. Threads compose; they are not sequential milestones like phases. — reach for the sync one inside another synchronous path. The instance holds open database
handles, so close() matters; the class implements Disposable, so using geocoder = new WOFReverseGeocoder(...) works where your runtime supports it.
Three options shape a call: placetypes restricts which tiers are considered, maxCandidates
(default 128) caps the bounding-box pull, and maxApproximateKm (default 25) caps how far a
centroid-only step may reach while descending into finer tiers.
Verify
The third coordinate in that transcript is the check worth running, because it is the one that fails in a way you have to design for.
36°N 45°W is open ocean, roughly 3,700 km from anything. It returned country: France. France's
bounding box spansspanA contiguous range of characters or tokens in the input string, tagged with an address component type (street, locality, postcode, etc.). Parsed addresses are represented as collections of spans, possibly nested in a tree. its overseas departments, so the mid-Atlantic point falls inside it; with a US-only
polygon sidecar there was no boundary to reject the candidate, and the nearest-centroid fallback took
it. Loading the DE/FR sidecar instead changes the answer to United States — the French polygon now
rejects the point, and the next bounding-box candidate wins the same way.
The guard is in the result:
36, -45 approximate [{"n":"France","km":3673.201314408038}]
When containment === "approximate", hierarchy[0].distanceKm is how far the point sat from the
winning place's centroid. Threshold it. The top-level fallback has no distance cap of its own —
maxApproximateKm bounds the descent into finer tiers, not the initial pick — so a coordinate outside
every boundary you hold polygons for gets an answer regardless.
Limits
- There is no address-level reverse.
WOFReverseGeocoderreads the full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution's bounding boxes, polygons and ancestor chainsparent chainThe sequence of administrative parents above a place — Springfield → Sangamon County → Illinois → United States. Used to check the geographic coherence of a parse.. It never consults an address-point database, so having the US rooftoprooftopGeocoding precision at the building or parcel level — coordinates within a few meters — the highest tier of the geocode cascade. Sourced from address-point and situs data. shardsshardA partial output file of the corpus build, written in Parquet format. The training pipeline streams shards row by row. on disk does not add a house numberhouse numberThe numeric or alphanumeric identifier of a building on a street. Mailwoman's house_number component; its position relative to the street name flips between locales. to the result. Forward geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. is where the address-point tier lives — see Improve geocode precision. - Reverse quality follows polygon coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present., and coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. is per-countrycountryThe top-level address component (an ISO country). Closed-vocabulary, so it is best handled by a deterministic matcher feeding a proposal rather than a retrained model head.. Without a sidecar every
answer is
approximate. With one, only the countries in that sidecar get boundary confirmation. approximateoutside all coveragecoverageThe fraction of a population or region for which a data source has real, non-placeholder entries — e.g. 47% rooftop coverage on Texas addresses. Distinct from accuracy on the rows that are present. is a confident wrong answer. See the verify step. CheckdistanceKm.- The full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution is a local build. It is not one of the four published bundlesevidence bundleThe pair of retrieval-augmented input channels (street-type + locality-surface) that feed lexicon membership as soft per-token evidence alongside the text. Shipped in 6.7.0; trained natively from step 0 in the from-scratch base line., and it is
hours of work plus tens of gigabytes of source data. If you need reverse geocodinggeocodingThe process of converting an address into geographic coordinates (latitude and longitude). Mailwoman geocodes in a multi-tier cascade: exact address-point match → street interpolation → locality centroid. Each tier is progressively coarser but more widely available. without that
build, run one of the drop-in servers against someone else's deployment instead —
Swap in for Nominatim covers the
/reverseendpoint. - Two source comments point at a script that no longer exists.
resolver-wof-sqlite/reverse.tsnamesscripts/build-wof-polygons.mjsin its header and in one error message. That script becamemailwoman gazetteer polygonsin June 2026. Use the command.
Related
- Swap in for Nominatim — the same reverse geocoder behind a
Nominatim-compatible
/reverseendpoint. - Use annotations — enriching a coordinate with timezone, currency, calling code and coordinate formats.
- Build the planet — building the full WOFWOF (Who's On First). An open-source gazetteer of places maintained by Mapzen/whosonfirst. Mailwoman builds a custom SQLite database from WOF GeoJSON repos, extended with postcode data, importance scores, and coincident-role relations. distribution this page needs.