Builder docs

Raster Basemap Archiver

Archive XYZ/WMTS imagery into a PMTiles basemap, with resumable downloads and hybrid planet + region pyramids.

By · Updated

Overview

geolith raster downloads a tile pyramid from any {z}/{x}/{y} HTTP endpoint and packs it into a PMTiles v3 archive with content deduplication. It exists to produce the imagery layer that drapes over terrain archives: one self-hosted file instead of a runtime dependency on someone else's tile server.

geolith raster \
  --url-template 'https://tiles.example.com/{z}/{x}/{y}.jpg' \
  --output ./data/basemap.pmtiles \
  --min-zoom 0 --max-zoom 8 \
  --format jpeg \
  --attribution '© Imagery provider'
FlagDefaultPurpose
--url-templaterequiredTile URL with {z}, {x}, {y} placeholders
--outputrequiredOutput PMTiles archive path
--min-zoom / --max-zoom0 / 8Zoom range
--bboxnoneRestrict coverage to min_lon,min_lat,max_lon,max_lat
--bbox-min-zoom0First zoom at which --bbox applies (see hybrid pyramids)
--formatjpegPayload type recorded in the archive: jpeg, png, or webp
--concurrency8Parallel download workers
--request-delay-ms50Delay after each request on every worker
--attempts5Download attempts per tile before the build fails
--cache-dir<temp>/geolith-rasterResumable tile cache
--name / --attributionraster / noneArchive metadata

Placeholder order is free-form, so row-major WMTS endpoints work unchanged:

--url-template 'https://server.example.com/wmts/1.0.0/layer/default/g/{z}/{y}/{x}.jpg'

Hybrid pyramids

A full-planet archive is a zoom-budget problem — each level quadruples the tile count:

DepthTilesApproximate size
z0–z887K1–2 GB
z0–z101.4M20–30 GB
z0–z1222M350+ GB

--bbox-min-zoom resolves this: below it the whole planet is fetched, at and above it only the bounding box. One archive then holds a spinnable globe plus one deep-zoom region.

geolith raster \
  --url-template 'https://tiles.example.com/{z}/{x}/{y}.jpg' \
  --output ./data/planet-raster.pmtiles \
  --min-zoom 0 --max-zoom 12 \
  --bbox 68.0,6.5,97.5,37.2 \
  --bbox-min-zoom 9

Planet to z8 plus India to z12 is ~260K tiles (~4 GB) instead of the 22M a uniform z12 planet would need.

The archive header declares the extent the pyramid genuinely covers, not the --bbox value: a hybrid archive holds planet tiles below the switch zoom, so it reports world bounds. Only when --bbox-min-zoom is at or below --min-zoom — meaning every zoom is clipped — does it report the bbox. Clients that skip tiles outside the declared bounds therefore still request the global low-zoom tiles.

Because world bounds alone cannot express where the deep zooms live, clipped archives also record the pyramid shape in their metadata:

KeyMeaning
geolith:bboxmin_lon,min_lat,max_lon,max_lat the deep zooms cover
geolith:bbox_min_zoomFirst zoom at which that clipping applies

Both are omitted for unclipped planet archives. Read them with pmtiles show <archive> to tell which zoom levels are global and which are regional without knowing the original build flags.

Resumability

Every tile is written to --cache-dir before packing, so an interrupted run resumes by re-running the same command — cached tiles are skipped and only the remainder is fetched. Coordinates the upstream has no tile for are recorded as misses, so they are not retried on later runs either. Re-running a completed build re-packs from cache in seconds without touching the network.

Choosing a source

Tile endpoints are not automatically redistributable. Before archiving a provider's imagery into a file you will host, confirm their licence permits re-hosting:

  • Suitable: sources published under CC-BY (attribution preserved in archive metadata via --attribution), public-domain government imagery, or imagery you hold a redistribution licence for.
  • Not suitable: most commercial basemap APIs, whose terms prohibit bulk download and redistribution — including cases where the tiles are publicly reachable without a key.

Licences also vary between layers of the same service, so check the specific layer rather than the provider.

Rendering

map.addSource('basemap', {
  type: 'raster',
  url: 'pmtiles://https://example.com/planet-raster.pmtiles',
  tileSize: 256,
});
map.addLayer({ id: 'basemap', type: 'raster', source: 'basemap' });

Draped over a terrain archive, the two combine into a 3D basemap:

map.setTerrain({ source: 'terrain', exaggeration: 1.25 });

Serving works through tileserver-rs or any PMTiles host — the archive declares its raster tile_type with no tile compression, per the PMTiles v3 spec.