This document describes how to generate a fully offline world vector basemap for QGIS.
The Protomaps project offers a convenient way to generate a vector basemap from OpenStreetMap data using the Protomaps CLI.
A full planet file is roughly 120 gigabytes, including zoom levels from 0 to 15. Each additional zoom level roughly doubles the size of the file.
You probably don't need data with such a level of detail, so you can use the extract CLI command to create a file of
the size of your choice by adjusting the maximum zoom level:
| Zoom level | File size | Level of detail |
|---|---|---|
| 6 | 40 MB | Regions, major roads |
| 7 | 180 MB | |
| 8 | 500 MB | Secondary roads |
| 9 | 1.5 GB | |
| 10 | 3.5 GB | Major streets |
| 11 | 7.5 GB | |
| 12 | 17 GB | |
| 13 | 32 GB | Buildings |
Example with level 8:
docker run -v $(pwd):/data protomaps/go-pmtiles extract https://build.protomaps.com/20260206.pmtiles --maxzoom 8 /data/world-20260206-maxzoom8.pmtilesQGIS unfortunately can't load .pmtiles files directly1, so we need to convert it to a vector tile format that
QGIS natively handles: MBTiles.
This can be done using the tile-join command of tippecanoe2.
Example with the level 8 PMTiles previously generated:
docker run -v $(pwd):/data --entrypoint /usr/local/bin/tile-join versatiles/versatiles-tippecanoe -o /data/world-20260206-maxzoom8.{mbtiles,pmtiles}This command can take a while to run, be patient! It will generate a file of roughly the same size as the PMTiles file.
We now have a vector basemap .mbtiles file that can be loaded in QGIS, but it's not styled yet.
The Protomaps project provides MapLibre GL styles in various "flavors" that can be used to style their PMTiles basemaps.
As explained in the docs, their styles can
be exported as JSON files by choosing the flavor and clicking on Get style JSON in
maps.protomaps.com. The white or black flavors can be good choices for a basemap.
The JSON then needs to be tweaked in order for text labels to be rendered correctly in QGIS3:
import json
target_ids = [
"places_country",
"places_locality",
"places_region",
"places_subplace",
"roads_labels_major",
"water_label_lakes",
"earth_label_islands",
"water_label_ocean",
"roads_labels_minor",
"water_waterway_label",
]
input_file = "./style.json"
with open(input_file) as f:
style_data = json.load(f)
for layer in style_data["layers"]:
if "id" in layer and layer["id"] in target_ids:
if "layout" in layer and "text-field" in layer["layout"]:
layer["layout"]["text-field"] = ["get", "name"]
with open(input_file, "w") as f:
json.dump(style_data, f, indent=2)Drag and drop the .mbtiles file in QGIS, then apply the style JSON file to the layer:
- Double click on the layer to open its properties
- Go to the
Symbologytab - Click on the
Style > Load Stylemenu button - Select the JSON file and click
Load Style
That's it!
Footnotes
-
PMTiles can currently be loaded in QGIS if served by a tile server (e.g. with the CLI
pmtiles serve). It may be possible to load them directly in QGIS in the future. See PMTiles issue for more information ↩ -
This conversion could maybe also be done with GDAL/OGR? I haven't tried. ↩
-
This is because QGIS uses a different syntax for text label filters than MapLibre GL. The
style.jsonfile provided by Protomaps uses a syntax with complex filters with fallbacks on names in different languages that QGIS can't handle. The python script simply replaces these filters with the defaultnameof the features. ↩