Creating Custom Basemaps with Vector Tile Servers
Contents
Overview
Basemaps play an important role in giving geographic context to a map, however they can be hard to come across and are typically only available under a copyright or license that makes the unusable for many projects. The styling of a basemap can also affect the aesthetic of a map, however the appearance of basemaps is generally controlled by the original creator of the basemap.
This tutorial will show how to create a custom basemap using open data, how to apply a customized style to the basemap, and how to use the basemap in a mapping application.
Prerequisites
This tutorial will require some basic command line use (running commands, changing directories) and will be done in a Linux environment, however the same steps can be performed on other operating systems like Windows and macOS as well.
The following tools will need to be installed:
- NodeJS
- Docker
- docker-compose
- git
- Osmium
- tileserver-gl-light
Vector Tiles
Tilesets are a way of storing geographic data that has been split up into a uniform grid of tiles, and pre-processed to be displayed at preset zoom levels. This pre-processing makes it really easy for a "tileserver" to serve only the relevant data for the current view and zoom of a map viewport, which is far more efficient than trying to load an entire raw dataset. A tileset can contain vector or raster data, however we will be creating a tileset of vector data.
Our vector data source will be OpenStreetMap which makes its data available under the Open Database License (ODbL).
Generating Tiles
To generate our own vector tiles, we will be using the OpenMapTiles project which provides and open-source tool for generating tilesets from OpenStreetMap data.
To install OpenMapTiles, run the following command, and then change into the openmaptiles directory:
git clone https://github.com/openmaptiles/openmaptiles.git
cd openmaptiles
A "quickstart" script is provided which can be run to quickly generate tiles. By default, it will download data for, and generate a tileset of Albania.
By providing the name of a specific region (from a set list of regions), the script will generate tiles for the specified region instead.
The tileset data will be stored in the data
directory, under the OpenMapTiles project.
./quickstart.sh # Generate tileset of Albania
./quickstart.sh canada # Generate tileset of Canada
./quickstart.sh quebec # Generate tileset of Québec
This process can be very slow for large regions like Canada or Québec however, so we will see how to create smaller OpenStreetMap datasets for OpenMapTiles to convert.
Creating Custom OpenStreetMap Data Extent
An OpenStreetMap dataset contains raw high detail vector data, which results in very large data files. For example, Canada alone contains 2.8GB worth of vector data which is both a lot to download, but also a lot to process. Most sources for downloading OpenStreetMap data will only allow a very small extract of data to be downloaded, but some sources like Geofabrik provide larger pre-extracted downloads. Geofabrik has per-province extracts, which can be more manageable to work with.
Let's say that for this tutorial we want to create a basemap of the Ottawa-Gatineau region, which happens to span across both Ontario and Québec. Unfortunately this means that the Geofabrik extracts would split our region across the provincial border, but we can combine the two extracts using a tool called Osmium. The commands below will download and merge the two extracts. We can also go one step further and also use Osmium to extract a much smaller extent of just the Ottawa-Gatineau area, and by leaving out the rest of Ontario and Québec we cut the merged dataset down from 1.2GB to just 48MB.
# Download provincial extracts
wget https://download.geofabrik.de/north-america/canada/ontario-latest.osm.pbf
wget https://download.geofabrik.de/north-america/canada/quebec-latest.osm.pbf
osmium merge ontario-latest.osm.pbf quebec-latest.osm.pbf -o merged.osm.pbf
# Extract the National Capital Region
osmium extract -b -76.2616,45.1249,-75.1726,45.6620 merged.osm.pbf -o ncr.osm.pbf
Generating Tiles (cont.)
Before generating any more tiles, we need to make one adjustment to the default configuration of OpenMapTiles to allow it to generate more tile zoom levels.
By default it will only generate 7 levels, as this is much quicker and can be used for creating previews of tilesets.
Open the .env
file from the OpenMapTiles project and change the MAX_ZOOM
variable to read MAX_ZOOM=14
.
This tells the tool to generate the higher-detail zoom levels.
Now with the ncr.osm.pbf
inside of the OpenMapTiles data
directory, we can now run the following commands to generate a tileset for our extent.
# ncr refers to ncr.osm.pbf, created in previous section
make generate-bbox-file area=ncr
./quickstart.sh ncr
After running, this will produce a tiles.mbtiles
file in the data
directory containing our tileset.
Serving Tiles
Now that we have generated our map tiles, we want to serve them so that they can be used by a mapping application.
We will do this with tileserver-gl-light
After running the following command, the server can be accessed at http://localhost:8080.
tileserver-gl-light ./data/tiles.mbtiles
The homepage of the tileserver includes a link that can be used to preview the tileset. By zooming into our extent of Ottawa-Gatineau we can see the detailed vector tiles being loaded in. Areas outside of the extent will be blank, since we didn't include that data when generating the tiles.
Using Vector Tiles in QGIS
Vector tiles in this format can be consumed by many tools and libraries, but we will demonstrate their use in QGIS here. MapTiler develops a plugin for QGIS, also called MapTiler, which enables the use of vector tiles in QGIS.
To install the plugin, in QGIS navigate to the Plugins > Manage and Install Plugins... menu. In the search bar, enter "MapTiler", select the plugin that turns up in the search results, and install the plugin. Once installed, a new "MapTiler" dropdown will be added to the QGIS Browser panel. Expanding the dropdown shows a list of preset vector tile services that can be added, however they require a MapTiler access key.
To add our own tile server, we can right click on the MapTiler dropdown and select "Add a new map...". The popup will default to adding a new map from URL, where we can give our map a name, and then enter the URL to the tile server's style JSON. We can then add the new map as a layer in our QGIS project.
The plugin treats the vector tiles as proper vector data, meaning that it can be queried like any other vector features in QGIS. When producing map layouts, the vector data can even be exported to vector graphics formats like SVG and PDF.
Styling the Map
By default the tile server we created renders the vector data in a fairly plain-looking style. Perhaps we would like to change the colours to fit our desired aesthetic, or to emphasize certain features, or even to add and remove some features from the map altogether.
The Mapbox GL style specification is used to customize the style of vector tiles. A style is a JSON document that specifies (among other things), a list of layers to be rendered. These layers derive their data from the vector data source we produced earlier, but can be filtered to only display certain features from the source. Those features can they have different layout and paint properties applied to them, and the styling can even be set up to change depending on a feature's data properties.
It is possible to write a style directly as a JSON document, however it can be complicated. Instead, we will use the Maputnik editor, which is an open-source web app for editing map styles. The editor shows the current style in a viewport, and has panels on the left which list the style's layers, and the different styling properties applied to a selected layer.
Adding / Removing Layers
Layers take some or all features from a data source (our vector tiles in this case) and apply some layout and paint properties to those features which dictate how they will be rendered. A layer can select which features will be included by specifying a source layer or filter property. A data source can have multiple data layers, and the filter can be used to further refine the selection using the filter property.
For example:
{
"id": "road_secondary_tertiary",
"type": "line",
"source": "openmaptiles",
"source-layer": "transportation",
"filter": [
"in",
"class",
"secondary",
"tertiary"
],
"layout": {},
"paint": {}
}
This defines a layer that takes the features from the transportation
source layer in the openmaptiles
data source,
and then filters specifically for features (roads in this case) that are in the secondary and tertiary road class.
The list of all available source layers and properties are defined in the OpenMapTiles Vector Tile Schema.
Layout and Paint properties
Layout and paint properties define how features in a certain layer will be displayed. These properties are specified in the Mapbox Style Specification.