Interactive maps with static vector tiles
September 26, 2020This post aims to be a practical guide to pre-build and serve vector tiles as static assets for interactive maps without the need for a dedicated server or a third-party service.
If you're in a hurry or you'd rather learn by example, here's the source code for a demo project that implements everything explained here.
Steps
- Assess if serving static tiles is right for you
- Find and download data
- Convert your data to GeoJSON
- Build static tiles
- Add necessary metadata
- Add styles
- Host the tiles
- Consume your tiles
Assess if serving static tiles is right for you
Statically generated content (like this blog) comes with advantages both in costs, resources, and security:
- Performance: no server-side processing nor databases
- Hosting: simple setup and zero to non-maintenance
- Security: no server-side language issues to exploit and no database to hack
However, pre-generating tiles also has limitations:
- You can't generate data on the fly based on user input.
- Static tiles are better suited for maps that don't change often, re-generating a large number of tiles frequently can be a waste of time and resources.
- You might need to put restrictions on the scales that a user can choose (zoomed layers can generate a large number of tiles)
Serving static content works in a fair amount of cases, but is not well suited for everyone, and before moving forward it's worth asking the question: will it work for you?
Find and download data
If you know what you need to display in the map, but you don't have the data yet, the next step is finding a good data source.
A web search is your best ally, but here's an incomplete list of sources with a wealth of information:
Before using the data, please make sure to comply to the terms of use and attributions of each source.
Convert your data to GeoJSON
There are different formats to store Geographic information data but the tool you're going to use in the next step requires the input data to be in the GeoJSON format.
To convert the data to GeoJSON you can use any online service or tool of your choice.
GDAL (ogr2ogr
) is my tool of choice because it allows you to
convert practically between any format you can find in the wild. Once you have
installed it, converting the data is a matter of running:
ogr2ogr -f GeoJSON source.geojson source.shp
Build static tiles
tippecanoe is a command line application that allows you to generate vector tiles from GeoJSON files, it's capable of generating:
- A single
.mbtiles
file (oversimplifying, think of tiles stored in a SQLite container). - A folder hierarchy that contains
{z}/{x}/{y}.pbf
files with the tiles, wherez
is the zoom level and(x, y)
the coordinate of the tile.
The file hierarchy is convenient to serve static tiles because clients commonly use URL structures in the form of http://server.com/z/x/y.pbf
to request tiles.
The idea is roughly as follows:
- Generate an
.mbtiles
file for every layer of data that you want to show. At this stage, you can adjust the placement and visibility of this layer at different zoom levels. - Combine the
.mbtiles
files and generate the file hierarchy we described above using thetile-join
command (included when you install tippecanoe)
# Generate a .mbtiles file for every layer you want to display
# this shows countries at low zoom levels but states at higher zoom levels:
tippecanoe --exclude-all -z3 -o countries-z3.mbtiles --coalesce-densest-as-needed countries.geojson
tippecanoe -zg -Z4 -o states-Z4.mbtiles --coalesce-densest-as-needed --extend-zooms-if-still-dropping states.geojson
# Join the generated files and Create a nested directory of tiles
tile-join -e tiles countries-z3.mbtiles states-Z4.mbtiles
Don't let the flags threaten you, tippecanoe
's README contains a
cookbook with examples, chances are you will find
something useful there.
This step will vary from project to project depending on what information you need to display.
Add necessary metadata
Along with the tiles, tippecanoe
generates a metadata.json
with
information about the tiles and the layers used to generate them.
The client consuming the tiles uses this file later on, but for it to be usable you need to do some cleanup:
- Set the URL in which you're going to host your tiles.
- Convert the string value of the
vector_layers
key into JSON. - Delete the
json
property.
You can do this manually, or with a bit of glue code, here's a bit of JavaScript, feel free to borrow, modify and use it:
const metadata = require("./tiles/metadata.json");
const { writeFileSync, copyFileSync } = require("fs");
// make a backup of the data in case something goes wrong
copyFileSync("./tiles/metadata.json", "./tiles/metadata.backup.json");
// convert the `vector_layers` key from a string to JSON
metadata.vector_layers = JSON.parse(metadata.json).vector_layers;
// Set the tiles key
metadata.tiles = ["https://your-domain.com/tiles/{z}/{x}/{y}.pbf"];
// Remove the `json` key to prevent errors
delete metadata.json;
// Write the updated file
writeFileSync("./tiles/metadata.json", JSON.stringify(metadata));
Host the tiles
Choosing where to host the tiles is entirely up to you, as it will depend on your infrastructure, but here are some recommendations:
tippecanoe
exports gzipped.pbf
files by default, make sure to set theContent-Encoding: gzip
header or to generate uncompressed tiles by adding the flag--no-tile-compression
- Make sure to upload the
metadata.json
file in the root of thetiles
folder.
Add styles
From the MapBox documentation:
A Mapbox style is a document that defines the visual appearance of a map: what data to draw, the order to draw it in, and how to style the data when drawing it. A style document is a JSON object with specific root level and nested properties. This specification defines and describes these properties.
It's important to highlight that despite what the word 'styles' might suggest, this JSON document also indicates what data to draw.
If you want to start from scratch to keep things light, here is base template you can use:
{
"version": 8,
"name": "Your Theme",
"sources": {
"tiles": {
"type": "vector",
"url": "https://your-domain.com/tiles/metadata.json"
}
},
"layers": [
{
"id": "country-fills",
"source": "tiles",
"source-layer": "countries",
"type": "fill",
"paint": {
"fill-color": "#E6ECF2"
}
}
]
}
Key points to note:
- A
vector
source namedtiles
with the URL hosting the generated vector tiles points to themetadata.json
file. - The display of different layers is managed via the
layers
property.
The style document can be as complex or as simple as your application needs, and can include expressions to compute the value for different attributes.
Consume your tiles
The final step of the puzzle is to consume your vector tiles with a client
library, from plugins for the popular Leaflet
, to native clients.
The people at MapBox keep an updated list at github.com/awesome-vector-tiles.
Whatever you choose will depend on your needs, but just so you have a
reference, consuming the generated tiles using mapbox-gl-js
is as simple as
providing a link to your style
s when instantiating a new map:
new Mapbox({
container: "map",
style: "https://your-domain.com/style.json",
});