astray.to

the bake · 7 min read

Teaching a computer what a road feels like.

Every navigation dataset can tell you how long a road takes. Almost nothing tells you what it is like to drive: whether it winds through redwoods or past a rail yard, climbs a ridge or crawls between stoplights. We build that data ourselves, ahead of time, for entire regions at once. We call the process the bake.

in plain words

The homework happens before you ask.

The raw ingredients are public. OpenStreetMap is the free, volunteer-built map of the world, and it is remarkably rich: alongside the roads themselves it records forests, coastlines, lakes, farmland, industrial areas, mountain peaks, stop signs, and speed limits. Add public elevation data and you have everything needed to describe the character of a road. The trouble is that it sits scattered across millions of records no one could consult while you wait for a route.

So we do the reading in advance. The bake ingests a region, a state at a time, and condenses all of it down to a report card stapled to every stretch of road: fourteen small measurements, one byte each. How bendy, how steep, how close to water, how far from industry. When you later ask for a route, the engine never touches raw map data at all; it reads report cards, millions of them, at arithmetic speed.

What the fourteen measurements are, and why we picked those fourteen. That story has its own article. The scenery model
the pipeline

From map dump to graded graph.

A bake runs region by region, in stages. Each stage is restartable and its output inspectable, because a full multi-state bake is measured in hours and you do not want to redo hours over a typo.

  • Fetch. Pull the region's OpenStreetMap extract, a compressed dump of everything mapped there, plus the elevation tiles covering it.
  • Parse. Read out the road network, and separately index every landscape feature the scorers will need: coastline and bays, lakes and rivers, forests, farmland and vineyards, industrial and military land, built-up areas, tagged peaks. Traffic signals, stop signs, and legal turn restrictions come along too.
  • Build the graph. Split each mapped road at every intersection into directed segments, the "edges" routing actually works on. Each edge gets a travel time derived from speed limits and road class, and a durable identity: which mapped way it came from and which piece of it it is. That stable identity is what lets loop routing recognize "same road, other direction," and what your device's private familiarity data attaches to.
  • Score. Run all fourteen signal producers over every edge, consulting the landscape indexes and the elevation model. A forest scorer measures overlap with woodland polygons; the gradient scorer reads the terrain under the road; and so on. The raw outputs are precise decimal numbers.
  • Quantize. Rank each measurement against every other road in the region and store it as a single byte, 0 to 255, with the extreme tails clamped. Ranking rather than storing raw values keeps regions comparable, so "very bendy for California" and "very bendy for Nevada" land in the same range. Fourteen bytes per edge is also what keeps the whole thing small enough to run in a browser.
two artifacts

One copy to inspect, one copy to fly.

The bake writes its results twice, deliberately. The first copy is a columnar feature store (Parquet): every edge, every signal, queryable with ordinary data tools. Because each signal lives in its own named column, adding a fifteenth measurement or fixing a bug in one scorer means recomputing one column, not the world. It is also where we debug. When a route does something odd, we can pull the actual per-road scores it saw.

The second copy is the graph blob: the same data packed into a single dense binary the routing engine can load whole and read in place, with no parsing step. One integrity-checked file holds the adjacency lists, coordinates, travel times, the fourteen signal columns, road geometry, stable identities, and turn restrictions. The blob is disposable by design: it is a pure function of the map extract and the scorer version, so "fix the scorer" or "take a fresh map" just means bake again.

tiling

Loading a trip instead of a state.

A whole state in one blob works on a server and dies in a browser: California alone packs to about 792 MB. So the final bake stage cuts each region into tiles on a fixed global quadtree. The grid keeps subdividing wherever the road network is dense, until each leaf holds a bounded number of edges. Every tile is a self-contained mini graph blob, addressed by a stable name that any build of any region agrees on.

The subtle part is the seams. A tile's manifest lists its topological neighbors, the tiles it actually shares border road-nodes with rather than just the ones adjacent on the grid, and border nodes carry their global OpenStreetMap identity. That is what lets the engine stitch a corridor of tiles into one continuous graph at query time, and it is why California and Nevada, baked independently on different days, still route correctly across the state line: a measured Truckee-to-Reno route comes out identical from separate builds and a combined one.

The payoff is the difference between loading a state and loading a trip: an SF-to-Sacramento request touches 41 tiles, about 271 MB, instead of the full 792 MB blob. A projected US-plus-Canada graph at roughly 8.7 GB is the number that makes tiling mandatory rather than nice-to-have. The same tiles, fetched from the same public mirror, feed the server and the on-device engine alike.

Why the engine loads tiles whole and does no I/O of its own, and how that one rule turns privacy tiers into a config flag. Architecture and privacy

The best way to evaluate a router is to drive it.

There is no account and no tracking. Open it, pick somewhere you've been meaning to go, and give it twenty spare minutes.