Tile layer
An item that wraps a pre-rendered tile container (PMTiles) or a raw raster (GeoTIFF / COG / JP2), served by range request.
intermediateA tile layer is an item that wraps either a pre-rendered tile container or a raw raster. Use for basemaps backed by your own cache, big raster overlays that would be too heavy to render from features on the fly, vector tiles from a curated dataset, or georeferenced imagery (aerial photos, scanned maps, elevation models).
At-rest formats: PMTiles + COG
The portal stores tile-layer bytes in one of two formats:
- PMTiles is the steady-state serving format for every tile layer. CC0 spec, single file with an embedded directory, range-readable over HTTP. MinIO serves it directly with no per-tile compute; MapLibre's pmtiles protocol plugin reads it from the browser.
- COG (Cloud-Optimized GeoTIFF) is the bridge format for raster uploads. Same shape (single file, range-served), read by MapLibre's cog-protocol plugin. Items live in COG state until a background worker bakes a PMTiles raster pyramid; then they flip to PMTiles automatically and the COG stays as the archival source.
Accepted upload formats
Pre-tiled containers (immediate PMTiles serving):
- **
.pmtiles**: pass-through, no conversion. - **
.mbtiles**: converted to PMTiles via thepmtilesCLI on upload. The SQLite directory is read once and re-packed. - **
.zipof an XYZ tile directory** ({z}/{x}/{y}.{ext}). Unzipped, thenpmtiles convert <dir>walks the structure and packs the archive.
Raw raster (immediate COG serving, then PMTiles in the background):
- **
.tif/.tiff/.geotiff**: GDAL'sgdalwarp -t_srs EPSG:3857 -of COGreprojects (if needed) and normalizes to a valid COG. Lossless DEFLATE compression by default. - **
.cog**: same path as a regular GeoTIFF; the.cogextension is a hint, not a contract, so the converter still normalizes the output to a guaranteed-valid COG. - **
.jp2**: GDAL reads JPEG 2000 via the OpenJPEG driver and emits the same COG output.
The portal records the original format and filename on the item so you can see what you uploaded vs. what's stored.
The COG-then-PMTiles bridge
When you upload a raw raster, the item is map-renderable within seconds:
- Upload + COG conversion (synchronous, seconds to minutes). GDAL normalizes to a COG and stores it in MinIO. The item's
processingStateis set tocog-readyand the map starts rendering immediately via the cog-protocol plugin. - Background pyramid build (asynchronous, minutes to hours). The portal-worker container picks up the item, runs
gdal2tiles.pyto produce a tile pyramid, packs it into PMTiles, and uploads. State flips topmtiles-readyand the item's served format silently switches to PMTiles. The COG stays in MinIO as the archival source. - On failure, the item flips to
tiling-failedwith the error message surfaced on the detail page. The COG continues to serve. Click Retry pyramid build to re-queue the job.
You can see the current state on the tile layer's detail page; a status block above the metadata card shows queued / building / ready / failed with timestamps.
TPK / TPKX / ECW / MrSID
Esri's TPK and TPKX bundles aren't accepted at upload today. They wrap PNG/JPG tiles in a custom index format that needs its own extraction pipeline. Documented as future scope.
ECW and MrSID aren't accepted either: their decode SDKs are proprietary and the licenses aren't AGPL-compatible. Convert to GeoTIFF locally with a GDAL build that includes the vendor SDK, then upload the .tif. The error message in the uploader includes the conversion recipe.
Raster vs. vector content
PMTiles can store either; the portal reads the file header at upload to determine which kind you have. Raster tile layers become raster MapLibre sources; vector tile layers become vector sources and can be styled like any other vector data.
COG items are always raster.
Pre-upload space check
When you pick a file, the portal first asks the api how much working space the upload + conversion pipeline needs (typically 2.5x the file size for raw rasters, 1.5x for pre-tiled inputs). If the host doesn't have headroom, the uploader refuses up front with a user-readable message naming the required and available sizes. Beats discovering ENOSPC after gigabytes of bytes have already transferred.
Using a tile layer as a basemap
The detail page surfaces a Use as basemap action that pre-fills a new basemap item with kind: tile-url and the tile layer's internal URL:
- PMTiles items:
pmtiles://<api-base>/api/portal/tile-layer/<id>/file - COG items:
cog://<api-base>/api/portal/tile-layer/<id>/file
Both protocols are wired into the portal's MapLibre setup; basemap rendering picks the right one based on the URL prefix.
What's stored on the item
- storageKey / storageUrl of the currently-served file (the PMTiles when
formatispmtiles, the COG whenformatiscog). - cogStorageKey / cogStorageUrl / cogSizeBytes for raster uploads. Stays populated even after pyramid build so you can re-tile later without re-upload.
- pmtilesStorageKey / pmtilesStorageUrl / pmtilesSizeBytes once the pyramid job lands.
- processingState (cog-ready / tiling / pmtiles-ready / tiling-failed) and related timestamps + error message.
- format (
pmtilesorcog), kind (rasterorvector), bbox, min/max zoom, suggested center. - tileType (mvt / png / jpg / webp / avif) for PMTiles items;
pngfor COG items. - attribution string from the file header, when set.
Notes
- No re-tile on data change. PMTiles archives are pre- rendered; updating the source means re-uploading. Raster items can be re-uploaded; the cog → pmtiles bridge runs again on the new bytes.
- Bounds enforce themselves. Tiles outside the bounding box in the file header simply don't exist; MapLibre stops asking for them.
- Both files retained. A raster item that's completed its pyramid build keeps both the COG (archival source) and the PMTiles (served bytes). Disk cost is ~2x the original upload; both are useful, so we don't auto-delete.