QGIS 3 and Vector map tiles

If you’ve ever tried printing a map from the Open Street Map tile service (available in QGIS through QuickMapServices plugin) you’ve experienced the annoyance of having all your labels come out tiny. In other words, you see this on the screen…

But then you get this out of your 300 dpi print layout….

The reason those labels and icons come out so small with raster tiles is that your screen display is something like 96 pixels/inch, but the print layout has to pack them down to 300 pixels/inch. The print layout also winds up asking for tiles at a higher zoom level. Text labels and icons, like everything else, are “baked” into the raster tiles at a reasonable size for display at 96 dpi. At 300 dpi, everything is much smaller.

In 2018 I described, at the end of a post, a way to work around this, but now there’s a better option. If you work instead with vector tiles, the text labels are not baked into the tile: they are rendered by QGIS. This means that if the label is supposed to be 2.5 mm high, it’ll be 2.5 mm high no matter what dpi you export at out of the print layout.

QGIS 3.14 and above can handle vector tile servers as data sources. This means the server sends tiles of vector data, which are then styled at the user’s end by QGIS.

How they work

Vector map tiles (VMTs) were not developed for GIS use. Their main use is for people viewing maps through browsers. You commonly have a map that’s been embedded in a web page that includes the javascript libraries for mapbox rendering, such as here.

But vector tiles offer the cartographer a lot. As raster tiles, OpenStreetMap data was ideal for areas where you just could not get decent data—if, say, you were mapping the streets of a city in Turkmenistan. But now, as vector tiles, this data allows you to control colours, line weight, font, and, of course the visibility of elements. Imagine your own version of OpenStreetMap, with pink highways, no railroads and emphasized parks, and you begin to get the idea.

There are multiple formats for tiles, but here we’ll just look at the Mapbox vector tiles format, since that is what QGIS can support.

Because vector map tiles are designed to be viewed over the internet, the most basic principle of VMTs is that you typically to have a tile server, which is firing off vector tiles of data, and a GL style file that understands what data layers are delivered in this server’s tiles, and tells the browser how to render that data. Often these are at different URLs.

The URLs of vector tile servers look like this:

The z, y and x in curly brackets get replaced by the zoom level, row and column, and the data comes as a PBF (Protobuf) file or a MVT (Mapbox vector tile) file.

The GL style file, on the other hand, is a text file in JavaScript Object Notation (JSON). It might begin like this, with information on the sources for tiles, sprites and glyphs (VMT lingo for markers and fonts):

{
  "version": 8,
  "name": "Positron",
  "metadata": {...  },
  "sources": {
    "openmaptiles": {
      "type": "vector",
      "url": "https://api.maptiler.com/tiles/v3/tiles.json?key={key}"
    }
  },
  "sprite": "https://openmaptiles.github.io/positron-gl-style/sprite",
  "glyphs": "https://api.maptiler.com/fonts/{fontstack}/{range}.pbf?key={key}",

and then it will continue with a list of the layers in the tiles, and how they should be styled.

  "layers": [
    {
      "id": "background",
      "type": "background",
      "paint": {"background-color": "rgb(242,243,240)"}
    },
    {
      "id": "park",
      "type": "fill",
      "source": "openmaptiles",
      "source-layer": "park",
      "filter": ["==", "$type", "Polygon"],
      "layout": {"visibility": "visible"},
      "paint": {"fill-color": "rgb(230, 233, 229)"}
    },
    {
      "id": "water",
      "type": "fill",
      "source": "openmaptiles",
      "source-layer": "water",
      "filter": [
        "all",
        ["==", "$type", "Polygon"],
        ["!=", "brunnel", "tunnel"]
      ],
      "layout": {"visibility": "visible"},
      "paint": {"fill-antialias": true, "fill-color": "rgb(194, 200, 202)"}
    },     ...

The styling information here is written in something called Mapbox GL style, and here are some GL Style file URLs:

Vector tiles are relatively new, and almost no one gives them away for free, but we do have a few heroic companies offering this. Most require API-keys or access-tokens, but these are frequently free.

The first question you might have is what you get if you ask for vector tiles from the tile server, but don’t combine them with a GL Style file. VMTs without a style file aren’t useless—but what you get is data where all points share a single style, as do all lines and polygons.

Data from the Nextgen vector tile server without styling

A simple VMT service brought in using QGIS3’s native abilities

Our best candidate at the moment for free OpenStreetMap (OSM) data delivered through vector tiles is the ESRI OSM vector tile layer. (Thank you ESRI!). The tile server is here:

https://basemaps.arcgis.com/arcgis/rest/services/OpenStreetMap_v2/VectorTileServer/tile/z/y/x.pbf

and the style file is here:

https://www.arcgis.com/sharing/rest/content/items/3e1a00aeae81496587988075fe529f71/resources/styles/root.json?f=pjson

We can add this tile server to QGIS by

  1. opening the Browser panel
  2. Right-clicking the new Vector Tiles category and choosing New ARCGIS vector tile service connection
  3. Filling it out like this:

Notice that the Service URL ends with “VectorTileServer.” There’s no “tile/{z}/{y}/{x}.pbf.” QGIS will append that automatically before making a tile request.

When we add this layer to our map canvas, and zoom to Baku, Azerbaijan at 1:8,028 scale, we get this pleasing map that looks remarkably like OpenStreetMap:

However, we also get a pile of errors, under the banner “Vector tiles: Style could not be completely converted.” If you click on “Details,” some will be like this:

Referenced font Arial Unicode MS Regular is not available on system

You can repair these font errors by installing the missing font – or simply accepting the local font that has been substituted.

But there are also a pile of errors along the lines of

amenity area/bowling alley: Could not retrieve sprite 'amenity area/bowling alley'

I will explore how to solve these sprite problems below. First let’s talk about what you’ve got so far.

Styling the vector tile layers

If I open the styling panel on the right of the map I get something truly new.

Wow, that’s the longest list of rule-based styling I’ve ever seen!

Can I literally uncheck or change the style for every type of layer? Are these layers within a layer?

Yes and yes. Vector map tiles contain many layers. (The set of layers is called the schema of a tile service.) Out of each layer (e.g., “landcover”), features are selected using a filter, which is quite similar to a rule in rule-based styling. For example, in the image above there is a specific fill style for features in the landcover layer for which _symbol = 48,and the zoom is 9 or higher. It’s called landcover/park.

I can uncheck landcover/park and get a map without parks…

Or I can double-click on the sample fill swatch and change the parks to a pink/white gradient fill.

In fact I can apply any QGIS style to these polygons: shapeburst fill, line fill, marker fill… I don’t have full control—I can’t change the min and max zooms that define this style, or the filter rule—but within the predefined filters, the possibilities are mind-boggling.

Once I like the way in which I have tweaked the styling, I can save the style for this layer to a .qml file. (Open the layer’s Properties, find the Style button, and choose Save Style…) This will allow me to apply this custom styling to the same vector tile layer, but in another QGIS project.

My powers over the styling of the VMTs are clumsy—a GL Style file typically defines scores of styles, and if I want to change, say, all my water styles from blue to grey, I will have to do that to each one individually. But it’s still very powerful.

There are a few other things to point out. Note at the bottom of the styling panel you have a checkbox for Visible rules only. Visible rules only cuts out styles that are not displayed at the current zoom. (It does not remove styling for features not visible in the current map canvas.)

The current zoom is also displayed there. This is always shown as an integer, but with vector map tiles you are not restricted to integer zoom levels. There’s no “Zoom to Native Resolution (100%)” like there is when you right-click a raster tile layer. The zoom level shows as 14, but the map canvas is at 1:10,000, which is somewhere between 1:17,061 (the break point for vector tiles changing from zoom 13 to 14) and 1:8,530 (the break point for vector tiles changing from zoom 14 to 15). If you do the math, you can conclude we’re at something like zoom level 14.17. This might make you queasy! And, welcome to the world of vector tiles.

[Note that zoom levels for vector tiles are one more than the zoom levels for typical raster map tiles (e.g., Google Maps, OSM ). This is a consequence of MapBox vector tiles being 512 pixels on a side. As well, I have a suspicion that, in QGIS, the detail shown on ESRI OSM vector tiles at zoom level 14 is the same as shown on OSM raster tiles at zoom level 13. But I’m not quite sure why this is.]

Styles are implemented in order, from the top of the list down, and you can drag them to reorder.

Finally, it’s important to understand that the styling rules you see here in QGIS are not the GL Style document itself. They are a translation of that document. You cannot (at, least, as far as I know) translate back from QGIS styling to a GL Style file.

Labels

If you move to the labelling tab in the style panel, you see this:

So, you also have extensive control over the labelling. Double click on a label’s style swatch, and change the font, size, colour… all of the usual label parameters are available.

For example, if I would like street names to be blue (bold-italic) and subway stations to be labelled in red, I can just do it by altering three styles.

Finally, note that while you can’t open an attribute table on your vector map tiles layer, you can use the Information tool. You will get results for every single layer under the mouse click.

I can now print at 300 dpi from this, and I get just what I see on the screen!

Sprites

All that might have been enough for you, and now you’re off to try customizing OpenStreetMap yourself. However, if you’re interested in fixing those sprite errors we encountered above, continue reading.

When you add a vector tile service to QGIS you frequently get this:

When you check out the Details, many of the errors refer to “could not retrieve sprite.”

At the same time, in the QGIS’s Network Log window, you’ll see something like this:

2020-12-31T20:27:24     WARNING    Error transferring https://cdn.arcgis.com/sharing/rest/content/items/684018c92e9b424ca8e900da8dad8b23/resources/styles/../sprites/sprite@2x.json - server replied: Not Found
2020-12-31T20:27:24     WARNING    Error transferring https://cdn.arcgis.com/sharing/rest/content/items/684018c92e9b424ca8e900da8dad8b23/resources/styles/../sprites/sprite.json - server replied: Not Found

So what’s going on?

If I paste the URL for the GL Style file into my browser (I’m using Firefox), the result is that whole code for the styling gets delivered to me in an unformatted blob.

It’s a bit dense reading. But near the top I can pick out this piece:

"sprite": "https://cdn.arcgis.com/sharing/rest/content/items/3e1a00aeae81496587988075fe529f71/resources/styles/../sprites/sprite"

Sprites, in vector map tile parlance, are markers. QGIS needs to use this URL look up the sprite image (a PNG file with all the sprites packed into it) and its partner sprite JSON file (which explains which piece of the image a given sprite is). But, for QGIS, the “styles/..” part of this particular URL is confusing. We might understand it as meaning descend into the styles sub-directory and then pop back up out of it, but QGIS doesn’t know this.

I can make a better address for the sprites by saving the GL Style file locally, editing it so the URL of the sprites is simpler, and then telling QGIS to now use my local version of the GL Style file.

I save the GL Style file locally by selecting all the text out of my browser, and copying and pasting into my text editor. Then I change the sprite URL by removing the “styles/..” part, so it now reads

"sprite": "https://cdn.arcgis.com/sharing/rest/content/items/3e1a00aeae81496587988075fe529f71/resources/sprites/sprite"

Once I’ve saved the file as ESRI_OSM_Style.json, I return to QGIS, where I edit the Vector Tiles connection I made for this server, and specify this new file as the “Style URL.” I could post it on a server somewhere and give a http:// URL to it, but, with Ubuntu, I can also just use the file:// protocol:

Sprites make quite a difference at high zoom levels.

No sprites, zoom level 17
With sprites, zoom level 17

A few final points

There are three different types of support for vector tiles in QGIS. The original one was the Vector Tiles Reader plugin, which received the vector tiles, converted them to geoJSON data, and made a different layer for each kind of features in the tiles. There is the new native support, which I have used here: it receives the tiles and applies filters, or rules, to style the many different kinds of features in them. Finally, there is the MapTiler plugin, which has a great deal of potential but requires a free account with MapTiler/OpenMapTiles, and this has an unfortunate data cap of 100,000 tile requests/month. (It seems like a lot, but while learning to use and style vector map tiles, I went through my 100,000 tiles in eleven days.)

The QGIS implementation of vector tiles permits you to add “New generic connection” if you have URLs for the server and the GL Style file. Yet outside of MapTiler and ESRI I have found precious few servers that also provide GL Style files. Here’s a quick survey:

  • The city of Wien (Vienna)—the data is limited to Austria. [Server URL] [GL Style URL]
  • Ordnance Survey UK—the data is limited to the U. K. and requires a free API key. [Server URL to which you have to append your free API key] [GL Style URL to which you have to append your free API key] Because of the API-key, QGIS does not yet seem to be able to make the request for the sprites correctly.
  • MapTiler/OpenMapTiles (OSM data)—beware the 100,000 tile limit. (Literally every time you zoom, pan or resize the screen you can trigger a tile request.) [Server URL to which you have to append your free access key] [GL Style URL for the “Basic” style]
  • Nextzen (OSM data)—lovely data, but I haven’t found a GL Style file. [Server URL to which you append your API key.]
  • ESRI generously offers another vector tile server based on more data sources than just OpenStreetMap. [Server URL] It comes with many different styles which you can find here. To obtain the GL Style file for one of these, you typically Open the style in Map Viewer and then in the upper right is a button called View style. Copy the link out if it and use it, as we did above, in the Style URL field. (Generally speaking, you’ll have to also do the same hack with the sprites as I showed above.)
  • Mapbox, if you can decode their mysterious URLs (mapbox://mapbox.mapbox-streets-v11), serves up vector tiles, and the public access key is free (without data limits), but I have not been able to find a GL Style file for its schema that QGIS can fully translate. [Server URL for Mapbox-streets-v8] [GL Style URL for MapBox-streets-v11]
  • Thunderforest has nice vector tile servers, such as their Transport layer, and their Outdoors layer, that you can have access to with a free API key—but I have not been able to find GL Style files for the schemas of these servers. [Server URL to which you have to append your free API key]

I understand that converting data to vector tiles, and keeping it up to date, costs money, so I don’t begrudge these companies charging fees or imposing limits. On the other hand, I really appreciate those which don’t!

As time passes there will no doubt be more vector map tile servers, and the way schemas and styling work will change. Nonetheless working with vector map tiles is addictive, and it will be exciting to watch this evolve.