# Raster Basemap Archiver

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

## 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](/builder/guides/terrain-3d): one self-hosted file instead of a
runtime dependency on someone else's tile server.

```bash
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'
```

| Flag | Default | Purpose |
| --- | --- | --- |
| `--url-template` | required | Tile URL with `{z}`, `{x}`, `{y}` placeholders |
| `--output` | required | Output PMTiles archive path |
| `--min-zoom` / `--max-zoom` | 0 / 8 | Zoom range |
| `--bbox` | none | Restrict coverage to `min_lon,min_lat,max_lon,max_lat` |
| `--bbox-min-zoom` | 0 | First zoom at which `--bbox` applies (see hybrid pyramids) |
| `--format` | `jpeg` | Payload type recorded in the archive: `jpeg`, `png`, or `webp` |
| `--concurrency` | 8 | Parallel download workers |
| `--request-delay-ms` | 50 | Delay after each request on every worker |
| `--attempts` | 5 | Download attempts per tile before the build fails |
| `--cache-dir` | `<temp>/geolith-raster` | Resumable tile cache |
| `--name` / `--attribution` | `raster` / none | Archive metadata |

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

```bash
--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:

| Depth | Tiles | Approximate size |
| --- | --- | --- |
| z0–z8 | 87K | 1–2 GB |
| z0–z10 | 1.4M | 20–30 GB |
| z0–z12 | 22M | 350+ 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.

```bash
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:

| Key | Meaning |
| --- | --- |
| `geolith:bbox` | `min_lon,min_lat,max_lon,max_lat` the deep zooms cover |
| `geolith:bbox_min_zoom` | First 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

```js
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:

```js
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.
