Reach — design doc
Reach answers one question: what can I reach from here within N minutes by walking, bike, transit or car, and what is inside that area? It is a public MCP server for agents plus a map playground.
Coverage: the 12 largest US metros (New York, Los Angeles, Chicago, Dallas–Fort Worth, Houston, Washington, Philadelphia, Atlanta, Miami, Phoenix, Boston, San Francisco Bay Area) and the 8 largest Indian metros (Delhi NCR, Mumbai, Kolkata, Bengaluru, Chennai, Hyderabad, Ahmedabad, Pune). The tool is built for dense cities, where "what's within 20 minutes" really depends on the mode and the transit network.
The primitive: a zone
A zone is an origin, a mode and a time budget, plus the assumptions behind them: departure time and how transit waits are counted. Creating one computes the travel time from the origin to every reachable street point and stores the result under a zone_id. Every other tool reads that stored zone and does no routing.
| Tool | Does |
|---|---|
create_zone |
Origin + mode + minutes → zone_id, resolved inputs, assumptions, data dates |
search_places |
Plain-language search inside a zone, ranked by travel time, paged |
travel_times |
Minutes from the origin to named places or coordinates |
combine_zones |
Intersect ("we can all reach it") or union; the result is a zone |
zone_geometry |
GeoJSON outline, optionally in time bands, for drawing |
list_regions |
Coverage, limits, data freshness, attribution |
Data, built offline
reach-build turns two kinds of public data into compact per-city files:
- OpenStreetMap (Geofabrik state or zone extracts) → per city, a street graph with separate walk, bike and drive edges and speeds, a full-text index of named places, and a geocoder of names, addresses and town boundaries. Each extract is parsed once for every city inside it (California feeds both LA and the Bay Area). Boston: 1.9M street points, 27k places, 650k geocoder entries.
- Transit timetables (GTFS) → every scheduled stop-to-stop ride from every agency serving the city, merged and sorted by departure. Feeds are found automatically in the Mobility Database catalog by bounding box (or by listed city when the catalog has no box), fetched from the agency's own URL, and skipped when they are national or statewide. When an agency only serves its feed behind an API key, Reach uses the catalog's public mirror copy, which can be months old. Boston merges 16 agencies, New York 26, Los Angeles 44.
Graph arrays are memory-mapped files. A city loads on its first request (seconds) and the least-used city is dropped when more than 3 are loaded, so all 20 fit on one 8 GB machine.
Routing, on demand
- Walk, bike, drive: Dijkstra from the origin, stopped at the budget. 0.03–0.25 s.
- Transit: walk to stops, then a Connection Scan over the timetable, then walk from every stop reached. Transfers are unlimited: board any ride leaving a stop at least 60 s after you arrive, and walk up to 300 m between nearby stops. The default "typical" wait repeats this for 6 departures 5 minutes apart and takes the median time per point. About 1.4 s.
- Places are pre-snapped to the nearest street point per mode. A place's time is that point's time plus the short last leg.
Why not precompute? Hub tricks like contraction hierarchies speed up single A-to-B routes. A zone needs the time to every point inside it, so the work scales with the answer, and timetables break those tricks anyway. Storing every answer for ~120k origin cells would take tens to hundreds of GB and days of transit compute, to save about a second on the first request. Identical requests already hit the cache.
Local assumptions
- India: driving uses half of typical speed limits to reflect city traffic, and trunk roads count as walkable and bikeable streets. Indian agencies often stop updating their published calendars. When a feed is past its end date, Reach assumes its last weekly pattern still runs and says so in the zone. Kolkata and Chennai have no usable open timetable, so they offer walk, bike and drive only.
- US: driving uses 85% of posted limits. Statewide and national operators such as Amtrak are left out of transit.
Honesty, privacy, limits
- Every zone echoes its city, the resolved origin and other candidate matches, along with its speeds, wait rule, stale-schedule assumptions, OSM snapshot date, transit agencies and attribution.
- Origins are snapped to a ~100 m grid cell before routing. Raw input is never stored or logged. Zone ids are HMACs. Zones expire after 24 h.
- Each client gets 40 new zone computations per hour, at most 2 run at once, and the HTTP transport is stateless.
Known gaps
- Driving has no traffic, turn delays or parking time.
- Transit has no real-time delays, and zones show the median trip, not the unlucky one.
- OpenStreetMap places are incomplete and hours are not checked. Coverage varies a lot: New York has 98k places, Ahmedabad 2.5k.
- Some agencies are missing because their feeds sit behind API keys with no usable mirror: AC Transit, SamTrans and SMART in the Bay Area (a free 511.org key would add them). Washington's WMATA comes from a mirror copy whose calendar has ended, so its weekly pattern is assumed.
- Outlines are drawn on a 50 m grid with a ≤200 m off-street walk, for display only. Search uses exact point times.
- The first request in a city waits a few seconds while it loads.
- Everything runs on one 8 GB laptop behind a Cloudflare tunnel.
Adding a city
Add a Region (bbox, timezone, Geofabrik extracts, default origin, aliases) to regions.py and run reach-build <id>. Transit feeds are discovered automatically; feeds the catalog lists without a bounding box can be added by id. The server picks up every built city on restart.