Runtime Recipes
Worked scripts for the runtime tool. The API they use is
knowhere://docs/runtime.
Query, projected down
const restaurants = query.execute("nw[amenity=restaurant](area=san_francisco)");
export const payload = restaurants.map((r) => ({
name: r.name(),
lat: r.asPoint().lat(),
lon: r.asPoint().lon(),
}));
Union of several queries
const all = query.union(
"nw[amenity=cafe](area=seattle)",
"nw[amenity=bakery](area=seattle)",
);
export const payload = { count: all.length };
Distance from a set's centroid
const pois = query.execute("n[tourism=museum](area=paris)");
const center = pois.length > 0
? geo.asBounds(...pois.map((p) => p.asBound())).asBound().center()
: null;
export const payload = center
? pois.map((p) => ({
name: p.name(),
distanceMeters: geo.distance(p.asBound(), center.asBound()),
}))
: [];
Deduplicating nearby points
One representative per cluster of points within radiusMeters of each other:
function cluster(results, radiusMeters) {
const kept = [];
for (const entry of results) {
const bound = entry.asBound();
const isDupe = kept.some((k) => geo.distance(bound, k.asBound()) < radiusMeters);
if (!isDupe) kept.push(entry);
}
return kept;
}
const costcos = cluster(query.execute("nwr[name=~Costco](area=colorado)"), 5000);
export const payload = costcos.map((r) => r.name());
Nearest neighbour in another set
For plain "find A with a B within N metres", prefer the near tool — it
joins server-side and is far cheaper than pulling both sets into the runtime.
Reach for this form only when you need extra computation around it:
const schools = query.execute("nwr[amenity=school][name](area=colorado)");
const cafes = query.execute("nwr[amenity=cafe][name!~Starbucks](area=colorado)");
export const payload = schools.map((school) => {
const sb = school.asBound();
let best = null;
for (const cafe of cafes) {
const d = geo.distance(sb, cafe.asBound());
if (best === null || d < best.meters) best = { cafe, meters: d };
}
return { school: school.name(), cafe: best ? best.cafe.name() : null };
});
Where three things coincide
"Within a short drive of a Costco, and a walk of a school and a coffee shop" — a join of three result sets at three different radii, which no tool expresses. Anchor on the rarest set: it decides how many candidate centres there are, and everything else is a distance check against it.
const wanted = [
{ radius: 5000, results: query.execute("nwr[name=~Costco](area=colorado)") },
{ radius: 1250, results: query.execute("nwr[amenity=school][name](area=colorado)") },
{ radius: 1250, results: query.execute("nwr[amenity=cafe][name][name!~Starbucks](area=colorado)") },
];
wanted.sort((a, b) => a.results.length - b.results.length);
const [anchor, ...rest] = wanted;
const found = [];
for (const centre of anchor.results) {
const bound = centre.asBound();
// Nearest member of each other set, bailing as soon as one is missing.
const matches = [];
for (const other of rest) {
let best = null;
for (const entry of other.results) {
const meters = geo.distance(bound, entry.asBound());
if (meters <= other.radius && (best === null || meters < best.meters)) {
best = { name: entry.name(), meters: Math.round(meters) };
}
}
if (best === null) break;
matches.push(best);
}
if (matches.length === rest.length) {
found.push({ centre: centre.name(), lat: centre.asPoint().lat(), lon: centre.asPoint().lon(), matches });
}
}
export const payload = found;
Two deliberate limits: geo.distance is straight-line, so tighten the radius
or confirm survivors with walk_reach; and it returns names, because an
exhaustive GeoJSON of every match is the usual way to hit
PAYLOAD_TOO_LARGE.
Ranking by travel time
The reason routing is in the runtime and not only in the tools — a query, a join, and a sort in one place:
const home = geo.point({ lat: 41.14, lon: -104.8202 });
const stores = query.execute("nw[shop=supermarket](area=wyoming)");
// One call, not one per store.
const seconds = routing.times("wyoming", home, stores.map((s) => s.asPoint()));
export const payload = stores
.map((store, i) => ({ name: store.name(), seconds: seconds[i] }))
.filter((s) => s.seconds !== null) // unreachable, not "zero"
.sort((a, b) => a.seconds - b.seconds)
.slice(0, 10);
On wyoming this ranks a supermarket 2.1 mi away ahead of one 1.9 mi away —
3.1 minutes against 5.2. For walkability pass { profile: "foot" }.
Every area at once
const counties = query.areas().flatMap((area) =>
query
.execute(`nwr[admin_level=6][boundary=administrative][name](area=${area.name})`)
.map((county) => {
const centre = county.asBound().center();
return { name: county.name(), area: area.name, lat: centre.lat(), lon: centre.lon() };
})
);
export const payload = counties;
One query per area, so guard the loop with budget.remainingMs() and return
what you have.
A walking loop of a given length
The walk_loop tool does this now — including themed loops via its
passing parameter ("a mile loop past the parks") and time targets via
target_minutes. Reach for this script only to build a variant the tool
does not offer: different thresholds, custom turnaround scoring, more than
two turnarounds per circuit.
A router finds the shortest path, so "walk out a mile and come back" returns the same street twice. A real loop needs turnarounds spread around you and a test that rejects circuits which double back.
const area = "colorado";
const origin = geo.point({ lat: 39.7328, lon: -104.9790 });
const target = 3200; // metres; 2 miles
function retrace(legs) {
// Hash consecutive point pairs and intersect. Undirected, so the same
// street walked back still collides — this is what separates a loop from
// an out-and-back.
const seen = {};
let shared = 0, total = 0;
for (const leg of legs) {
for (let i = 1; i < leg.geometry.length; i++) {
const a = leg.geometry[i - 1], b = leg.geometry[i];
const key = [
Math.min(a.lat(), b.lat()).toFixed(6), Math.min(a.lon(), b.lon()).toFixed(6),
Math.max(a.lat(), b.lat()).toFixed(6), Math.max(a.lon(), b.lon()).toFixed(6),
].join(",");
total++;
if (seen[key]) shared++; else seen[key] = true;
}
}
return total === 0 ? 1 : shared / total;
}
function loops(radius) {
const reached = routing.reach(area, origin, { maxWalkMeters: radius });
if (!reached) return [];
// One turnaround per compass sector, so they spread around the origin
// instead of clustering down whichever street is longest.
const sectors = {};
for (const n of reached) {
if (n.meters < radius * 0.7) continue; // too near: a stunted loop
const deg = (Math.atan2(n.point.lon() - origin.lon(),
n.point.lat() - origin.lat()) * 180) / Math.PI;
const s = Math.floor((((deg % 360) + 360) % 360) / 45);
if (!sectors[s] || n.meters > sectors[s].meters) sectors[s] = n;
}
const spread = Object.keys(sectors).map((k) => sectors[k]);
const out = [];
const opts = { profile: "foot", geometry: true, maxWalkMeters: target };
for (let i = 0; i < spread.length; i++) {
for (let j = i + 1; j < spread.length; j++) {
const a = routing.route(area, origin, spread[i].point, opts);
const b = routing.route(area, spread[i].point, spread[j].point, opts);
const c = routing.route(area, spread[j].point, origin, opts);
if (!a || !b || !c) continue;
const ratio = retrace([a, b, c]);
if (ratio > 0.25) continue; // an out-and-back wearing a loop's clothes
out.push({
meters: Math.round(a.meters + b.meters + c.meters),
minutes: Math.round((a.minutes + b.minutes + c.minutes) * 10) / 10,
retrace: Math.round(ratio * 100) / 100,
});
}
}
return out;
}
// A circuit through two turnarounds has THREE legs, so the reach radius is a
// third of the target, not a half. Widen only if nothing survives.
let found = loops(target / 3);
if (found.length === 0) found = loops(target / 2.4);
found.sort((x, y) => Math.abs(x.meters - target) - Math.abs(y.meters - target));
export const payload = found.length ? found.slice(0, 5) : { none: "no loop of that length here" };
The two thresholds were tuned against real extracts: target/3 lands within
2 % where target/2 overshoots by 10–20 %, and genuine loops retrace
0.09–0.19 where a cul-de-sac subdivision scores 0.50 on every circuit it can
build. "None found" is the right answer in a subdivision with one way in
and out, and outside towns there is often no pedestrian network to make a
circuit from at all. Cost is about 1 s at a 1,600 m target, 6 s at 3,200 m.
Report the length it actually came out at, never the length that was asked for. 2.3 miles honestly labelled beats 2.0 miles claimed.
To route the loop through query results instead of bare turnarounds (what
walk_loop's passing does), remember Result fields are methods in
the runtime — r.name(), r.asPoint() — and snap each match with
routing.snap() first: a big park's centroid sits deep inside the
polygon, and the snapped point is the entrance the path network actually
reaches.
Tag vocabulary in an area
Prefer the tag_values tool — it is cheaper. This form is for combining it
with other computation:
nwr[name](area=colorado) matches every named feature in the state, which is
well past the 50,000-row cap and returns TOO_MANY_RESULTS. Scope it to the
part you are actually asking about:
const entries = query.execute(
"nwr[name](area=colorado)(around=5000,39.7392,-104.9903)",
);
export const payload = Object.entries(entries.tagCount())
.sort(([, a], [, b]) => b - a)
.slice(0, 20);
Related
knowhere://docs/runtime— the API these use.knowhere://docs/globals— TypeScript type declarations.knowhere://docs/routing— whatrouting.*measures, and its limits.