PMTiles
A .pmtiles file holds a whole tile pyramid, every zoom level of a dataset,
in one file that a static host can serve. The browser reads only the tiles in
view. Use it for large data that doesn’t change: building footprints,
parcels, a road network, imagery or elevation. It is one of the
large-data formats.
Quick start
Section titled “Quick start”Point a vector source’s url: at the file, and name the tile layer to draw
in each map layer’s source-layer::
type: mapid: districtsconfig: center: [10.2, 42.15] zoom: 10 mapStyle: "https://tiles.openfreemap.org/styles/positron"sources: outlines: type: vector url: /gallery-assets/cloud-native/districts.pmtileslayers: - id: outlines type: line source: outlines source-layer: districts # a layer inside the archive paint: line-color: "#1d3557"The file is on this documentation site; to run the document on yours,
download districts.pmtiles
into gallery-assets/cloud-native/ and serve the folder with npx serve.
You should see district outlines on a coast. The
Large data quick start shows it live
with a FlatGeobuf layer.
Vector, raster and elevation archives
Section titled “Vector, raster and elevation archives”The source type: says what the tiles hold:
sources: buildings: type: vector url: data/buildings.pmtiles # layers say which source-layer to draw imagery: type: raster url: data/imagery.pmtiles tileSize: 256 # most raster archives use 256-pixel tiles terrain: type: raster-dem url: data/terrain.pmtiles encoding: terrarium- The archive’s own metadata (zoom range, bounds, attribution, layer names)
reaches MapLibre, so you rarely set
minzoomormaxzoom. - Framing: a tile archive has no extent the browser can measure, so
fitTodoesn’t apply to it. Setcenterandzoom(orbounds) for where the data is. pmtiles://data/buildings.pmtilesworks too, the form MapLibre’s own examples use.hover.highlightworks on vector archives (core 0.9.2 or later) when the tiles carry feature ids; if they don’t, addpromoteId: { <layer>: <property> }to the source, naming a property that is different for every feature, and the map tells you so while you develop.- MLT tiles (MapLibre Tiles) draw with no
encoding:on maplibre-gl 6.0 or later: maplibre-yaml reads the tile type from the archive’s header, and anencoding:you write wins. MapLibre can fail on MLT tiles with 64-bit integer columns; the map’s notice says to write those columns as doubles or strings, or build MVT tiles. A page that registers its ownpmtilesprotocol writesencoding: mltitself (MLT tiles).
Make a PMTiles file
Section titled “Make a PMTiles file”- Vector tiles from GeoJSON: tippecanoe,
tippecanoe -o buildings.pmtiles buildings.geojson. Its-zgoption picks a zoom range for you. For parcels and building footprints, use the settings that keep the fabric whole. - From GDAL:
ogr2ogr -f PMTiles out.pmtiles in.gpkg(GDAL 3.8 or later). - From an existing MBTiles file:
pmtiles convert in.mbtiles out.pmtileswith thepmtilestool. A browser can’t read.mbtilesdirectly.
Keep a file small by cutting it to the area and zoom levels you show: each extra zoom level makes it about three times bigger.
From a large file
Section titled “From a large file”A GeoParquet or FlatGeobuf file too large to draw at every zoom (a city’s parcels or buildings seen whole) is what PMTiles is for: the map reads only the tiles in view, simplified for the zoom. tylertoo builds the archive from a GeoParquet file in seconds, with nothing to install but uv:
uv run --with tylertoo==0.8.0 python -c "import tylertoo as t; t.overview('lots.parquet', 'lots-ov.parquet', min_zoom=0, max_zoom=14, collapse_square=True); t.export_pmtiles('lots-ov.parquet', 'lots.pmtiles', layer_name='lots', tile_size_limit=None)"- This is the parcel recipe measured on NYC’s 856,687 lots (8 columns): 26 s
and 2.1 GB to build a 115 MB archive (tippecanoe took 8 min 40 s), and the
citywide map draws in 1–3 s at 58–60 fps on a desktop.
collapse_square=Truekeeps lots too small for a zoom visible as small squares;tile_size_limit=Nonestops tylertoo 0.8.0 dropping features from dense tiles.min_zoom=0draws the data at every zoom, a region-wide view too: starting at zoom 10 instead saves only 1.3 s and 3.5 MB, and leaves the map blank further out. MapLibre draws zoom 14’s tiles further in; raisemax_zoomfor finer detail. - tylertoo reads GeoParquet. Convert a FlatGeobuf, GeoJSON or GeoPackage file
first with geoparquet-io (nothing to install but uv):
uvx --from geoparquet-io==1.7.0 gpio convert geoparquet lots.fgb lots.parquet. GDAL’sogr2ogr -f Parquetworks too when your GDAL has its Arrow/Parquet driver (ogrinfo --formats | grep Parquet; conda-forge’sgdalwithlibgdal-arrow-parquet, or theghcr.io/osgeo/gdalDocker image). - What the coarse zooms show (squares for small lots, every lot from zoom 14), and how tippecanoe and freestiler compare: Parcel fabrics: PMTiles.
- Every column stays a tile property. To highlight on hover or address
features by id, name an id column with
promoteId:
sources: lots: type: vector url: data/lots.pmtiles promoteId: { lots: BBL }PMTiles inside a basemap style
Section titled “PMTiles inside a basemap style”A basemap style loaded from a URL can use PMTiles too, with no page
JavaScript: mapStyle: can point at a style whose sources are pmtiles://
URLs. maplibre-yaml registers the pmtiles:// protocol before the map is
created, so the style’s first requests find it.
Relative URLs inside the style are relative to the style.json, the way a
stylesheet’s url() is relative to the stylesheet. That applies to a
source’s url (including the file inside pmtiles://), tiles, sprite
and glyphs. base applies to the style URL itself, so a style under
base="/my-repo/" finds its own files under /my-repo/ too. If the style
names a file that isn’t there, the map’s notice says which one.
To build a basemap from a Protomaps file, see Host your own basemap.
Your own protocol registration
Section titled “Your own protocol registration”A page that already calls addProtocol("pmtiles", …) through
@maplibre-yaml/core/maplibre (for custom request headers or a custom
Source) keeps it: maplibre-yaml leaves an existing registration alone.
Troubleshooting
Section titled “Troubleshooting”- The notice says “…isn’t a PMTiles file”: a wrong path often returns a web page instead. Check the file name and folder.
- Nothing draws, no notice: the
source-layer:name doesn’t match a layer inside the archive.pmtiles show data/buildings.pmtileslists them. - “…was served without range-request support”: see Publishing › Large files.
More: Large data › When something goes wrong.
Export to style.json: Large-data files in an exported style.
Gallery: PMTiles source and protocol.