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.
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:
- MBTiles — a SQLite database with one row per tile. Requires a process to read it (range queries over SQLite), so you serve it through an endpoint.
- PMTiles — a single flat file with an index, designed for HTTP range requests. A static object store serves it directly; the client reads only the bytes for the tiles it needs. No server process.
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
- PMTiles for static hosting. Upload one file to S3/R2/any bucket; MapLibre reads it with the protocol shim. No server, trivial caching, cheap.
- MBTiles behind a server. When you need auth, dynamic filtering, or an existing tile server (e.g. a Flask/FastAPI endpoint) — see Serving MBTiles with Python.
- Source from GeoParquet. Tiling integrates naturally with Cloud-Native Geospatial Formats: keep the analytical store as GeoParquet and treat tiles as a derived rendering artifact.
Windows / Platform Edge Cases & Debugging
- No native tippecanoe on Windows. Use WSL2 or a Linux container; PMTiles packaging in pure Python works anywhere once the MBTiles exists.
- Tiles look empty at low zoom. Features were dropped by density flags; raise
--minimum-zoomdetail or use--coalesceinstead of--drop. - PMTiles won't load in MapLibre. The protocol shim wasn't registered before map construction.
- Huge MBTiles file. Over-detailed at high zoom; cap
--maximum-zoomto what users actually reach. - Attributes missing in tiles. Tippecanoe drops attributes past a size budget; whitelist needed fields with
-y field_name. - Wrong placement. Source wasn't reprojected to EPSG:4326 before tiling.