Build a geocoder: troubleshooting guide
A practical, provider-neutral troubleshooting guide for building a geocoder that converts addresses, place names, and coordinates into useful geographic results. It covers parsing, normalization, structured and free-form queries, result ranking, reverse geocoding, autocomplete, spatial validation, and operational safeguards. Examples use concepts and interfaces documented by Nominatim, Pelias, libpostal, and PostGIS, but the diagnostic workflow applies to other geocoding stacks.
A geocoder is not simply a database lookup with latitude and longitude attached. It must interpret messy human input, distinguish a street from a venue or administrative area, resolve ambiguous names, select an appropriate geometry, rank competing candidates, and explain enough about the result for an application to decide whether it is trustworthy. A failure in any one of those stages can look like the same symptom: the wrong place appears on the map. This guide is organized around isolating the failing layer. Start with the original input and the expected geographic object. Then check parsing, normalization, source coverage, indexing, ranking, geometry, and API behavior in that order. The examples are intentionally provider-neutral, with concrete references to open geocoding systems where their documented behavior is useful. Nominatim supports both free-form and structured search, while Pelias documents separate search, reverse, autocomplete, and structured-geocoding workflows. (nominatim.org)
How EDK fits
For: Developers and GIS engineers building or operating an address search, place search, forward geocoder, reverse geocoder, batch geocoding pipeline, or location-aware application.
Geocoding is one of the clearest examples of turning human language into a computable representation of the physical world. A user supplies an address, road name, landmark, facility, or coordinate; the system must connect that input to a real geographic feature with a location, type, hierarchy, geometry, and evidence of match quality. That makes geocoding an important boundary between unstructured intent and a searchable world model. The connection to E2 is editorial rather than a claim that E2 is a geocoder implementation. E2 frames location data as a common representation of places and physical features that can be found, understood, compared, filtered, measured, and analyzed. A reliable geocoder can provide one input path into that kind of system: it helps resolve a user’s text to a building, road, address, facility, neighborhood, or region before downstream geographic computation begins. It also exposes an important limitation: resolving text to a point is not the same as understanding the full physical context around that point. Downstream workflows may still need roads, buildings, infrastructure, environmental features, public assets, boundaries, and time-sensitive observations.
- •Resolve a user-entered address or place name into a candidate physical feature, then use that feature as the starting point for proximity, containment, routing, comparison, or regional analysis.
- •Use reverse geocoding to attach a coordinate from a sensor, inspection, incident, vehicle, or field workflow to nearby roads, buildings, facilities, administrative areas, or other physical context before computing about the location.
- •Treat geocoder confidence, match type, source, and geometry precision as analytical attributes rather than hiding them, so later geographic computations can distinguish an exact address from a street or locality fallback.
The EDK outcome
Before you start
Basic understanding of latitude and longitude, coordinate reference systems, and GeoJSON or another geographic interchange format.
A representative set of addresses, place names, coordinates, and expected results for testing.
Access to the geocoder’s request logs, source data or import logs, and returned candidate metadata.
A development environment capable of making HTTP requests and inspecting JSON responses.
If using spatial SQL, PostgreSQL with PostGIS or an equivalent spatial database.
Learning objectives
Distinguish address parsing failures from source-coverage, candidate-generation, ranking, geometry, and API failures.
Build a reproducible test corpus for forward, reverse, structured, autocomplete, and batch geocoding.
Use geographic constraints and explicit result precision to reduce ambiguity.
Validate coordinate order, CRS, distance units, and reverse-geocoding semantics.
Design an operational result contract that preserves confidence, match type, source, and geometry metadata.
Verification
Every test case records raw input, normalized input, parsed components, candidate count, selected result, precision, source, and latency.
Exact addresses do not silently become street or locality results without an explicit fallback classification.
Forward and reverse requests use the documented coordinate order and a known CRS.
Structured and free-form queries are compared for the same expected location.
Autocomplete does not allow stale responses to overwrite newer suggestions.
Distance thresholds use meters for geography or documented units for geometry, and spatial indexes are present where expected.
Production logs exclude sensitive address data or apply the organization’s approved retention and redaction policy.
Troubleshooting
No results: query the source data directly, check import freshness, verify country and language assumptions, and test street-only or locality-only fallbacks.
Wrong country or city: add a country, region, boundary, or focus constraint and inspect whether the query is ambiguous rather than merely misspelled.
Street returned instead of address: verify house-number coverage and inspect the result’s precision or match type; do not infer an exact point from a street centroid.
Wrong venue among same-name places: use geographic focus, venue or layer filters, category information, and explicit locality or country constraints.
Reverse result looks nearby but wrong: verify latitude/longitude order, CRS, nearest-feature semantics, and whether the selected object is suitable for address return.
Distances are wildly incorrect: check SRIDs and units. Geometry distance uses the spatial reference units; geography distance uses meters. (postgis.net)
Autocomplete flickers or shows old results: throttle requests, cancel or ignore stale responses, and log request sequence numbers.
Works locally but fails in production: compare source versions, index contents, environment variables, proxy timeouts, CORS, rate limits, and response schemas.
High latency: reduce candidate payloads, cap result counts, add geographic constraints, inspect index health, and separate autocomplete from full search.
Low confidence but plausible results: retain alternatives, expose precision and match metadata, and require user confirmation or review for consequential workflows.
Next steps
Read the companion Build a geocoder guide for the baseline architecture and data flow.
Use the production guide to plan imports, indexing, updates, observability, and operational ownership.
Add a regression suite that runs after every source refresh or ranking change.
Measure results separately by country, language, feature type, and address completeness instead of reporting one global accuracy number.
Connect resolved locations to downstream spatial operations such as proximity, containment, routing, facility analysis, or change detection only after preserving the geocoder’s uncertainty and precision metadata.
What you need before debugging
Before changing ranking logic or adding fuzzy matching, create a small diagnostic corpus. Include exact street addresses, addresses with missing punctuation, local abbreviations, multilingual forms, postal codes, landmarks, venue names, ambiguous city names, rural addresses, apartment or unit identifiers, and coordinates near boundaries. For each case, record the expected result type and acceptable geometry precision: address point, building, interpolated point, street, locality, or region.
Keep the raw query unchanged and store every intermediate representation you generate. At minimum, retain the normalized string, parsed components, country or language assumptions, selected source records, candidate list, score components, returned geometry, match type, and response latency. A single final latitude and longitude is not enough to explain why a result was chosen.
- •A reproducible test set with expected result IDs or acceptable areas.
- •A way to inspect raw source records and their update dates.
- •A known coordinate reference system, normally WGS84 for API latitude and longitude inputs.
- •A request log that includes query, parameters, response status, candidate count, selected result, and latency.
- •A policy for attribution, licensing, caching, privacy, and third-party service limits.
Step 1: determine whether parsing or search is failing
Start by asking whether the input was understood as the intended components. Parsing and geocoding are separate operations. libpostal, for example, is an address parser and normalizer designed to label components such as house number, road, city, state, and postcode; it does not by itself prove that the address exists or return the authoritative coordinate for it. (github.com)
Run the same query through a component-inspection path before sending it to the search index. For a query such as `1600 Pennsylvania Ave NW Washington DC 20500`, inspect whether the parser identifies the house number, road, locality, region, and postcode. Then compare that result with a deliberately structured request. Structured geocoding can reduce ambiguity because the caller supplies separate fields rather than asking the parser to infer field boundaries from one string. Pelias documents structured fields including address, locality, region, county, neighborhood, and postal code. (github.com)
- •Compare `text=...` or free-form input with structured fields for the same address.
- •Check whether a comma, apartment number, directional, or postal code changes the parse unexpectedly.
- •Test local-language and Latin-transliterated forms separately.
- •Verify that normalization does not remove meaningful tokens such as a house number, unit, building name, or road suffix.
- •If a parser consistently fails for one country or address style, inspect the source data and parser’s documented issue process instead of assuming the index is at fault. libpostal explicitly notes that some formatting mistakes require preprocessing and that missing or incorrect source data can be the real problem. (github.com)
Step 2: verify source coverage and candidate generation
If the parsed components look correct but no result is returned, determine whether the target exists in the data you indexed. Query the source dataset directly by stable identifier, name, street, locality, and approximate geometry. Check whether the address is represented as a point, building, entrance, street range, or only as a street or locality.
A common mistake is expecting every geocoder to return an exact address point. The available geometry may be a building centroid, a street centroid, an interpolated position, or a broader administrative feature. Pelias documents that address search can fall back to less precise results when an exact address is unavailable, and that result accuracy can distinguish a point from a centroid. (github.com)
- •Search the source data independently of the geocoder API.
- •Compare exact address, street-only, locality-only, and venue-name queries.
- •Inspect whether the expected object is an address, venue, street, region, or country record.
- •Use country or region restrictions when the application already knows them.
- •Record whether a result is exact, interpolated, centroid-based, or a fallback.
Step 3: troubleshoot ranking and ambiguity
When the correct candidate exists but is not first, inspect ranking rather than adding a larger result limit. Ranking commonly combines text similarity, component matches, postal-code agreement, geographic focus, feature type, popularity, and source-specific importance. Pelias documents that confidence and match quality depend on factors such as house-number and street-name agreement, postal-code matches, nonmatching locality fields, and the type of result returned. (github.com)
A useful diagnostic response contains the top several candidates and a breakdown of why each candidate scored as it did. For an address query, a house-number mismatch should be a serious penalty. A city or country match should not outrank a complete address match merely because it has a more prominent name. For a venue query, however, popularity or local focus may be reasonable ranking signals.
- •Compare exact spelling, abbreviation-expanded, and typo-containing variants.
- •Check whether the house number matched, not merely the street name.
- •Test the same query with country, boundary, and focus parameters.
- •Separate ranking for address search from ranking for venue or locality search.
- •Define acceptance rules such as exact address match, minimum confidence, or maximum distance from a trusted coordinate.
Step 4: validate reverse geocoding and spatial behavior
Reverse geocoding has a different failure mode from forward geocoding. Given latitude and longitude, the system must choose a nearby suitable object and return its address or place context. Nominatim documents that reverse geocoding does not calculate a perfect civic address from the coordinate; it finds a nearby suitable OpenStreetMap object, which can produce unexpected results in dense areas or near unrelated streets. (nominatim.org)
Validate reverse results with distance and geometry checks. Confirm latitude and longitude order, coordinate reference system, point precision, and whether the returned feature is actually the nearest relevant object. Test points near road intersections, parcel boundaries, coastlines, campuses, large buildings, parking lots, and unnamed paths. A visually plausible result can still be several hundred meters from the input coordinate.
- •Confirm the API expects `lat,lon` or `lon,lat` before sending requests.
- •Check the input and returned geometry in the same CRS.
- •Measure the distance from the query point to the returned feature.
- •Distinguish nearest object from nearest addressable object.
- •Use indexed distance filtering before expensive exact-distance calculations.
Step 5: isolate autocomplete, API, and operational failures
Autocomplete should not be treated as a smaller version of final search. It receives partial input, often on every keystroke, and needs throttling, cancellation of stale requests, bounded result counts, and a clear handoff to a full search when the user submits. Pelias recommends throttling autocomplete requests and using a final search request when the user completes the interaction. (github.com)
If autocomplete is slow or inconsistent, log request sequence numbers and response timestamps. A late response for `san` can overwrite a newer response for `san f`. Apply client-side cancellation or discard responses whose sequence number is older than the latest request. Keep the result payload compact and avoid returning full geometries until the user selects a result.
- •Throttle keystroke-driven requests and discard stale responses.
- •Use autocomplete for suggestions and full search for committed queries.
- •Track rate-limit responses, timeouts, retries, and upstream failures separately.
- •Validate attribution and data-license requirements before publishing results.
- •Keep provider-specific response fields mapped into a stable internal result contract.
Workflow
Create a reproducible diagnostic request
Capture the raw query, request parameters, timestamp, expected result, and actual response. Keep a small corpus of exact, partial, multilingual, ambiguous, and coordinate-based examples. Re-run the same request against development, staging, and production so data and configuration differences are visible.
curl -sS 'https://your-geocoder.example/v1/search?text=1600%20Pennsylvania%20Ave%20NW%20Washington%20DC&size=5' | jq .Inspect parsing before search ranking
If you use a separate parser, log its component output before querying the geographic index. Compare free-form input with structured fields. Verify that house number, road, locality, region, postal code, language, and country were not dropped or misclassified.
python - <<'PY'
from postal.parser import parse_address
query = '1600 Pennsylvania Ave NW Washington DC 20500'
print(parse_address(query))
PYCheck source coverage and result precision
Look up the expected address or place in the underlying source data. Determine whether the available feature is an address point, building, street, venue, locality, centroid, or interpolated position. Treat missing source data as a coverage problem rather than automatically changing fuzzy matching.
curl -sS 'https://your-geocoder.example/v1/search/structured?address=1600%20Pennsylvania%20Ave%20NW&locality=Washington®ion=DC&size=5' | jq '.features[] | {label: .properties.label, layer: .properties.layer, accuracy: .properties.accuracy, confidence: .properties.confidence, geometry: .geometry}'Test ranking with geographic constraints
Run the same query without constraints, with a country filter, and with a focus point or bounding area. Compare candidate order and score metadata. Verify that the application is not treating a broad locality result as equivalent to an exact address.
curl -sS 'https://your-geocoder.example/v1/autocomplete?text=union%20square&focus.point.lat=40.7&focus.point.lon=-73.9&size=5' | jq '.features[] | {label: .properties.label, layer: .properties.layer, confidence: .properties.confidence}'Validate reverse results spatially
Check coordinate order and calculate the distance between the input point and returned feature. For database-backed workflows, use indexed distance filtering with units appropriate to the geometry or geography type.
SELECT id, name
FROM places
WHERE ST_DWithin(
geog,
ST_SetSRID(ST_Point(-73.986027, 40.748517), 4326)::geography,
100
)
ORDER BY ST_Distance(
geog,
ST_SetSRID(ST_Point(-73.986027, 40.748517), 4326)::geography
)
LIMIT 10;Separate autocomplete from committed search
Throttle keystroke requests, attach a monotonically increasing request ID, and ignore responses older than the latest request. Use a full search request after submission so the final result can use more complete input and broader ranking logic.
let latestRequest = 0;
async function suggest(text) {
const requestId = ++latestRequest;
const response = await fetch('/v1/autocomplete?text=' + encodeURIComponent(text));
const data = await response.json();
if (requestId !== latestRequest) return;
renderSuggestions(data);
}Questions teams ask
Why does my geocoder return the street instead of the exact address?
The source may not contain an address point for that house number, or the geocoder may intentionally fall back to a less precise feature such as a street. Inspect the source record, returned precision or match type, and whether the house number matched. Do not label a street centroid as an exact address.
Should I use free-form or structured geocoding?
Use free-form input when the application receives natural user text. Use structured fields when your data already separates address, locality, region, and postal code, or when field boundaries are especially ambiguous. Structured input can reduce parsing ambiguity, but it does not fix missing or incorrect source data. (github.com)
How should I decide whether the first result is safe to accept?
Define an acceptance policy using the result type, house-number and street matches, geographic constraints, confidence or score, geometry precision, and distance from any trusted reference coordinate. For important workflows, retain alternatives and route ambiguous records to review rather than accepting the first result automatically.
Why is reverse geocoding sometimes wrong even when the coordinate is accurate?
Reverse geocoding usually selects a nearby suitable mapped object; it does not necessarily calculate the exact address of the coordinate. Dense streets, campuses, large parcels, unnamed paths, and administrative boundaries can all produce a nearby result that is not the one a person expects. (nominatim.org)
Can a parser such as libpostal replace a geocoder?
No. An address parser labels and normalizes components. A geocoder must then search geographic data, generate candidates, rank them, and return geometry and metadata. libpostal’s documentation describes parsing and normalization, not authoritative coordinate resolution. (github.com)
Keep exploring
build a geocoder
Use the companion practical guide for the end-to-end architecture, data flow, and implementation choices behind a geocoder.
build a geocoder production guide
Continue with production deployment concerns such as ingestion, indexing, update operations, observability, and service boundaries.
analyze spatial data with geopandas
Use GeoPandas workflows to inspect returned geometries, compare candidate locations, and validate spatial joins or measurements after geocoding.
build a routing service
Follow geocoding with routing when a resolved address or place must become a network path, travel-time estimate, or service-area computation.
Sources
Nominatim Search API documentation
Authoritative documentation for free-form and structured search queries, endpoint behavior, and search parameters.
Open sourceNominatim Reverse Geocoding documentation
Explains how reverse geocoding selects nearby suitable mapped objects and why the result may not be an exact civic address.
Open sourcePelias documentation
Documents forward search, reverse geocoding, autocomplete, structured geocoding, source and layer filters, and search workflows.
Open sourcelibpostal project documentation
Primary documentation for international address parsing, normalization, supported interfaces, model data, and known limitations.
Open sourcePostGIS ST_DWithin reference
Authoritative reference for indexed distance filtering, geometry units, geography meters, and compatible spatial reference systems.
Open source