An administrative gazetteer and experimental forward/reverse geocoder built on Overture Maps data, powered by Rust, Cloudflare Workers, and R2. Production coverage is divisions-first; Places/POI support is opt-in and experimental, and addresses, streets, fuzzy matching, and a broadly Nominatim-compatible contract are not yet provided.
- Global Coverage: 450K+ cities, neighborhoods, and administrative areas worldwide.
- Serverless Architecture: Runs entirely on Cloudflare Workers with zero persistent server management.
- Cost-Effective: Uses SQLite shards stored in R2 to bypass database storage limits and minimize costs.
- Fast Search: Full-Text Search (FTS5) with prefix matching for autocomplete.
- Reverse Geocoding: Routes from requested coordinates when one country bbox is unambiguous, then performs bbox-based hierarchical lookup over countries, regions, counties, and populated localities. Ambiguous country bboxes conservatively fall back to the global reverse shard; IP location never decides a geometric ambiguity. Results remain bbox-confidence estimates, with exact polygon containment left as future work.
- GERS ID Lookup: Resolve any Overture GERS ID to its bounding box via UUID-prefix-sharded parquet index.
- Zero Egress: Client-side libraries can fetch full geometry directly from Overture's S3 buckets.
Base URL: https://geocoder.bradr.dev
Forward shards include countries, regions, counties, local-admin divisions,
and localities. Counties and local-admin divisions are kept in their
country/region shards rather than the globally loaded HEAD shard.
Endpoint: GET /search
| Parameter | Type | Default | Description |
|---|---|---|---|
q |
string | required | Search query (e.g., "Boston", "New York") |
limit |
int | 10 | Max results to return (1-40) |
autocomplete |
bool | true | Enable prefix matching for the last token |
format |
string | json | Response format: json or geojson |
debug |
bool | false | Include debug info (shards loaded, user location) |
Example:
curl "https://geocoder.bradr.dev/search?q=boston&limit=1"Response:
{
"results": [
{
"gers_id": "...",
"name": "Boston",
"type": "locality",
"lat": 42.3601,
"lon": -71.0589,
"bbox": [ ... ],
"importance": 0.85,
"country": "US",
"region": "US-MA"
}
]
}Endpoint: GET /reverse
| Parameter | Type | Default | Description |
|---|---|---|---|
lat |
float | required | Latitude (-90 to 90); used for reverse lookup and country-shard routing |
lon |
float | required | Longitude (-180 to 180) |
Example:
curl "https://geocoder.bradr.dev/reverse?lat=42.3601&lon=-71.0589"Endpoint: GET /id/:gers_id
Resolves any indexed Overture ID to its bounding box. Format-v3 indexes also return a release-pinned locator for the source GeoParquet file.
Example:
curl "https://geocoder.bradr.dev/id/08b2a100-d664-7fff-0200-a44bcea04b76"Response:
{
"id": "08b2a100-d664-7fff-0200-a44bcea04b76",
"bbox": {
"xmin": -71.06,
"ymin": 42.35,
"xmax": -71.05,
"ymax": 42.36
},
"feature_type": "place",
"theme": "places",
"filename": "part-00001-example-c000.zstd.parquet",
"last_seen_release": "2026-06-17.0",
"registry_member": true,
"exists_in_current_release": true,
"overture_path": "release/2026-06-17.0/theme=places/type=place/part-00001-example-c000.zstd.parquet"
}The locator fields are additive. Legacy format-v1 shards continue returning
only id and bbox. Format v3 stores compact source-file and historical-
release IDs in each shard and expands them through one content-addressed
dictionary per shard set. A registry record with no current path returns
null type/filename/path fields and exists_in_current_release: false.
This project uses a sharded architecture to handle global datasets within the constraints of serverless edge computing.
- Data Ingestion: DuckDB extracts division data from Overture Maps' S3 buckets (Parquet format).
- Shard Generation:
scripts/build_shards.pypartitions divisions by country/region into optimized SQLite databases with FTS5 indexes.scripts/build_id_index.pybuilds a UUID-prefix-sharded parquet index mapping every GERS ID to its bounding box and optional source-file locator. Streams from Overture's registry and release themes via DuckDB, stages partitioned parquet to R2, then merges into sorted uncompressed shards.
- Storage: All shards are uploaded to Cloudflare R2 (
geocoder-shardsbucket), versioned by date. - Runtime: The Rust Worker (
crates/geocoder-worker) dynamically fetches shards from R2, caches at the edge via the Cache API, and queries them for each request.
graph LR
User[Client] --> Worker[Rust Worker]
Worker --> Cache[Edge Cache]
Cache --> R2[R2 Bucket]
R2 --> SQLite[SQLite Shards]
R2 --> Parquet[Parquet ID Index]
Measured 2026-06-12 against production from a single US-East client
(scripts/benchmark_latency.py, paced under the 60 req/min rate limit;
"cold" targets low-traffic shards likely absent from the edge cache,
"warm" repeats the same requests):
| endpoint | warm p50 | warm p95 | cold p50 |
|---|---|---|---|
/search |
156ms | 4,822ms* | 274ms |
/reverse |
36ms | 689ms | 192ms |
/id/{gers_id} |
119ms | 5,749ms* | 465ms |
* tail samples are first-touch loads of multi-MB shards on a fresh isolate or evicted edge cache; warm-isolate requests typically answer in 30-160ms. Run-to-run p50s vary roughly 2x with colo and cache state.
Result quality vs other open geocoders on the
geocoder-tester global
city cases (scripts/benchmark_geocoders.py, 2026-06-11):
| Overture | Nominatim | Photon | |
|---|---|---|---|
| Top-1 name match | 80% | 67% | 67% |
| p50 latency | 77ms | 65ms | 479ms |
Reproduce with:
python scripts/benchmark_latency.py --output run.json --compare benchmarks/2026-06-11-baseline.json
python scripts/benchmark_geocoders.py --limit 40 --warmup 1- Rust (latest stable)
- Node.js & npm
- Cloudflare Wrangler (
npm install -g wrangler) - DuckDB + Python
duckdbpackage (for data scripts)
cd crates/geocoder-worker
wrangler dev# Build US shards for local testing
./scripts/download_divisions.sh
python scripts/build_shards.py --countries USDeploy the worker to Cloudflare:
cd crates/geocoder-worker
wrangler deployAutomatically deploys the worker to Cloudflare when CI passes on main.
A scheduled workflow (monthly, 25th) that rebuilds all data from the latest Overture release. Runs two parallel jobs:
- Forward + Reverse shards: Downloads divisions, builds SQLite shards, uploads to R2.
- ID Index: Streams the Overture registry + release themes, builds UUID-prefix-sharded parquet, uploads to R2.
Manual Trigger Inputs:
build_type:forward,reverse, orbothbuild_id_index: Toggle ID index build (default: true)countries: Comma-separated list (e.g.,US,CA) to limit forward/reverse buildconfirm: TypeREBUILDto confirm
MIT