Skip to content

ras2cng.pmtiles

ras2cng.pmtiles

PMTiles generation for the ras2cng pmtiles CLI surface.

Vector tiles: - GeoParquet -> reproject to EPSG:4326 -> newline-delimited GeoJSON -> tippecanoe -> PMTiles

Raster tiles: - delegated to :mod:ras2cng.maplibre, which colorizes at native resolution, warps to an exact Web Mercator zoom, and builds the tile pyramid.

Note: tippecanoe, gdal, and pmtiles are command-line tools and must be installed separately and available on PATH.

:mod:ras2cng.maplibre is the maintained implementation of both jobs and streams its inputs; this module is the older single-file CLI surface kept for ras2cng pmtiles.

generate_raster_pmtiles(input_file, output, min_zoom=None, max_zoom=None, *, map_type='depth')

Render a numeric raster to PNG PMTiles via the maplibre pipeline.

This used to run gdal_translate -of MBTiles -co TILE_FORMAT=PNG directly on the source. Against a float32 depth or WSE COG that produces an 8-bit image with no color ramp and no alpha -- nodata renders as opaque black -- and it built no overviews at all, so the MINZOOM creation option had nothing to apply to.

:func:ras2cng.maplibre._render_raster_pmtiles is the correct implementation of this job: it applies the color ramp at native resolution before any downsampling, warps to an exact Web Mercator zoom resolution, and builds the pyramid.

Source code in ras2cng/pmtiles.py
def generate_raster_pmtiles(
    input_file: Path,
    output: Path,
    min_zoom: Optional[int] = None,
    max_zoom: Optional[int] = None,
    *,
    map_type: str = "depth",
):
    """Render a numeric raster to PNG PMTiles via the maplibre pipeline.

    This used to run ``gdal_translate -of MBTiles -co TILE_FORMAT=PNG`` directly
    on the source.  Against a float32 depth or WSE COG that produces an 8-bit
    image with no color ramp and no alpha -- nodata renders as opaque black --
    and it built no overviews at all, so the ``MINZOOM`` creation option had
    nothing to apply to.

    :func:`ras2cng.maplibre._render_raster_pmtiles` is the correct
    implementation of this job: it applies the color ramp at native resolution
    before any downsampling, warps to an exact Web Mercator zoom resolution, and
    builds the pyramid.
    """

    from ras2cng import maplibre

    input_path = Path(input_file)
    output = Path(output)
    output.parent.mkdir(parents=True, exist_ok=True)

    _require_cli("gdal_translate")
    _require_cli("pmtiles")

    info = maplibre._gdalinfo(input_path)
    bands = info.get("bands") or []
    if not bands:
        raise ValueError(f"Raster has no readable bands: {input_path}")
    band = bands[0]
    stats = {
        "minimum": float(band.get("minimum", 0.0)),
        "maximum": float(band.get("maximum", 1.0)),
    }

    def ramp_writer(path: Path) -> None:
        maplibre._result_color_ramp(stats, map_type, path)

    with tempfile.TemporaryDirectory(prefix="ras2cng-raster-pmtiles-") as scratch:
        maplibre._render_raster_pmtiles(
            input_path,
            output,
            ramp_writer=ramp_writer,
            max_zoom=max_zoom,
            scratch_dir=Path(scratch),
            prefix="cli-raster",
        )