# The routing part

> The byte layout of the routing part of a .glx file: a 64-byte routing header, a tile index, an optional zstd dictionary, and each graph tile stored as fixed 64 KiB zstd blocks behind a block directory, read with range requests. Specified in the .glx spec, chapter 10.

The routing part is part of the [.glx specification](/glx/spec): the file header and the parts table are in [section 2.6](/glx/spec#_26-file-header-and-parts), the routing part in [chapter 10](/glx/spec#_10-routing-part). This page summarizes them; the specification is the source.

**Status: the format is done and geolith's builder writes it** (`geolith --output-format routing`). The router reads it block by block. Routing is not deployed for public use; see [Status and roadmap](/routing/status).

## A .glx file holds parts

A `.glx` file starts with a 256-byte file header: the magic `GLX0`, the version `1`, the part count (`1` to `7`) and a table of up to seven 32-byte part entries. Each entry names the part's kind in four ASCII bytes, its byte range in the file (offset and length, `u64`) and an XXH3-64 checksum (seed `0`) of its bytes. A file holds at most one part of each kind.

| Kind   | Part                                          |
| ------ | --------------------------------------------- |
| `SRCH` | The search part: the geocoding index          |
| `ROUT` | The routing part: road graph tiles, this page |

- **A reader reads only its own part.** A router looks up the `ROUT` entry and ignores the rest; a search reader looks up `SRCH`. A reader refuses a file that has no part of its kind, so renaming a file cannot make a reader use the wrong data: the file header says what each file holds.
- **Parts can be split or merged.** Offsets inside a part are counted from the start of the part, so a part's bytes are the same in any file. The first part starts at byte `256`; a search part, when present, comes first. `glx-parts merge` joins the parts of several files into one, and `glx-parts info` lists a file's parts and checks their checksums.
- **Checksums are checked when parts are copied.** A tool that copies a part checks its XXH3-64 checksum. A reader serving queries over HTTP does not read whole parts and does not check it.

## Layout of the routing part

| Offset in the part   | Size         | What                                                                                     |
| -------------------- | ------------ | ---------------------------------------------------------------------------------------- |
| `0`                  | 64 bytes     | Routing header: kind `ROUT`, block size, features, tile count, section table             |
| `64`                 | 16 × T bytes | Tile index: per tile, its key, tile length, first frame length and stored length (`u32`) |
| after the index      | 0 or more    | Tile dictionary: a zstd dictionary for every block frame, empty when none is used        |
| after the dictionary | the rest     | Tiles: per tile, its block directory, then its block frames, in index order, no gaps     |

The routing header is 64 bytes: kind (`ROUT`, 4 bytes), block size `B` (`u32`), features (`u32`), tile count `T` (`u32`), then the section table. The block size is a power of two from 4 KiB to 16 MiB; geolith's builder writes 64 KiB. The section table holds three (offset, length) pairs of `u64`, counted from the start of the routing part: tile index, tile dictionary, tiles. No feature bit is defined: a writer writes `0` and a reader refuses a part that sets any bit. The header, index and dictionary sit back to back, so one range request fetches all three.

**Tile keys.** Tiles sit on three grids, the levels: level `0` of 4° cells, level `1` of 1° cells and level `2` of 0.25° cells, each covering the whole globe with cells numbered row by row from the south-west. A tile's key is the `u32` `level | cell << 3`, the low 25 bits of the tile format's graph id. The index is sorted by key with no key twice, so tiles are ordered by level, then cell.

**Offsets are running sums.** The index stores lengths, not offsets. A tile's offset in the tiles section is the sum of the stored lengths before it; a reader computes these sums once, when it opens the part.

**A tile.** A tile of `L` bytes has `N = ceil(L / B)` blocks. It is stored as its block directory (`N` `u32` frame lengths), then its `N` block frames. Block `b` holds tile bytes `b × B` up to `(b + 1) × B`; each is one zstd frame, compressed with the tile dictionary when the dictionary is not empty. The blocks together are one graph tile in the graph tile format of Valhalla 3.9.1: a 272-byte tile header, then the cell's nodes, directed edges, edge shapes and names, access rules and turn restrictions. The specification does not repeat that layout; the `geolith-routing` crate is the reference codec.

**Why blocks.** A Cloudflare Worker has 128 MB per isolate, and Berlin's largest tiles are 47 MB and 53 MB. With one frame per tile, 87 of 100 test routes ran out of memory. With 64 KiB blocks, the router reads only the records a route touches. The specification's section 10.4 gives the measurements.

## Reading

1. Fetch the 256-byte file header and find the `ROUT` entry.
2. Fetch the routing header, the tile index and the dictionary in one range request. Check the kind (`ROUT`), the block size, that no feature bit is set, and the index. Keep the index and the dictionary in memory.
3. For each tile a request needs, find its key in the index (binary search) and fetch its head: the block directory and the first block, which are adjacent. Block `0` holds the tile header and so the offset of every section. A key that is not in the index means the cell has no roads.
4. To read a record, fetch and decompress the blocks that hold it. A reader may drop blocks and fetch them again later, and may merge the frames of adjacent blocks into one range request.

A reader treats every tile as untrusted input. A record that points outside the tile, or past the end of a section, is refused, never read in part.
