Public web maps are amazing—until they are slow, rate limited, offline, or missing the style and layers you need. Running your own map stack fixes that. You keep performance and privacy under your control, add your data cleanly, and take the whole thing into the field. The good news: modern tools make this far easier than it used to be.
This guide shows how to build a practical, maintainable open map stack using vector tiles, PMTiles archives, and MapLibre. You will be able to host a global or regional basemap on a low-cost object store, serve it at scale, and package the exact same tiles for offline use on laptops and phones.
What You’re Building
The stack is split into two halves: a build pipeline that turns open data into vector tiles, and a delivery path that serves those tiles online and offline. Your client apps (web and mobile) use the exact same style JSON and assets in both cases.
Core components
- Data: OpenStreetMap (OSM) extracts for roads, buildings, and points of interest; optional Overture Maps data for standardized places and admin boundaries.
- Tile builder: Planetiler for fast, schema-driven tile generation; Tippecanoe for custom overlays.
- Tile archive: PMTiles (a single-file vector tile archive you can host and stream efficiently via range requests).
- Hosting: Any object store (S3, Cloudflare R2, etc.) behind a CDN.
- Client: MapLibre GL JS for the web, MapLibre Native for Android/iOS.
With this design, you don’t run a heavy tile server. You publish a stable file and let the CDN’s cache do the work. For offline, you ship the same file to the device.
Why Vector Tiles and PMTiles
Vector tiles are small binary packets of geometry and attributes arranged in a tile pyramid by zoom level. The client renders them with a style. This is different from raster tiles (pre-rendered images) and gives key advantages:
- One tile source; many styles. Switch between light/dark/terrain without new tiles.
- Selectable, searchable features. You can interact with the map.
- Bandwidth wins. At large scales, vectors are smaller than rasters and cache better.
PMTiles wraps all zoom levels and tiles into a single HTTP-addressable file. The client requests specific byte ranges, so your CDN can cache segments efficiently. Operationally, this means:
- One object to upload, version, and invalidate.
- No 404 storms from per-tile requests.
- Simple offline packaging. Copy the same file to a device.
Choose a Tile Schema and Style
Before you build, pick a schema that defines which layers and attributes your tiles contain. Your schema choice affects downstream styles, labeling, and performance.
Schema options
- OpenMapTiles schema: A widely used schema covering roads, buildings, landuse, water, POIs, admin, and more. Many ready-made styles are available.
- Overture-based schema: Leverages Overture Maps’ standardized datasets (places, admin, buildings, transportation). Good if you care about consistent global IDs and deduplication across sources.
- Custom schema: Define exactly what you need (for minimal size or special attributes). More work, maximal control.
Styling with MapLibre
For fast progress, start with an existing MapLibre style compatible with your chosen schema. Then tune fonts, colors, and label rules. Keep your style assets (glyphs and sprites) in your own bucket so everything is yours to control. Remember to include attribution for OSM and any other sources—more on that later.
Build Tiles with Planetiler
Planetiler is a high-performance Java tool that converts large geodata sources into vector tiles. It runs on a single machine and is astonishingly fast compared to traditional pipelines.
Hardware and scope
- CPU: Modern multi-core CPU recommended.
- RAM: 32–64 GB is comfortable for regional builds; global builds need more and careful config.
- Storage: Fast SSD for temp files. Final PMTiles is often measured in gigabytes for large regions.
To keep things simple at first, pick a region, not the whole planet. Start with a country or state-level extract.
Inputs to download
- OSM extracts: Use regional extracts (PBF) from a reputable mirror.
- Overture data (optional): If integrating Overture, download relevant themes (e.g., places, admin, buildings).
- Natural Earth (optional): Good for low-zoom layers (borders, coastlines, large water, land).
Producing PMTiles
The typical flow looks like this:
- Pick a schema repo compatible with Planetiler (e.g., an OpenMapTiles-like config).
- Run Planetiler to generate vector tiles at your chosen zoom range (e.g., z0–z14 or z0–z15 for road detail).
- Tell Planetiler to package output as PMTiles.
- Validate tiles with a quick viewer or inspector before shipping.
Practical notes:
- Zoom levels: Lower max zoom shrinks file size. Urban areas may need z15 for comfortable street detail; rural-only maps can stop at z14.
- Language and labels: Choose language preference rules for label fields (e.g., Latin script fallbacks).
- Bounding box: Clip to your area of interest to reduce size and build time.
Serve Tiles Online with Object Storage and a CDN
Once you have a .pmtiles file, hosting is refreshingly simple. Upload it to your object store, place a CDN in front, and point your MapLibre style to the file using the PMTiles protocol.
Why this works so well
- One file, many clients: The same file serves browsers and mobile apps.
- Range requests: Clients fetch only the parts needed for the viewport and zoom.
- Caching: CDNs cache the hottest byte ranges near users. Cold regions don’t cost you much.
- Ops sanity: Zero per-tile 404s. Versioning is easy: v1/basemap.pmtiles to v2/basemap.pmtiles and so on.
Key hosting settings
- CORS: Allow your app origins to request the file.
- Content-Type: application/octet-stream is safe. Ensure range requests (HTTP 206) are supported.
- Cache headers: Set long Cache-Control on the PMTiles file since it’s content-addressed by your versioned path.
Estimating cost and performance
CDN egress dominates cost. PMTiles typically reduce chatter and improves cache hit rates compared to per-tile PNGs. Start with a small audience, measure, then scale. Logging per-path stats helps spot hot zoom areas and plan pre-warms if needed.
Make It Work Offline
Offline is where this stack shines: copy the same .pmtiles file to a device and feed it to a MapLibre client. Because styling is client-side, the result looks identical to the online map. This is ideal for fieldwork, disaster response, and network-constrained sites.
Packaging for devices
- Web: Use a Service Worker to cache the PMTiles file in the browser’s storage for progressive offline. Large files can exceed quotas, so prefer native apps for multi-GB archives.
- Android/iOS: Bundle PMTiles with the app or download on first run. Store in app-specific storage (with user consent and a size estimate).
- Laptops: Keep PMTiles in the user’s Documents folder and point the viewer to the local file URL.
Updates without pain
- Versioning: Name the file by date or build number. The app can check a lightweight manifest for updates and swap in the new file atomically.
- Delta updates: For frequent changes, consider partial downloads by tile region or maintain multiple regional files so users only update what they need.
Offline search and routing
Basemaps don’t provide search or routing by themselves. If needed, pair the map with a compact offline index:
- Search: Build a small geocoder index (e.g., using Pelias components or a custom index of your POIs). Scope it to the region to keep size manageable.
- Routing: Precompute or embed a routing graph with a tool like Valhalla, limited to your coverage area.
Performance and Quality Tuning
Good tiles are a careful balance of size, detail, and label readability. Start with defaults, then iterate.
Generalization and tile size
- Line simplification: Increase simplification at lower zooms so major lines remain smooth but small wobbles vanish.
- Feature filtering: Remove tiny features below certain zooms (e.g., minor buildings at z10 and below).
- Tile size guardrails: Aim for vector tiles below ~200 KB for worst-case urban zooooms; smaller is better. Watch for a few oversized tiles that can tank performance.
Label density and collisions
- Collision rules: MapLibre will hide labels that overlap. Tweak symbol spacing to reduce jitter while panning.
- Priority: Set layer priorities so important labels (cities, highways) appear first.
- Fonts: Use efficient glyph sets; include scripts your users need to avoid missing labels.
Stress test your map
- Viewport sweeps: Programmatically pan and zoom across dense cities, measuring frame time and network.
- Device matrix: Try older phones and low-end laptops to make sure it still feels snappy.
- Cache warmup: Simulate a cold CDN and a warm CDN to understand first-user vs steady-state performance.
Add Your Own Data, Cleanly
Overlays are where the map becomes your map. Keep overlays separate from the basemap so you can update them daily without touching the core file.
Create overlay PMTiles
- Convert your data to MVT: Use Tippecanoe to generate tiles from GeoJSON or shapefiles.
- Pack as PMTiles: Tippecanoe can output MBTiles; then convert to PMTiles, or stream directly with tools that support PMTiles output.
- Reference in style: Add an extra vector source in your MapLibre style JSON that points to the overlay PMTiles.
Examples of useful overlays:
- Company assets and service regions.
- Sensor footprints, safety zones, or temporary closures.
- Survey points and annotations.
3D terrain and hillshade
For outdoors or engineering use, consider adding a digital elevation model (DEM) tile source for terrain exaggeration and hillshade. Many open DEM sources can be rendered as raster tiles for use with MapLibre’s terrain support. Keep terrain tiles separate to reuse them across multiple basemaps and styles.
Security, Licensing, and Attribution
Even an open map stack needs a little governance.
Secure distribution
- Public vs private: If your map is for staff only, put your PMTiles behind authenticated endpoints or signed URLs.
- CORS and origins: Restrict CORS to known app origins to reduce abuse.
- Logs: Keep minimal logs and consider IP anonymization if privacy is a goal.
Licensing and attribution
- OSM: Follow the OpenStreetMap attribution guidelines. Typically, “© OpenStreetMap contributors” in the corner is required, with a link to OSM.
- Overture: Follow Overture Maps attribution rules if you use their data.
- Third-party assets: Check licenses for fonts, icons, and styles you adopt.
A Small, Maintainable Release Process
A reliable map is the result of boring, repeatable steps. Set up a simple pipeline early.
Suggested pipeline
- Weekly build: Nightly or weekly cron runs Planetiler with current OSM/Overture extracts.
- Validation: Automated checks for tile size outliers, missing layers, and label coverage in a few known bounding boxes.
- Versioned publish: Upload to /basemap/vYYYYMMDD/basemap.pmtiles and keep a /basemap/latest/ pointer for clients.
- Blue/green swap: Update the style to the new version only after a quick human spot-check.
- Rollback: If issues arise, flip the pointer back to the previous version.
Troubleshooting Common Issues
Tiles are fast but labels flicker
Reduce label density or adjust repeat distances. Verify fonts include all scripts in your data to prevent fallback quirks.
Some regions never cache well
Consider splitting into multiple PMTiles by region so popular areas cache independently, while rarely used areas don’t evict hot slices.
Tiles look empty at certain zooms
Check your schema’s minzoom/maxzoom per layer. Important layers might be filtered out too aggressively. Temporarily increase logging in the client to inspect which layers are loaded at a given zoom.
PMTiles downloads feel large
Audit offending tiles: dense urban centers often drive size. Increase simplification or merge small features at those zoom levels. Consider compressing attributes you don’t use.
Planning for Growth
As usage grows, this design scales without dramatic changes.
- Global coverage: Build a world PMTiles at low zooms and combine with regional high-zoom PMTiles that load only when the user zooms in.
- Multi-tenant apps: Keep a single basemap and attach tenant-specific overlays as separate PMTiles, isolating updates and permissions per tenant.
- Analytics: Track only what you need: tile byte ranges requested (anonymized) and style version in use, to guide optimizations.
Cost and Footprint Tips
- Use a CDN with tiered caching: Popular with global maps. It reduces origin hits in far regions.
- Compress everything: PMTiles is compact already, but ensure HTTP compression for style JSON, sprites, and glyphs.
- Right-size builds: Don’t build zooms you won’t show. Your style can cap maxzoom, and your tiles should match.
- Run builders on spot/preemptible instances: If building in the cloud, lower costs by using ephemeral VMs and caching inputs in persistent storage.
When You Might Still Want a Dynamic Server
Static hosting covers most needs, but a dynamic tile server is still useful if you require:
- On-the-fly filters: e.g., time sliders that filter features server-side before tile generation.
- Auth per feature: Serving different tile subsets to different users.
- Ad hoc queries: Vector tiles aren’t a database; if users must query geometry server-side, a separate service can help.
Even then, keep your basemap static and let the dynamic server handle overlays only.
A Minimal Example Stack
- Build: Planetiler with OpenMapTiles schema; produce z0–z15 PMTiles for your region.
- Host: Upload PMTiles to an S3 bucket; enable CORS; serve via CloudFront with long cache control.
- Style: MapLibre style JSON with two vector sources: basemap PMTiles + your overlay PMTiles.
- Apps: Web dashboard with MapLibre GL JS; Android/iOS apps using MapLibre Native with the same style.
- Ops: Weekly builds, versioned paths, automated checks for tile size outliers, and a one-click rollback.
Summary:
- Use vector tiles and PMTiles for a simple, scalable basemap you control.
- Pick a tile schema early (OpenMapTiles, Overture, or custom) to guide data and styles.
- Planetiler turns open data into tiles fast; start with a regional build and iterate.
- Host a single .pmtiles file on object storage behind a CDN for efficient, low-ops delivery.
- Ship the same PMTiles file to devices for offline maps that match your online style.
- Keep overlays separate, also as PMTiles, so you can update fast without touching the basemap.
- Tune tile size, label rules, and zoom levels for quality and performance.
- Respect attribution and licenses, lock down distribution where needed, and keep boring, repeatable release steps.
External References:
- OpenStreetMap: About
- Geofabrik OSM Download Server
- Overture Maps
- Planetiler on GitHub
- PMTiles on GitHub
- MapLibre GL JS Documentation
- MapLibre Native
- OpenMapTiles
- Tippecanoe on GitHub
- BBBike OSM Extracts
- MapLibre Terrain Example
- Mapbox Vector Tile Specification
- Pelias Geocoder
- Valhalla Routing Engine
