Skip to content

Runtime Map Bundle Builder

Use bin/build_map_bundles.py as the supported maintainer workflow for publishing map data bundles. The builder now produces country archives only:

  • ${COUNTRY_CODE}.tar.gz (one archive per selected ISO2 country)

<COUNTRY_CODE> stays ISO based (IN, FR, NL, and so on).

Shared map assets are no longer maintainer-published artifacts. They are prepared during backend image build from core-owned inputs and pinned upstream sources.

Required external inputs

bin/build_map_bundles.py requires:

  • A world basemap PMTiles source (--world-basemap)
  • A world terrain PMTiles source (--world-terrain)
  • A world countries GeoJSON file (--world-countries)

--world-basemap and --world-terrain accept either a URL or a local file path.

Build bundles (supported path)

From the repository root:

bin/build_map_bundles.py --country NL FR DE \
  --world-basemap /mnt/datasets/maps/world-basemap.pmtiles \
  --world-terrain /mnt/datasets/maps/world-terrain.pmtiles \
  --world-countries /mnt/datasets/maps/world-countries.geojson

Build every ISO code found in the world countries dataset:

bin/build_map_bundles.py --all \
  --world-basemap /mnt/datasets/maps/world-basemap.pmtiles \
  --world-terrain /mnt/datasets/maps/world-terrain.pmtiles \
  --world-countries /mnt/datasets/maps/world-countries.geojson \
  --jobs 8

This command:

  1. Extracts ISO country boundaries from the world countries file.
  2. Builds merged PMTiles artifacts for each selected country.
  3. Writes ${COUNTRY_CODE}.tar.gz for each selected country.

Default export directory is maps/dist/export (or --export-dir when provided).

Expected output layout:

maps/dist/export/
  NL.tar.gz
  FR.tar.gz
  ...

Archive contents:

  • ${COUNTRY_CODE}.tar.gz contains tiles.pmtiles, contours.pmtiles, and country.geojson.

Publish with rclone (manual maintainer flow)

Publishing is intentionally manual so maintainers can review exactly what is uploaded.

1) Install rclone

# Ubuntu / Debian
apt install rclone

# macOS
brew install rclone

2) Configure an S3-compatible remote

Create object-storage S3 credentials in your provider dashboard and note the access key and secret key.

Add a remote to ~/.config/rclone/rclone.conf (example for Hetzner Object Storage):

[hetzner-map-assets]
type = s3
provider = Other
access_key_id = <access_key>
secret_access_key = <secret_key>
region = hel1
endpoint = hel1.your-objectstorage.com

3) Upload country bundles to whitebox-maps.hel1.your-objectstorage.com

Use the same export directory that bin/build_map_bundles.py wrote to (maps/dist/export by default, or your --export-dir value).

EXPORT_DIR="maps/dist/export"
REMOTE_BUCKET="hetzner-map-assets:whitebox-maps"
COUNTRY_CODE="NL"

# Upload one country bundle
rclone copyto "$EXPORT_DIR/${COUNTRY_CODE}.tar.gz" "$REMOTE_BUCKET/${COUNTRY_CODE}.tar.gz" --progress --stats=1s --stats-one-line

# Upload all country bundles from export dir (keeps existing remote objects)
rclone copy "$EXPORT_DIR" "$REMOTE_BUCKET" --include "*.tar.gz" --progress --stats=1s --stats-one-line

Expected object layout:

whitebox-maps/
  NL.tar.gz
  FR.tar.gz
  ...

4) Verify object location and runtime URL mapping

REMOTE_BUCKET="hetzner-map-assets:whitebox-maps"
COUNTRY_CODE="NL"

rclone ls "$REMOTE_BUCKET/${COUNTRY_CODE}.tar.gz"
curl -I "${MAP_ASSETS_BASE_URL}/${COUNTRY_CODE}.tar.gz"

Runtime country bundle URLs should resolve to uploaded objects:

  • ${MAP_ASSETS_BASE_URL}/${COUNTRY_CODE}.tar.gz

Shared style/resources are handled separately by backend image build; maintainers publish country bundles only.

PMTiles tool bootstrap

  • bin/build_map_bundles.py uses PMTILES_BIN (if set) or pmtiles from PATH.
  • If neither is available, it attempts an automatic download for Linux/macOS on x86_64 and arm64.
  • On unsupported platforms, install pmtiles manually and set PMTILES_BIN (or add it to PATH).

Validate against dev runtime

Building and publishing bundle archives does not make compose use them automatically.

  1. Add 127.0.0.1 dev-server to /etc/hosts and verify ping dev-server works.
  2. Ensure ${MAP_ASSETS_BASE_URL} points at the storage path where you published country bundles.
  3. Set matching COUNTRY_CODE for the runtime.
  4. Start compose (docker compose -f compose.dev.yml up).
  5. Run backend and frontend as usual.

Shared style/resources in dev are prepared by backend image build from core-owned sources.

Style development notes

  • For Maputnik-based editing, run:
docker run -it --rm -p 8888:8000 ghcr.io/maplibre/maputnik:main