Knowhere

Routing

Knowhere answers travel questions over the real network — actual roads, one-way streets, footpaths, per-road-class speeds — not straight-line distance.

  • route — one origin to one destination. Travel time, distance, and (opt-in) the polyline.
  • drive_times — one origin to many destinations (max 100) in one call.
  • walk_reach — what you can reach on foot from a point, without naming the destinations first. Matching places with real walking distances, nearest first.
  • walk_loop — a walking circuit of roughly a requested length that starts and ends at a point without walking the same street twice, optionally routed through places a query names.

route and drive_times answer how far away are these places I named. walk_reach answers what is there. walk_loop answers take me for a walk.

Which tool

near measures straight-line distance and is the cheapest way to filter candidates. drive_times measures how quick they are to get to. The two diverge sharply wherever the road network does not follow the crow — a supermarket 1 km away across a freeway is a 15-minute drive. The usual pattern combines them:

1. search  nw[shop=supermarket](area=wyoming)       -> candidates
2. drive_times from home to those                   -> ranked by minutes

Walkable is never a straight line. A cafe 200 m away across a freeway with no crossing is a two-kilometre walk or no walk at all. Any question with "walk" in it — "walking distance", "a 10-minute walk", "can I walk to" — goes to walk_reach. Answering it with near or (around=…) produces a confident number that is wrong exactly where people care.

walk_reach

Walks outward over the pedestrian network until the distance budget runs out, then returns the features matching find that it actually got to.

walk_reach area=colorado from_lat=39.7392 from_lon=-104.9847
           max_walk_meters=1250
           find="nw[amenity=cafe](area=colorado)"

Budgets: 15 minutes ≈ 1,250 m, 30 minutes ≈ 2,500 m; "walkable" with no number given is 1,250 m. Each result carries walk_meters (network distance, including how far the place sits off the nearest path), walk_minutes, and snap_meters.

Omit find for a summary instead of a list — reachable junction count, bounding box, distance deciles. Raw junctions are never returned: a reach settles thousands of them and they crowd out the answer. Scripts that want them use routing.reach() (knowhere://docs/runtime).

Foot only. The car graph is a contraction hierarchy whose upward edges are only part of the graph, so a one-sided search over it returns the search space rather than the reachable set — a wrong answer that looks plausible. walk_reach has no profile parameter, and asking for a driving version returns NO_ISOCHRONE. "Within a 20-minute drive" needs drive_times against destinations you name.

Capped at 5,000 m, about an hour's walk, independent of route's 25 km ceiling: a reach holds its whole neighbourhood in memory and that grows with the square of the radius. Over the cap returns REACH_TOO_LARGE rather than quietly answering a smaller question.

Nowhere to walk

Some places have no pedestrian network. A mountain-valley address reaches four junctions at 1,250 m where a suburb reaches ~2,100 and a city centre ~6,500. The result then sets no_walkable_network: true.

Read it and say so. An empty results there means there is nowhere to walk, not the area was searched and had no cafes — reporting the latter blames the neighbourhood for missing sidewalks. origin_component_junctions tells the two apart: it counts everything connected to the starting junction at any distance, so a rural road joins the whole state network and still reaches nothing in fifteen minutes (a place with no destinations), while a severed footway is a handful of junctions however far you ask (a place with no paths).

Fragmented pedestrian data is normal in OSM, so snapping prefers a junction with a real network behind it over a closer one that leads nowhere.

walk_loop

"I want to walk a mile in a loop." A router alone cannot answer this: the shortest path out and back is the same street twice — technically the distance asked for, and not what anyone meant. walk_loop spreads turnaround points around the origin (one per compass sector, from the reachable network itself), builds circuits through them, and rejects any circuit that retraces more than 25% of its own geometry. The thresholds were tuned against real extracts in #62: genuine loops retrace 0.09–0.19, a cul-de-sac subdivision scores 0.50 on every circuit it can build.

walk_loop area=colorado from_lat=39.7328 from_lon=-104.9790
          target_meters=1600

Give the length as target_meters (default 1,609 — a mile) or the time as target_minutes at 5 km/h; one or the other, not both. Cap is 10,000 m, about a two-hour walk.

Each loop reports the length it actually came out at. A neighbourhood may not contain a 2.0-mile loop, and 2.3 miles honestly labelled beats 2.0 miles claimed. Report meters/minutes from the result, never the target.

passing makes it a themed loop. Give it a Knowhere query and the circuit routes through what it names — the parks, the coffee shops:

walk_loop area=colorado from_lat=39.7328 from_lon=-104.9790
          target_meters=1600 passing="nw[leisure=park](area=colorado)"

Matches are bounded to the disc the loop can reach, snapped onto the path network (a match with no path within 150 m is not somewhere a walk can pass), and visited in a bearing sweep so the circuit circles rather than zigzags. All of them when the full circuit holds up — the nearest 8 at most — otherwise circuits through pairs, so one unreachable park costs itself and not the answer.

"None found" is a real answer. A cul-de-sac subdivision with one way in and out genuinely has no mile loop, and outside towns there is often no pedestrian network to make a circuit from. The result then carries none with the reason — quote it rather than retrying, and read no_walkable_network the same way as walk_reach's. Loops are foot-only, like reachability, and for the same reason.

Hard limits

Routing does not cross area boundaries. Each area's graph stops at its extract's edge, so a route into a neighbouring state returns NO_ROUTE even though both areas exist. Both endpoints must be in the same area. Straight-line tools have no such limit.

Coordinates snap to the nearest usable junction, and the response reports where they landed and how far that was (offset_m). "Usable" is per end: an origin needs somewhere to go, a destination something that can arrive — a coordinate metres from a one-way stub can land on a node you may drive out of but never into.

A far snap is refused. max_snap_meters defaults to 2000, and anything further returns NO_ROAD_NEARBY. Real street addresses measure tens of metres off, so this only catches points that are not in the area at all — and the error names another indexed area containing the point when there is one, which is usually the fix. Without it, a coordinate outside the area silently lands on whatever junction is nearest inside it and the trip is measured to somewhere you did not ask about.

Lower it (max_snap_meters: 100) to verify a coordinate really sits on a street; raise it for a genuinely remote destination — a lake centroid, a trailhead — or pass -1 for no limit. Either way an endpoint resolving further than 2 km comes back far_from_road: true, and any distance reported for it is to the nearest road, not to the place. drive_times reports both per row.

Two profiles: car (default) and foot. Separate networks, not one at a different speed: walking uses footways, paths, and steps no vehicle can, and ignores one-ways, so an urban walking route is often shorter than the drive. Walking is a flat 5 km/h (1.5 km/h on steps) and capped by max_walk_meters (default 10,000, max 25,000) because a pedestrian search is not hierarchical and its cost grows with distance covered. Not every area has a foot graph; one without returns NO_ROUTING_DATA for profile: "foot".

No cycling profile, no live traffic, no time-of-day variation — two calls for the same pair always return the same number. Turn restrictions are not modelled, so a route may occasionally take a signposted-illegal turn; travel times are unaffected at any meaningful scale.

Geometry

route returns the polyline only when include_geometry is true, because a long route is thousands of points. Points are {lat, lon} in travel order, following real road shape, and meters / miles are measured along it — the driving distance, not the straight line.

Which roads

Every route reply names the road each endpoint snapped onto, in from.road / to.road: OSM way id, name, ref (the route number, "I 70"), and highway (the class — motorway, primary, residential). It costs nothing extra, because the router resolved that edge in order to pick the junction.

Do not reconstruct this from search. A way is indexed by its bounding box, so ranking ways by distance to their box centre puts the middle of a kilometre of Main Street 500 m away while you are standing on it. Positional questions about the network go through the graph, which measures to the road's real shape.

route with steps: true returns the whole road log: the sequence of roads the trip runs along, in order.

"steps": [
  { "ref": "US 6", "name": "West 6th Avenue Freeway", "highway": "motorway",
    "at_km": 2.2, "km": 15.2 },
  { "ref": "I 70",  "highway": "motorway", "at_km": 17.4, "km": 46.2 },
  { "ref": "US 40", "highway": "primary",  "at_km": 63.6, "km": 42.1,
    "via": "exit 232" }
]
  • Run-length encoded. Consecutive stretches of the same road are one entry. A road is "the same" when it shares a route number or a name — I-70 is tagged unnamed for most of its length and "Veterans Memorial Tunnel" for 230 m of it, and that is not a change of road.
  • Slip roads are folded in. A *_link way belongs to the road it delivers you to, and its distance is credited there, so "US 40 at 63.6 km" is the point the interstate was left.
  • Steps tile the route. The last at_km plus its km is the trip length; nothing is sampled and nothing is dropped.
  • via is the motorway junction, where there is one — usually an exit number. Most roads change at an ordinary intersection and carry none.
  • This is not turn-by-turn. No turn directions, no angles, no lane tags. It is a log of roads, not instructions for driving them.

from.road and the first step can disagree, and both are right: they answer different questions. A coordinate at the corner of 14th and Bannock snaps to the junction the two share, so from.road names whichever of them the coordinate sits closest to, while step one names the road the trip drives away along.

It costs roughly half a route again, so it is opt-in. It is still far cheaper than the alternative it replaces: sampling the polyline every couple of kilometres and running a radius query per sample is dozens of queries, and the ones that hit a long way get the wrong label for the bounding-box reason above.

Errors

NO_ROUTING_DATA, NO_ROUTE, NO_ROAD_NEARBY, NO_ISOCHRONE, REACH_TOO_LARGE, BEYOND_RANGE — see knowhere://docs/errors for what each means and how to correct it. In drive_times, an unreachable destination is flagged on its own row (reachable: false with a reason) rather than failing the call.