Shrinking a PMTiles archive from 393MB to 86MB: drop the zooms and attributes you never draw

7 min read

TL;DR

  • Building outlines are drawn only from z16, yet 69% of the archive (271.6MB) was z13-15 tiles; keeping z16 and dropping building ids took it from 393MB to 86MB.
  • Removing --simplify-only-low-zooms cut z16 coverage polygon vertices from 41.9 million to 16.7 million, at tippecanoe's default tolerance, which aims to stay within one tile unit (about 0.12m).
  • We left the point layer alone: tippecanoe thins points below the maximum zoom, so z14 held only 55,706 of the 258,376 sites.

Viewcone serves its map data from two PMTiles files. One archive holds CCTV sites and their estimated coverage. The other holds the building outlines that coverage is clipped against. On 2026-10-15 we rebuilt both: the building archive went from 393MB to 86MB, and the CCTV archive from 164MB to 139MB. The number of sites and coverage areas did not change, and the rebuild filled in about 6,500 buildings the old archive was missing.

The idea was to keep only the zooms and attributes the style actually draws. Applying the same idea to the point layer, though, would have changed the map, so that part is here too.

Count the bytes per zoom first

A PMTiles file carries its own tile directory, so you can count tiles and bytes per zoom without decoding a single tile. The building archive looked like this:

ZoomTilesSizeMean tile
z136,65881.5MB12,239 bytes
z1419,21189.4MB4,655 bytes
z1555,189100.7MB1,825 bytes
z16153,050121.2MB792 bytes

Viewcone draws building outlines only from z16. That is the zoom where coverage areas and direction arrows appear, so the buildings that clip the coverage appear with them. The z13-15 tiles, 271.6MB or 69% of the file, were never drawn on any screen.

The web map reads only the tiles it needs through HTTP range requests. So the waste showed up mostly on the server's disk and whenever the file was handled as a whole, as in backups and copies.

Match the zoom range to the style and overzoom the rest

When you zoom in past a source's maximum zoom, MapLibre scales up the maximum-zoom tiles. The MapLibre style spec says the same: data from tiles at the maxzoom are used at higher zoom levels. A PMTiles archive records its minimum and maximum zoom in the header, and those become the source's zoom range.

A layer that starts at z16 needs nothing but z16 tiles, scaled up beyond that. So the main change to the tippecanoe command for buildings was the minimum zoom:

# before: z13-16
tippecanoe -o buildings-kr.pmtiles --layer=buildings \
  --minimum-zoom=13 --maximum-zoom=16 --drop-densest-as-needed buildings-kr.geojson

# after: z16 only
tippecanoe -o buildings-kr.pmtiles --layer=buildings \
  --minimum-zoom=16 --maximum-zoom=16 --drop-densest-as-needed buildings-kr.geojson.gz

The .geojson.gz input is new as well. The nationwide coverage geometry alone is about 2.7GB as GeoJSON, so to save disk space on the server every intermediate file is now gzipped and handed to tippecanoe as is. tippecanoe reads .gz input directly.

Drop the attributes nothing reads

Every building carried an id property and a feature id. The outlines are drawn as lines only, and nothing lets you tap or highlight a building, so no code read either id. The new archive writes empty properties and no feature id.

BeforeAfter
Zooms storedz13-16z16
z16 size121.2MB86.2MB
z16 mean tile792 bytes563 bytes
z16 building features4,476,8954,484,447
Whole archive393.3MB86.5MB

The feature count went up by 7,552. The old archive, built on 2026-09-02, held about 6,500 fewer buildings than the building table does today (3,866,053 distinct ids at z16 against 3,872,512 rows), and the rebuild filled them in. With more buildings the z16 tiles still shrank by 29%, so the saving came from the ids and properties.

Simplify coverage polygons at the maximum zoom too

The CCTV archive holds site points (the cctv layer) and coverage polygons (the coverage layer).

  • Coverage is an estimate drawn from the direction, angle and range published in the national CCTV standard dataset (전국CCTV표준데이터).
  • Only 792 of the 258,376 sites publish a direction to point the cone; the rest (no direction, or published as 360°) are drawn as a full circle. Where no range is published (most sites) it is 55m, and a directed site without an angle uses 68°. Buildings clip it where they block the view.
  • The arc of each circle or cone and every clipped edge are chains of short segments, so the polygons have a lot of vertices.

We used to pass --simplify-only-low-zooms to tippecanoe, which turns off line and polygon simplification at the maximum zoom. Without it, the default simplification applies at the maximum zoom as well.

  • The tippecanoe README says the standard tolerance "tries to keep the line or polygon within one tile unit of its proper location".
  • A z16 tile has 4096 units across and is about 485m wide at 37.5°N, so one unit is about 0.12m.
BeforeAfter
Coverage pieces (z16)372,681372,681
Vertices41,937,13416,720,868
z16 mean tile1,413 bytes1,120 bytes
Whole archive164.1MB139.2MB

Vertices fell by 60% and the polygon count stayed the same. The z5-15 tiles kept the same tile count and size: coverage exists only at z16, and the points below it have no lines to simplify.

We also worked out what 0.12m means on screen.

  • At the web map's maximum zoom of 20, one CSS pixel is about 0.06m, so at full zoom an edge can move by about two pixels.
  • At z18 a pixel is about 0.24m, so the shift is half a pixel.
  • Coordinates were already snapped to the z16 tile grid, so errors up to half a unit (about 0.06m) existed before.
  • Coverage is an estimate, not a measured field of view, so we judged that this shift does not change what anyone reads from the map.

With simpler coverage and lighter building tiles, the tiles a browser fetches at z16 got smaller too. By mean tile size, a CCTV tile and a building tile together went from about 2.2KB to 1.7KB.

Why the point layer kept its zooms

The first plan was to store points only up to z14 and overzoom from there. Each site carries up to 31 attributes, and the same site is stored again at z14, z15 and z16. Counting the sites per zoom changed the plan:

ZoomSites in the archive
z1455,706
z15125,182
z16258,376

By default tippecanoe thins points below the base zoom (--base-zoom).

  • The base zoom defaults to the maximum zoom, "the level at and above which all points are included".
  • The drop rate (--drop-rate) defaults to 2.5.
  • So of the 258,376 sites on the map, z14 held 55,706 and z15 held 125,182 (z13 and below are clusters).

Moving the points into their own archive with a maximum zoom of 14 would make z14 their base zoom, which puts every point into the z14 tiles. Overzoomed z14-15 views would then show all 258,376 sites, much denser than today. That would save bytes but change the map, so the point layer stayed as it was. Before you lower a point layer's maximum zoom, count its features per zoom.


Method

  • On 2026-10-15 we read every tile of the old and new archives on the Viewcone server, decoding the gzip vector tiles with the Python standard library.
  • Per zoom we counted tiles and compressed bytes. Per layer we counted features, distinct feature ids and vertices. A feature that crosses tile edges counts once per tile.
  • Both archives were built with tippecanoe 2.82.0.
  • CCTV data is the national CCTV standard dataset (data downloaded 2026-09-20). Buildings come from Overture Maps.
  • The new archives are the files Viewcone serves now. Screen sizes assume MapLibre's 512-pixel tiles at 37.5°N.

Share