PMTiles Migration Design
Summary
Migrate Marandoo tile overlay from 138K scattered PNG files (1.7GB) on R2 to a single .pmtiles file with WebP compression. Cloudflare Worker handles PMTiles unpacking — front-end unchanged.
Architecture
Google Maps ImageMapType
→ GET https://map-tiles.fudong.workers.dev/{site}/{z}/{x}/{y}.png
→ Cloudflare Worker (pmtiles JS library)
→ R2: {site}.pmtiles (HTTP Range Request)
→ Response: WebP tile dataR2 Storage
# Before
map-tiles/marandoo/14/8683/4566.png (138K files, 1.7GB)
# After
map-tiles/marandoo.pmtiles (single file, ~200-300MB)Multi-site Support
Worker maps {site} in URL to {site}.pmtiles in R2 automatically. Adding a new site = upload one .pmtiles file.
Local Conversion Pipeline
PNG tiles → WebP tiles (cwebp -q 80) → MBTiles (mb-util) → PMTiles (pmtiles convert)Tools Required
cwebp—brew install webpmb-util—pip install mbutilpmtiles—npm install -g pmtiles
Conversion Script
scripts/convert-tiles-to-pmtiles.sh — reusable for future sites.
Parameters: tiles directory path, site name.
Steps:
- Batch convert PNG → WebP (quality 80) preserving directory structure
- Pack into MBTiles via
mb-util(TMS scheme,--image_format=webp) - Convert MBTiles → PMTiles via
pmtiles convert
Cloudflare Worker Changes
File: workers/map-tiles/src/index.ts
Key Changes
- Add
pmtilesnpm dependency - Implement custom
R2Sourceclass (mapsgetRange(offset, length)to R2get()with range option) - Parse URL
/{site}/{z}/{x}/{y}.png→ lookup tile in{site}.pmtiles - Cache PMTiles header/directory via Cloudflare Cache API
- Cache tile responses with
Cache-Control: max-age=31536000, immutable
CORS
const ALLOWED_ORIGINS = [
'https://dashboard.dustac.com.au',
'http://localhost:3000',
];Check Origin header, return matching origin or no CORS header.
Backward Compatibility
If {site}.pmtiles not found in R2, fall back to scattered file path {site}/{z}/{x}/{y}.png. Allows incremental migration per site.
Front-end
No changes. overlayConfig.ts keeps existing tileBasePath and URL format.
Update maxZoom from 18 to 20 to use all available zoom levels (tiles exist for 14–20).
Deployment
- Run conversion script locally →
marandoo.pmtiles - Upload:
wrangler r2 object put map-tiles/marandoo.pmtiles --file=marandoo.pmtiles - Deploy Worker:
cd workers/map-tiles && wrangler deploy - Verify with curl
- (Optional) Clean up scattered files from R2
Rollback
Delete marandoo.pmtiles from R2 → Worker falls back to scattered files. Or rollback Worker version via Cloudflare dashboard.
Adding Future Sites
- Prepare tiles directory
- Run
./scripts/convert-tiles-to-pmtiles.sh ./tiles {site-name} - Upload
{site-name}.pmtilesto R2 - Add entry in
overlayConfig.ts