Offline address geocoding
MESA attaches coordinates to addresses by matching them against a local copy of authoritative address points, entirely in memory, with no network calls. The matcher is deterministic (same input and reference produce the same output) and every result is labeled with how precisely it was placed.
01 reference data
The geocoder reads one required layer and, optionally, two supplemental layers, each a separate SQLite file that is loaded once into compact in-memory indexes. Nothing is merged, and no web service is ever called. You supply the data; a bundled Python script builds it from the authoritative source.
addressesrequired. Authoritative address points (street, city, ZIP, lat, lon, optional grid/unit/landmark columns). All validation uses Utah's UGRC statewide points, 1,489,417 records.road_segmentsoptional. Road centerlines with address ranges, consulted only after the point layer fails, to interpolate a block-level position.address_pointsoptional local supplement, consulted first but only for exact ZIP+street matches; it never fuzzy-matches.Coordinates must be WGS84 (EPSG:4326) decimal degrees. Provenance tables record the dataset, build, and source licenses (for Utah: UGRC address points and roads, public domain under Utah GRAMA / SGID).
02 normalization & parsing
The pipeline is Normalize → Parse → Retrieve → Score → Place. Normalization uppercases
text, strips punctuation, collapses whitespace, and canonicalizes standard abbreviations
(AVENUE → AVE) and compound directionals
(NORTHEAST → NE) before any lookup. Parsing splits the
address into house number, directional, street name, type, unit, city, state, and ZIP.
The canonical full address stays the primary match field; the parsed components are
structured inputs to scoring.
Several jurisdiction-specific normalizations matter for the rural western address forms MESA targets:
- Grid-coordinate splitting adds the missing space in a Utah grid address, so
820S 2670Ebecomes820 S 2670 E; only a single trailing directional letter is split, which leaves ordinals like3RDand147THintact. - Decompounding separates a run-together street token into two attested corpus tokens
(
STONERIDGE→STONE RIDGE), but only when both halves are known and the compound is not, or when the spaced pair is attested in that ZIP; run-together PO boxes are handled the same way. - A "Highway Drive" with no route number resolves to the single numbered highway serving that ZIP.
- Non-physical inputs such as PO boxes, HC/RR routes, and placeholders are classified as not spatially locatable (following NG9-1-1 practice) rather than forced onto a coordinate.
A ZIP+4 is accepted but only the first five digits are used.
03 candidate generation
Candidates are retrieved through several blocking channels (sorted in-memory lookup tables built once at load), keyed by ZIP, city, house+ZIP, house+city, and so on. The shipped cascade is: exact hash lookup first; then, if needed, house-number-keyed fuzzy buckets. Fuzzy matching is skipped entirely if a candidate set exceeds 500 records (it would be both slow and low-precision), and exact string equality within the set is always tried before any fuzzy comparison.
04 the address-geometry gate
Before any fuzzy name comparison, a candidate must pass a geometry-skeleton gate: the numeric and directional tokens of the address (its "skeleton") must agree. This is the single most important guard against false matches.
- For pure-grid streets the whole skeleton must match exactly. This rejects adjacent
grid streets and quadrant transpositions such as
710 E 340 Svs710 S 340 E. - For named streets the numeric/grid tokens must match exactly, but one side may carry
an extra directional the other omits (
4827 W TIMBER FORK RDvs4827 TIMBER FORK RD), preserving token order; a conflicting quadrant is still rejected.
A name-token alignment check then requires every canonical street-name token to appear in the query within a typo allowance, which tolerates trailing locality junk (like a county name) while rejecting a fabricated street name grafted onto a real house and ZIP.
String similarity
The live matcher uses two string metrics, both fully specified for reproducibility:
bounded Levenshteinearly-exit banded edit distance, used for token-level checks4OSA distanceoptimal string alignment (transposition-aware Damerau-Levenshtein); a swap of two adjacent characters is a single edit, which matches how real address typos and OCR errors behave305 the match score
Surviving candidates are ranked by a Fellegi-Sunter record-linkage score1:
a sum of per-field log-odds weights. Each weight is log2(m/u) in bits, where
m is the probability a true match agrees on a field (taken as 0.97 under
realistic input noise) and u is the probability two records agree by chance,
measured from random same-ZIP address pairs in the Utah corpus.
A field's weight is added on agreement and subtracted on disagreement (a suffix
mismatch is penalized more softly, at half weight). The street name contributes a
rarity × similarity term: each name token is weighted by how rare it is in the
corpus (its inverse document frequency) and scaled by 2·similarity − 1,
where similarity is 1 − OSA / max(len). So an exact token earns its full
(rarity-weighted) positive weight, a half-corrupted one earns roughly zero, and a
genuinely different name earns a negative weight. This is what lets the score separate a
real typo from a fabricated name dropped onto a real block. ZIP and city agreement are
weak nudges, since candidates are already ZIP/city-scoped.
The total log-odds is mapped to a 0-1 certainty by a Platt sigmoid2, fit by logistic regression on the development split only:
An ambiguity guard withholds a point (degrading it to a centroid) when a different-ZIP record sharing the query's house number scores within a small log-odds margin of the winner; same-ZIP rivals are treated as data duplicates, not ambiguity.
06 match modes
A match whose certainty is below the active mode's threshold is degraded to an
APPROXIMATE centroid rather than emitted as a possibly-wrong point. The mode
changes only this threshold, not the scoring.
| mode | threshold | use |
|---|---|---|
| Permissive | 0.00 | emit every point match; maximize yield, review downstream |
| Balanced | 0.90 | default. Calibrated, but effectively non-withholding, so real-world point yield is preserved |
| Strict | 0.98 | withhold low-confidence points; maximize precision, accept lower yield |
Balanced is the default for an evidence-based reason. On the held-out synthetic test set, Strict looked nearly free; but on the real patient sample it cut point yield from 62% to 50%, because real patient-entered addresses score lower than synthetic positives and a threshold tuned on synthetic data does not transfer. Balanced keeps the calibrated score without paying that yield cost.
07 precision tiers
Every geocoded row carries a Geocode_Precision label. Read it first; it,
not the certainty score, tells you whether a coordinate is a real location.
| tier | meaning | position |
|---|---|---|
| POINT | matched an authoritative address point | the reference coordinate itself |
| INTERPOLATED | estimated between known house numbers, or along a road centerline whose range brackets the house | median 9 m, P95 85 m (96% < 100 m) |
| RELOCATED | unique street+house recovered in a neighbouring ZIP (a ZIP/city typo), gated by proximity | address-level |
| LANDMARK | the "street" was a named place found in the stated ZIP | approximate |
| APPROXIMATE | no street match; a ZIP, city, or ZIP+city centroid | locality only |
| VERY_LOW | last-resort whole-database centroid | do not map as a location |
| NONE | could not be placed; coordinates blank | n/a |
MESA also emits a derived analytic-support class (address_point, interpolated_street, zip_centroid, and so on) and a plain-language description, computed deterministically from the precision and method so downstream analysis can filter to the tier it needs.
08 validation
Accuracy here means recovery of the correct reference record under noisy input, not independent real-world coordinate accuracy (which these figures do not measure). A query counts as a success only if it resolves to within 50 m of the correct record's coordinate, so snapping to a different nearby record is scored a miss. The noise generators deliberately corrupt the exact tokens the production gate guards (house numbers, directionals, grid ordinals), and the negative controls are fabricated by mechanisms the gate was not tuned on (directional swaps to real adjacent locations, transposed grid ordinals, house numbers lifted from distant real addresses, phonetic fake streets, cross-county ZIP splices), so a high specificity means plausible fakes are rejected rather than forced onto a point.
Evaluated queries are split into disjoint development and test partitions by a stable hash of the address id; the reference database is always the full corpus. Calibration constants and thresholds were fit on development only, and confidence intervals come from a county-stratified bootstrap of at least 10,000 resamples. Changes that improved only the synthetic harness, or one metric at another's significant expense, were rejected.
| metric (held-out test, Balanced) | estimate | 99% CI |
|---|---|---|
| sensitivity | 92.7% | [92.3, 93.1] |
| specificity | 98.2% | [97.9, 98.4] |
| precision (PPV) | 95.7% | [95.4, 96.0] |
| accuracy | 94.9% | [94.6, 95.1] |
There is deliberately no "positional error" headline: because the reference coordinate is both what the matcher returns and what it is scored against, a correct match is 0 m by construction, so that number would restate precision rather than measure independent accuracy. The honest measures are the recovery and rejection rates above.
On real free-text input (rather than the clean reference), some addresses fall back to labeled centroids. A forensic review of that residual found it dominated not by matcher failures but by addresses genuinely absent from the reference data: non-standard grid and rural addressing, naming-convention mismatches, and house numbers outside any reference range. Spot-checked cases were absent from both UGRC and OpenStreetMap. The residual is therefore an authoritative-data-coverage limit, not a matching one, and it moves with the completeness of whatever address data you load.
§ references
- Fellegi, I. P., & Sunter, A. B. (1969). A theory for record linkage. Journal of the American Statistical Association, 64(328), 1183-1210.
- Platt, J. C. (1999). Probabilistic outputs for support vector machines and comparisons to regularized likelihood methods. In Advances in Large Margin Classifiers (pp. 61-74). MIT Press.
- Damerau, F. J. (1964). A technique for computer detection and correction of spelling errors. Communications of the ACM, 7(3), 171-176.
- Levenshtein, V. I. (1966). Binary codes capable of correcting deletions, insertions, and reversals. Soviet Physics Doklady, 10(8), 707-710.
- Goldberg, D. W., Wilson, J. P., & Knoblock, C. A. (2007). From text to geographic coordinates: the current state of geocoding. URISA Journal, 19(1), 33-46.
- Zandbergen, P. A. (2008). A comparison of address point, parcel and street geocoding techniques. Computers, Environment and Urban Systems, 32(3), 214-232.
- United States Postal Service. Publication 28: Postal Addressing Standards.
- Utah Geospatial Resource Center (UGRC). Utah Statewide Address Points, Utah SGID. gis.utah.gov