Knowhere

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);