Self-hosted regional vector map

Web maps can be created using either raster or vector tiles. Historically, raster tiles were the standard method for rendering maps in the browser. These pre-generated image files can be easily integrated into a webpage using libraries like Leaflet. In contrast, vector tiles store raw geospatial data, which is rendered directly in the browser based on a style definition. This approach offers significantly greater flexibility for customizing a map’s appearance.

While vector tiles are highly versatile, certain types of maps, such as satellite imagery, are still rendered as raster tiles due to their nature. Notably, MapLibre GL JS, a popular open-source library for rendering vector tiles, also supports rendering raster tiles, making it a versatile choice for various map applications.

The encoding of vector tiles follows the Mapbox Vector Tile Specification. Within each tile, geographic features are represented as coordinates along with tags that define their properties. To achieve high compression, the format is encoded using Google Protocol Buffers. It’s also worth noting that MapLibre is actively exploring improvements to this format through its own MapLibre Tile Specification, which is currently a work in progress.

The full example is available within a GitHub repository for a detailed investigation.

Preparing vector tiles

The basis for the vector map is comming from OpenStreetMap data. OpenStreetMap data can, e.g., be downloaded from geofabrik.de as binary encoded pbf data. For this article the town Kufstein should be visible inside the map, so a dump of Austria should be downloaded: Austria.

The downloaded austria-latest.osm.pbf file contains all geographical data from OSM for Austria. However, much of this data is not relevant for the map we aim to create. Including all OSM data would result in overly complex and overloaded vector tiles. To address this, a schema is used to extract and transform only the essential data needed for a clear and visually appealing map. Several schemas are available for this purpose, such as OpenMapTiles, Shortbread, and vector tilesets from Thunderforest. These schemas define which OSM tags are included in the final vector tiles.

Tilemaker is a tool for transforming OSM extracts into vector tiles. The tool can be configured with a schema, such as OpenMapTiles, to define how the data is processed and included in the tiles. To speed up the tile generation process, the relevant area around Kufstein is extracted from the larger austria-latest.osm.pbf file beforehand. This can be accomplished using a tool like osmium by specifying a bounding box (-b) and an output filename (-o):

osmium extract -b 12.156623,47.559905,12.188230,47.602054 austria-latest.osm.pbf -o kufstein.pbf  

The resulting kufstein.pbf file is only 1.1 MB in size, a significant reduction from the approximately 800 MB of the full Austria dump. In the next step tilemaker is used for the generation of the vector tiles. Tilemaker is available as a Docker image, making it simple to use. The following command does the transformation for the area of Kufstein:

docker run -it --rm -v $(pwd):/data ghcr.io/systemed/tilemaker:master --input /data/kufstein.pbf --output /data/tiles/ --config /data/config-openmaptiles.json --process /data/process-openmaptiles.lua --bbox 12.156623,47.559905,12.188230,47.602054

In the command, the current directory is mounted as a volume ($(pwd):/data) to provide both input files and a location for the output. The generated vector tiles are stored in the tiles/ directory. Tilemaker allows customization through a config file and a Lua process script, which control how the tiles are generated from OSM data. Predefined configurations for the OpenMapTiles schema are available in the Tilemaker GitHub repository.

Displaying vector tiles

The default library for rendering vector tiles in the browser is MapLibre GL JS. This library leverages WebGL to provide efficient and performant map rendering. In addition to the vector tiles generated in the earlier step, a style definition is required to determine how specific geographical features, such as streets, forests, or lakes, are visually represented on the map. For instance, a style definition might specify that forest areas are filled with a light green color and outlined with a dark green border.

In MapLibre, the style is defined using a JSON document that adheres to the style specification. Crafting a map style from scratch can be a complex task, but many pre-built styles are available, such as those listed on OpenMapTiles Styles. In this example, the MapTiler Basic style was used for the Kufstein map.

Once the style and tiles are integrated into a frontend project, initializing a MapLibre map is straightforward. Here’s an example code snippet:

import maplibregl, { AttributionControl, NavigationControl } from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';

const map = new maplibregl.Map({
    container: 'map', 
    style: 'style.json',
    maxBounds: [12.1566, 47.5599, 12.1882, 47.6021],
    zoom: 12,
    maxZoom: 18,
    minZoom: 10,
    attributionControl: false
});

map.addControl(new AttributionControl({
    compact: true,
    customAttribution: ['© MapTiler', '© OpenStreetMap contributors']
}));

map.addControl(new NavigationControl());

The critical element in this snippet is the style.json file. This file not only defines the visual styling of the map but also specifies where to fetch tiles, glyphs, and sprites. The style.json used in this example was slightly adapted to suit the project’s needs and can be found here.

Conclusion

Vector maps offer remarkable flexibility, as rendering is performed directly in the browser and can be fully customized through a style definition. It’s also impressive to see the range of open-source tools available for building custom self-hosted maps.

However, rendering vector maps in the browser is likely more resource-intensive than serving raster maps, as the client must handle the rendering process. This could have a noticeable impact on the battery life of smartphones, which although requires further investigation. Consider a scenario where a map is highly popular, with 100,000 regular users. If the energy consumption of vector maps is significantly higher compared to raster maps, opting for raster tiles could be a more energy-efficient solution on a large scale. Balancing performance, flexibility, and environmental impact is an essential consideration when choosing between vector and raster maps.