Vector Tile Pipelines with PMTiles

Once a dataset is too large to ship as GeoJSON, the client should download only the features in view at the current zoom — that is what vector tiles deliver. This guide covers building tiles in Python-orchestrated pipelines and packaging them as PMTiles, a single-file, server-less tile archive, with MBTiles as the SQLite-based alternative when you need a running endpoint. It is the scaling layer beneath Interactive Maps with Folium and MapLibre GL Vector Web Maps in Web Mapping & Interactive Visualization.

Vector tile build and serve pipeline GeoParquet or GeoJSON is tiled by tippecanoe into MBTiles, converted to PMTiles, and served from a static bucket over HTTP range requests to a MapLibre client. Build once, serve statically GeoParquet source features tippecanoe tile + simplify per zoom MBTiles SQLite store PMTiles single file range reads Static bucket / CDN → MapLibre fetches only the viewport tiles
Tiles are built once with tippecanoe, packed into PMTiles, and served as a static file — no tile server in the request path.

Architecture & Data Structures

A vector tile is a compact, pre-clipped slice of geometry for one z/x/y cell, encoded in the Mapbox Vector Tile (MVT) protobuf format. A tileset is the full pyramid across zoom levels. The two containers in common use:

The build tool is tippecanoe, a C++ binary that ingests GeoJSON or GeoParquet (via GDAL) and emits MBTiles with per-zoom simplification and feature dropping. Python orchestrates it as a subprocess and handles the PMTiles conversion.

import subprocess

# tippecanoe is a native binary invoked from Python
subprocess.run([
    "tippecanoe",
    "-o", "buildings.mbtiles",
    "-zg",                      # choose max zoom automatically
    "--drop-densest-as-needed", # shed features where tiles get too big
    "-l", "buildings",          # layer name inside the tileset
    "buildings.geojson",
], check=True)

Environment Configuration & Dependency Resolution

# tippecanoe: system binary (no native Windows build — use WSL2 or a container)
#   brew install tippecanoe         # macOS
#   build felt/tippecanoe           # Linux
# Python side:
conda install -c conda-forge "pmtiles=3.2.*" "geopandas=0.14.*" "gdal=3.8.*"

gdal supplies ogr2ogr, which converts GeoPackage/Parquet to the GeoJSON or FlatGeobuf that tippecanoe ingests. The pmtiles Python package provides both the CLI conversion and a reader for verification.

Vectorized Operations & Core Workflow

The pipeline: produce source features (ideally GeoParquet from a cloud-native workflow), tile with tippecanoe, convert to PMTiles, upload. The detailed recipe is in Generating PMTiles from GeoParquet.

import subprocess

# 1. GeoJSON → MBTiles (tippecanoe)
subprocess.run([
    "tippecanoe", "-o", "parcels.mbtiles", "-zg",
    "--coalesce-densest-as-needed", "-l", "parcels", "parcels.geojson",
], check=True)

# 2. MBTiles → PMTiles (single-file, static-host ready)
subprocess.run(["pmtiles", "convert", "parcels.mbtiles", "parcels.pmtiles"], check=True)

Geometry / Data Processing Details

Tippecanoe simplifies geometry per zoom level, so low zooms carry coarse outlines and high zooms carry full detail. You control the trade-off with flags: --simplification, --drop-densest-as-needed (drop features), and --coalesce-densest-as-needed (merge features). Validate and clean topology before tiling — tippecanoe will tile invalid polygons without complaint, and the artifacts surface only on the map. Reuse Topology Validation & Repair.

import geopandas as gpd
from shapely.validation import make_valid

land_use = gpd.read_file("land_use.gpkg")
land_use["geometry"] = land_use.geometry.apply(make_valid)
land_use = land_use[land_use.geometry.is_valid].to_crs(epsg=4326)
land_use.to_parquet("land_use.parquet")   # GeoParquet input for tiling

CRS Alignment & Projection Pipeline

Vector tiles are addressed in the Web Mercator tile grid, and tippecanoe expects EPSG:4326 input — it performs the Mercator projection during tiling. Convert to 4326 as the final step before tiling, after all metric processing, using Coordinate Systems with PyProj.

import geopandas as gpd

buildings = gpd.read_file("buildings.gpkg")   # EPSG:25833
buildings["footprint_m2"] = buildings.geometry.area   # metric attribute first
buildings.to_crs(epsg=4326).to_file("buildings.geojson", driver="GeoJSON")

Production Export & Integration

Windows / Platform Edge Cases & Debugging