OpenRoadCode Map Builder

Source: tools/map_builder/README.md

Reproducible Debian-container workflow for generating the offline map and routing data consumed by OpenRoadCode.

It automates the previously manual chain: discover Geofabrik regions, download and validate OSM PBF extracts, merge selected regions when necessary, build MapLibre-compatible MBTiles with tilemaker, install the OpenRoadCode map style and offline glyphs, build Valhalla routing data, validate all generated artifacts, write a build manifest, and publish/deploy the result as a /srv/openroadcode dataset.

The recommended production model uses a dedicated map-build machine. The vehicle does not compile its own map data; it pulls validated datasets from the build machine with scripts/runtime/pull_navigation_data.sh. See Navigation Build and Deployment.

Toolchain

The toolchain is pinned in toolchain.lock to specific tilemaker, Valhalla, glyph, and Debian versions. A container engine is used only as a build/data-compilation environment; the OpenRoadCode runtime does not require one. The scripts use Docker by default. To use Podman, set CONTAINER_ENGINE=podman:

CONTAINER_ENGINE=podman ./scripts/build-image.sh
CONTAINER_ENGINE=podman ./scripts/run-builder.sh tui

Build image

cd tools/map_builder
./scripts/build-image.sh

Interactive region selector

./scripts/run-builder.sh tui

The runner bind-mounts the local builder/ and templates/ directories into the container, so Python, TUI, and style-template edits are available immediately. Rebuild the image only after changing the Dockerfile, toolchain versions, or container-installed dependencies.

Controls: Up/Down and PageUp/PageDown navigate, Right expands or collapses a region group, Left collapses or moves to its parent, Space selects, / searches, c clears the search, Enter accepts the selected regions, and q quits. b also accepts the selection. Parent/child region combinations are rejected to prevent duplicate map data.

The last accepted selection is stored in .cache/selected-regions.json. On the next run, regions that still exist in the current Geofabrik index are selected with [x], and their parent groups are expanded so they are visible. Quitting with q leaves the previous accepted selection unchanged.

Before starting an expensive build, the builder checks build-output/build-manifest.json. If the manifest contains the same selected Geofabrik regions and the existing generated output passes validation, the existing build is reused instead of rerunning tilemaker and Valhalla. Region order does not matter. Use --force when a fresh rebuild is intentional, for example to pick up newer OpenStreetMap source data.

./scripts/run-builder.sh tui --force

Non-interactive build

./scripts/run-builder.sh build --regions north-america/us/michigan

Multiple regions are comma separated:

./scripts/run-builder.sh build --regions north-america/us/michigan,north-america/us/ohio

Normal non-interactive builds also reuse matching validated output. Force a rebuild with:

./scripts/run-builder.sh build --regions north-america/us/michigan --force

After a successful interactive or non-interactive build, the builder reports the selected region names, their combined source PBF size, total deployable output size, elapsed build time, and output path. When an existing build is reused, it reports that result and prints the validation summary instead.

List known Geofabrik IDs with:

./scripts/run-builder.sh list

Generated output

The host build-output/ directory is mounted in the container as /srv/openroadcode, so generated file URLs and Valhalla paths are identical during validation and after deployment.

build-output/
├── build-manifest.json
├── maps/
│   ├── source/
│   ├── vector/openroadcode.mbtiles
│   ├── glyphs/
│   ├── search/openroadcode-search.sqlite
│   ├── styles/openroadcode.json
│   └── routes/
└── valhalla/
    ├── valhalla.json
    ├── admins.sqlite
    ├── timezones.sqlite
    ├── tiles/
    └── tiles.tar

maps/routes/ is runtime/debug space. Routes are sent dynamically to the native map renderer rather than generated as part of the base dataset. Vehicle-side deployment preserves this directory across dataset updates.

The canonical style name is openroadcode.json; runtime code should not depend on a region-specific filename.

Buildings use offline vector footprints and available height attributes, with a 3.66-metre fallback. Map-anchored directional lighting, vertical shading, and subtle height-based colors improve depth in the tilted 3D view. ORC applies light/dark building palettes when generating its runtime style. These effects require no textures, extra datasets, or network access; restart ORC after pulling style changes to regenerate the installed style.

Validation

Validation runs automatically after a build. It checks source PBFs with osmium, MBTiles SQLite integrity and required vector layers, style JSON and runtime sources, glyph presence, Valhalla databases/tiles/extract, an optional valhalla_service /status smoke test, and SHA-256 checksums for key artifacts.

Run validation again with:

./scripts/validate-host.sh

A dataset is not considered deployable without a validated build-manifest.json.

Publish on the map-build machine

The recommended vehicle-pull model publishes the latest validated dataset at /srv/openroadcode on the map-build machine:

./scripts/deploy-to-srv.sh

The deployment script refuses to install an output tree without a validated build-manifest.json. It synchronizes generated data into /srv/openroadcode while preserving maps/routes/ as runtime/debug space. The POI search index is deployed at the canonical runtime path maps/search/openroadcode-search.sqlite; legacy maps/poi/openroadcode-poi.sqlite outputs are migrated during deployment.

The vehicle can then preview and pull that dataset over SSH:

./scripts/runtime/pull_navigation_data.sh \
  --source mapbuilder@MAP_HOST:/srv/openroadcode \
  --dry-run

./scripts/runtime/pull_navigation_data.sh \
  --source mapbuilder@MAP_HOST:/srv/openroadcode

These commands are run from the OpenRoadCode repository on the vehicle, not from tools/map_builder on the build host.

SSH key authentication is recommended. For the pull model, the vehicle only requires read access to the published build-machine dataset; the build machine does not need privileged SSH access to the vehicle.

Optional push deployment

deploy-to-srv.sh --remote remains available for development or manually managed targets:

./scripts/deploy-to-srv.sh --remote openroad@192.168.1.50
make deploy REMOTE=openroad@192.168.1.50

For production vehicle updates, prefer the Pi-initiated pull workflow because it controls update timing, stages and validates the incoming dataset, retains the previous dataset, and can roll back after a failed Valhalla restart.

Cache and scratch data

.cache/ stores downloaded Geofabrik data, .scratch/ stores intermediate build data, and build-output/ contains only the deployable result. These directories are intentionally ignored by Git.

Tests

make test

The included tests cover Geofabrik region parsing/selection rules, MapLibre style installation/validation, and reuse detection for matching validated build output.

Attribution

Generated datasets are based on OpenStreetMap/Geofabrik data and use open-source tilemaker, Valhalla, and glyph assets. Downstream applications must preserve the applicable licenses and attribution requirements.