17.5 Lab — Publish a Web Map End-to-End
Ship a public interactive map: prepare vector tiles with Tippecanoe, host on a CDN, style with MapLibre.
Key takeaways
- You can publish a production web map with free tools and a static host.
- Tippecanoe generates vector tiles; MapLibre renders them; any static host serves them.
- The entire pipeline is reproducible and cheap.
Introduction
In this lab you'll go from raw GeoJSON to a live interactive web map published on a public URL. Total time: about an hour. Tools: Tippecanoe, MapLibre GL, any static host (Netlify, Vercel, GitHub Pages, S3).
Prerequisites
tippecanoeinstalled (brew install tippecanoeor from source on Linux).- A basic HTML / JS environment.
- Sample GeoJSON — use any open dataset (city buildings, parks, points of interest). We'll use "buildings in your city" as the running example.
Step 1 — Prepare GeoJSON
Download building footprints for a city from OSM / Overpass Turbo or an open-data portal. Clean:
ogr2ogr -f GeoJSON buildings.geojson input.gpkg buildingsVerify:
ogrinfo -so buildings.geojson buildings | headStep 2 — Generate vector tiles
1tippecanoe -o buildings.mbtiles \
2 --name "Copenhagen buildings" \
3 --description "Building footprints" \
4 --minimum-zoom 10 \
5 --maximum-zoom 16 \
6 --drop-densest-as-needed \
7 --extend-zooms-if-still-dropping \
8 -l buildings \
9 buildings.geojsonFlag explanation:
-ooutput file.--minimum-zoom/--maximum-zoom— tile pyramid range.--drop-densest-as-needed— drop features at low zooms to keep tile sizes manageable.-llayer name inside the MVT.
Tippecanoe output is an MBTiles file (SQLite-backed).
Step 3 — Convert MBTiles to PMTiles
pmtiles convert buildings.mbtiles buildings.pmtiles(pmtiles CLI is a Go binary; install from the Protomaps GitHub.)
PMTiles is a single file the browser can read over HTTP range requests.
Step 4 — Host on a CDN or static hosting
Any of:
- Netlify drop — drag and drop.
- Vercel —
vercel deploy. - GitHub Pages — commit the file to a repo.
- AWS S3 —
aws s3 cp buildings.pmtiles s3://bucket --acl public-read. - Cloudflare R2 — similar.
Ensure CORS allows browser access if hosting on S3:
[{"AllowedMethods": ["GET"], "AllowedOrigins": ["*"], "AllowedHeaders": ["*"]}]Step 5 — Write the MapLibre page
1<!DOCTYPE html>
2<html>
3<head>
4 <title>Copenhagen Buildings</title>
5 <link href="https://unpkg.com/maplibre-gl@3/dist/maplibre-gl.css" rel="stylesheet" />
6 <script src="https://unpkg.com/maplibre-gl@3/dist/maplibre-gl.js"></script>
7 <script src="https://unpkg.com/pmtiles@2/dist/index.js"></script>
8 <style>body, html, #map { margin: 0; padding: 0; height: 100vh; width: 100vw; }</style>
9</head>
10<body>
11 <div id="map"></div>
12 <script>
13 const protocol = new pmtiles.Protocol();
14 maplibregl.addProtocol('pmtiles', protocol.tile);
15[object Object]
16Replace YOUR-URL with your hosted PMTiles URL.
Step 6 — Test
Open the HTML in a browser. You should see an OSM base map with your buildings extruded in blue. Pan, zoom, tilt — everything should work.
Step 7 — Add interactivity
Click handler to show building attributes:
1map.on('click', 'buildings', (e) => {
2 const f = e.features[0];
3 new maplibregl.Popup()
4 .setLngLat(e.lngLat)
5 .setHTML(`<strong>Height: ${f.properties.height || 'unknown'} m</strong>`)
6 .addTo(map);
7});Step 8 — Publish and share
Commit your HTML to the same repo / host. Share the URL.
Congratulations — you've published a modern web map with zero server infrastructure.
Alternative path: if your goal is to share the dataset and get feedback rather than practise the full web stack, upload the GeoJSON to Atlas, style it, configure popups, and share the map link. The coded MapLibre path teaches infrastructure; the Atlas path is useful when the deliverable is a working map for collaborators.
Step 9 — Reflection
Record:
- How big is your
.pmtilesfile? - What's the average tile size?
- How long does initial load take?
- What could you add next (filtering, legend, tooltip styling)?
Troubleshooting
- Tiles not loading — check CORS headers on the PMTiles URL.
- Buildings flat — verify the
heightattribute is in the source. - Map blank — check the browser console; usually a missing
source-layeror a misformatted style. - Performance poor — reduce max zoom in tippecanoe, use
--drop-densest-as-needed.
Self-check exercises
1. Why host as PMTiles instead of a tile server?
Zero infrastructure. PMTiles is one file on any static host — no compute, no tile-server process, no scaling concerns. Perfect for data that updates infrequently (daily or less) and for anyone wanting a cheap, reliable public map. Traditional tile servers add complexity, cost, and a running process to maintain.
2. What's the role of Tippecanoe in the pipeline?
Tippecanoe converts raw GeoJSON into a properly indexed, generalised vector tile pyramid. It decides which features to drop at low zoom (so tiles don't explode), respects attribute preservation, and outputs MBTiles — which pmtiles convert then packages into a single-file archive.
3. How would you add a filter that only shows buildings over 20 m tall?
Use a MapLibre filter:
1map.addLayer({
2 id: 'tall-buildings',
3 type: 'fill-extrusion',
4 source: 'buildings',
5 'source-layer': 'buildings',
6 filter: ['>', ['get', 'height'], 20],
7 paint: { ... }
8});Or add a filter to an existing layer at runtime with map.setFilter().
Summary
- Pipeline: GeoJSON → Tippecanoe → MBTiles → PMTiles → static host → MapLibre client.
- Zero infrastructure, fully interactive, global-scale capable.
- The same pattern scales from a single city to a planet-scale map.
Further reading
- Tippecanoe documentation.
- Protomaps PMTiles documentation.
- MapLibre GL JS documentation.
- GeoHipster "Maps without servers" tutorials.
Module 17: Web GIS & APIs
Answer these quick multiple-choice questions to check your understanding before moving on.