Installing MapLibre GL with TileServer GL

Implementation Notes for Jura Mountains mapping

Jura Mountains mapping | Terrain3d map | Main map

In preparation for serving vector tiles for high-resolution elevation mapping together with terrain raster tiles, we need to set up a vector tile server on our Ubuntu 18.04 box.

TileServer GL for the server-side rendering of vector map tiles by Mapbox GL Native was designed to use mapbox-gl.js. With licencing changes, mapbox-gl users have migrated to MapLibre GL, with the comment "If you depend on mapbox-gl directly, simply replace mapbox-gl with maplibre-gl in package.json".

A simple test to run maplibre-gl-js (as the successor for mapbox-gl-js) with tileserver-gl has been developed.

TileServer GL with MapLibre GL cannot currently be installed using Node. Docker is needed.

In the case of say Ubuntu 18.04 with Docker installed, to install TileServer GL with MapLibre GL, simply:

  • git clone
  • cd poc-maplibre-gl-js-with-tileserver-gl

in console in a home, working or temporary directory. Then edit docker-compose.yaml to change ports, if necessary, and to set the latest TileServer GL version (currently 3.1.1). One can use this docker-compose.yaml for example:

version: "3.4"




- tileserver


context: ./map-client

dockerfile: Dockerfile


- "/usr/app/node_modules"

- "./map-client:/usr/app"


- "3005:3005"


image: maptiler/tileserver-gl:v3.1.1


- "./tileserver-gl:/data/"

command: ["-p", "80", "-c", "/data/config.json"]


- "8189:80"

Then run:

  • docker-compose up

In earlier versions of poc-maplibre-gl-js-with-tileserver-gl, Docker threw an error ("Service 'mapclient' failed to build: When using COPY with more than one source file, the destination must be a directory and end with a /"). This could be fixed. The poc now uses MapLibre GL version 1.13.0-rc.4 without maplibre-3d. Changing the map-client package.json to MapLibre GL 2.2.1 - for the latest version of MapLibre GL created some difficulty in updating the map-client package-lock. json on running:

  • npm install

As we only need TileServer GL to serve tiles we have not tried using either either MapLibre GL 2.21 or the 3d-terrain fork with TileServer GL.

A default installation of TileServer GL is given by running:

  • docker run --rm -it -v $(pwd):/data -p 8188:8080 maptiler/tileserver-gl:v3.1.1

The default zurich_switzerland.mbtiles are automatically downloaded on running Docker for the first time.

Greater flexibility is available by using a config.json in the tileserver-gl directory and running:

  • docker run --rm -it -v $(pwd):/data -p 8188:8080 maptiler/tileserver-gl:v3.1.1 -c config.json --verbose

In our case the tileserver-gl directory is located as follows:

  • /home/user/tileserver-maplibre/tileserver-gl

Adjustments to config.json are described here and here. In our case we use TileServer GL to serve:

  • terrain slope (slope greater than 60 degrees to identifying cliffs) vector tiles;
  • terrain-rgb raster tiles (described below) stored as terrain-rgb.mbtiles.

mbtiles files are stored in tileserver-gl directory along with config.json. We also keep the TileServer basic preview to make sure that TileServer is running properly. Our tileserver-gl directory therefore contains config.json and "fonts", "styles" and "sprites" directories and the mbtiles files.

So in the config.json, we have "root":"/data" and the data section is:

  • "data": {"dynamic": {"mbtiles": "terrain.mbtiles"}, "slope": {"mbtiles": "slope.mbtiles"},"juramap": {"mbtiles":"rio.mbtiles"}}

This means that the Docker-installed TileServer GL found at has a "DATA" section with sub-sections with identifiers labelled as "dynamic", "slope", and "juramap". URLs are of the form:

The TileJSON link for the slope vector data shows that the data layer is called "allsmooth_slope_1sep22 ". This is the important information that is needed for setting up a maplibre-gl client for vector data.

Using TileServer GL to provide:

  • cliffs (using vector tiles) is shown here.
  • GLOperations shaded relief (using raster tiles) is shown here.
  • Rasterio-generated rgb-terrain shaded relief (using raster tiles) is shown here

TileServer GL with Mapbox GL

The zurich_switzerland.mbtiles can also be found in the working directory when one installs TileSever GL in the normal way to use Node (as an alternative to using Docker).

For a fresh Node installation of TileServer GL with mapbox-gl, first make sure one has the requirements (for Ubuntu 18.04 in our case):

  • sudo apt-get install -y software-properties-common protobuf-compiler pkg-config libcairo2-dev libjpeg-dev libgif-dev git libgl1-mesa-glx build-essential g++ curl

It is also necessary to use Node version 10. Then run in console:

  • git clone
  • cd tileserver-gl
  • sudo npm install -g --unsafe-perm

A simple way to run the server is:

  • cd src

and to set the port in tileserver/src/main.js and run using:

  • node main.js

TileServer GL will display its landing page with a link to the default zurich_switzerland.mbtiles located in the working directory (tileserver/src).

Raster elevation (terrain-rgb) tiles

The raster GL tiles we currently link to using TileServer GL with MapLibre GL are terrain-rgb tiles as a first step towards a more sophisticated hill-shading than the simple overlay of a Digital Elevation Model (DEM) that we use for the main Jura Mountains map.

Our ultimate aim is to explore the use of adjustable vector shading as has been discussed (and proposed as long ago as 2012) to enhance hillshading. Currently, for the Mapbox Terrain version 2 tileset, the hillshade layer contains polygons that can be styled to display the shaded relief of hills (i.e., it is posterised hillshading based on vector shapes).

An interim step is to explore adjustable raster hillshading using terrain rgb raster tiles that contain Digital Elevation Model (DEM) elevation data encoded into a RGB color model (maybe originally attributed to Mapbox). The elevation is generally (but not always) calculated as:

  • height = -10000 + ((R * 256 * 256 + G * 256 + B) * 0.1)

for the red, green and blue bands.

There are several recipies for creating terrain RGB tilsets (see the National Scenic Trails Guide, for example).

A. Initial trial using GLOperations

In our case we used Qgis to merge the Swisstopo swissALTI3D two-metre DEM product for the Jura Mountains. After reprojection to Web Mercator (including setting the no-data value to -999999), the merged geotiff was exported as a geotiff. Qgis's Grass r.mapcalc.simple was then used to calculate the terrain RGB raster which was again exported as a geotiff. The tileset was created as mbtiles using Qgis's GenerateXYZ Tiles.

Hillshading was set up using the Leaflet.Tilelayer.GLOperations javascript plugin with the terrain RGB mbtiles served using TileServer GL with MapLibre GL setup described above.

GLOperations used a white colour for the colorValue that is the hillshade's background, and the offset was set to transparent:

  • const colorScale = [{ offset: 0, color: 'transparent'},{ offset: 0.0, color: '#ffffff'},];

The nodataValue of -999999 is transparent by default.

Simple hillshading (i.e., hillShadeType :'simple') with a slope scale of 0.001 gave the elevation map at the default zoom level of 12 (a coloured DEM generated using Qgis's gdal2tiles and having an opacity of 0.5 overlays the hillshade).

It is rare to see terrain RGB tiles displayed at zoom levels above 12 (a zoom level limit of 10 is common). This is presumably because tile borders become noticeable at this zoom level (an artifact that can be removed by rendering a group of tiles and extracting one). More disturbing is the banding that is evident a zoom levels of 12 and above. To avoid this effect, some recent terrain RGB hillshading seems to use small slope scales that lead to low contrast levels and presumeably suppress artifacts such as banding (see, for example, the National Scienic Trail Guide's map at zoom level 13 where hillshading was generated using a DEM with a 30-m resolution).

B. Using the rasterio rgbify plugin and the MapLibre GL terrain-3d fork

A hillshade displayed using the Maplibre GL terrain-3d fork and generated using terrain-rgb mbtiles served by our TileServer GL is shown here (terrain3djura). The mbtiles were produced using the rasterio rgbify plugin (see reference).

For desktop use, we in fact prefer using standard OpenStreetMap raster tiles and a raster hillshade DEM ( see the JuraMap OSM + relief layer) with MapLibre GL.

Maplibre GL terrain-3d

Back in February 2022, a few words about the Maplibre GL terrain-3d fork. A terrain3d branch has been added to a maplibre-gl-js fork. As pointed out by contributors, the branch's maplibre-gl.js and maplibre-gl.css can be built in the usual way, as described by maplibre-gl-js (for Ubuntu 18.04, check that you have Node version 14 installed; clone the repository; npm install; npm build-prod; npm build-css; maplibre-gl.js and mapibre-gl.css are created in the dist directory).

To test the terrain-3d Maplibre GL fork, a simple web app can be set up using the terrain3d MapLibre GL .js and .css files and the terrain3d terrain.html file. Courtesy of the terrain 3d developer (prozessor13 and Tourspring GmbH), we have done this (see terrain3d map).

MapLibre GL.js also supports dynamic hillshading (see dynamic terrain3d map) where we used the UK Ordinance Survey demo and not the MapTiler Mapbox demo (note that labelling is displaced on tilting the map).

In both these cases we used terrain-rgb tiles.

As of May 2022, now that the second generation of Maplibre GL has incorporated 3d terrain (see news item), one needs to look again at how rgb-terrain files are handled.

Vector data mbtiles

Normally, one needs to load vector data (e.g., polygons describing areas with high slopes that may represent cliffs - see above) into the JOSM OpenStreetMap editor and Qgis. We have found that the most reliable approach is to generate the polygons in Qgis and save them as a geojson file. The geojson is then converted to mbtiles file using Tippecanoe.

For example, the swisstopo DEM is converted to a slope map using the GDAL Raster Analysis slope module in Qgis. Raster calculation in Qgis is then used to set slopes below say 60 degrees to zero.The map is then converted to a vector format using the Qgis Raster Conversion polygonise tool, reprojected if necessary to EPSG 4326 and exported ("Export -> Save features as ...") as a geojson.

However, while the resulting mbtiles files work with Maplibre GL and Qgis, this is not always the case for JOSM. This needs looking into.

31 August 2022