Tile Format
Calculon reads Valhalla's binary graph tile format. This page describes how tiles are structured, loaded, and cached.
Hierarchy levels
The road network is split into three hierarchy levels:
| Level | Tile size | Coverage | Road importance |
|---|---|---|---|
| 0 | 4° | ~440 km × 310 km | Motorway, trunk |
| 1 | 1° | ~110 km × 78 km | Primary, secondary |
| 2 | 0.25° | ~28 km × 19 km | All roads including local |
Higher-level tiles (0, 1) duplicate important roads from lower levels, creating shortcuts that let the router skip over local streets during long-distance searches.
GraphId
Every node, edge, and tile is addressed by a GraphId — a single u64 packing:
| Field | Bits | Range | Description |
|---|---|---|---|
| Level | 3 | 0-7 | Hierarchy level |
| Tile ID | 22 | 0-4M | Tile index at that level |
| ID | 21 | 0-2M | Node or edge index within the tile |
Edge IDs are formatted as level/tileid/id in API responses (e.g., 2/751073/456).
Tile structure
Each tile is a binary file containing, in order:
┌─────────────────────────┐
│ TileHeader │ Fixed size: tile metadata, counts, offsets
├─────────────────────────┤
│ NodeInfo[] │ 40 bytes each: position, edge index, access, density
├─────────────────────────┤
│ NodeTransition[] │ Transitions to other hierarchy levels
├─────────────────────────┤
│ DirectedEdge[] │ 24 bytes each: endnode, speed, classification, access
├─────────────────────────┤
│ EdgeInfo[] │ Variable length: street names + encoded shape
├─────────────────────────┤
│ Spatial bins │ 5×5 grid for fast candidate edge lookup
└─────────────────────────┘
NodeInfo (40 bytes)
- Latitude/longitude (encoded as offsets from tile base)
- Edge index and count (which directed edges start here)
- Access mask (which travel modes can traverse)
- Density (0-15, traffic density for turn cost scaling)
- Admin index, traffic signal flag, drive-on-right flag
- Per-edge headings (compressed, for turn angle computation)
- Transition count (number of edges crossing to other levels)
DirectedEdge (24 bytes)
Bit-packed fields across 6 words:
- End node GraphId
- Speed (kph), length (meters)
- Road classification, surface type, use type
- Forward/reverse access masks
- Shortcut flag, internal flag, toll, destination-only
- Turn type, stop impact, name consistency (per pair with predecessor)
- Opposing edge index (for traversal and per-pair data lookup)
EdgeInfo (variable)
- Encoded polyline shape (the geometry of the road segment)
- Street name list (zero or more null-terminated strings)
Spatial binning
Each tile contains a 5×5 grid of spatial bins (25 bins total). When snapping a coordinate to the nearest edge, the router:
- Determines which bin the coordinate falls in
- Checks edges in that bin and neighboring bins
- Projects the point onto each candidate edge
- Returns the closest match with snap fraction
This avoids scanning all edges in a tile.
Tile loading and caching
GraphReader manages tile loading with a three-level cache:
| Level | Storage | Latency | Synchronization |
|---|---|---|---|
| Thread-local | HashMap |
~20ns | None (per-thread) |
| Shared | RwLock<HashMap> |
~100ns | Read lock (parallel) |
| Disk | mmap (memory-mapped) |
OS page cache | None after map |
Tiles are memory-mapped using memmap2. The OS page cache handles eviction. The --cache-size option controls the shared cache capacity (default: 200 tiles).
Binary parsing safety
Tile parsing uses unsafe for zero-copy pointer access. Every unsafe block is:
- Bounds-checked against the tile header's declared sizes
- Validated that offsets don't exceed file length
- Documented with safety comments
- Only used on little-endian platforms (compile-time check)
Obtaining tiles
Calculon uses standard Valhalla tiles. To generate tiles from OpenStreetMap data:
# Using the included script (requires Docker)
make tiles
# Or manually with Valhalla
docker run -v /path/to/osm:/data ghcr.io/valhalla/valhalla:latest \
valhalla_build_tiles -c /data/valhalla.json /data/extract.osm.pbf
Tiles are written to the configured tile directory, organized in subdirectories by level and tile ID.