Guide

Hillshading in MapLibre GL JS

Hillshading darkens the slopes that face away from an imagined light, so that hills and valleys show on a flat map. MapLibre draws it from terrain tiles, as a layer under the roads and labels. The layer, its colours for light and dark maps, and the five shading methods. Tested with MapLibre GL JS 6.13.0.

On any of our styles, add hillshade=1 to the style URL:
style: "https://api.overpass.ch/v1/YOUR_KEY/maps/styles/light.json?hillshade=1"
For your own style, add a raster-dem source and a hillshade layer, as below.
Go to:
Loading the map…
The light style with the hillshade layer from the code below.

The hillshade layer

const map = new maplibregl.Map({
  container: "map",
  style: "https://api.overpass.ch/v1/YOUR_KEY/maps/styles/light.json",
  center: [7.62, 46.12],
  zoom: 9.6
});

map.on("load", () => {
  map.addSource("terrain", {
    type: "raster-dem",
    url: "https://api.overpass.ch/v1/YOUR_KEY/maps/terrain.json"
  });
  // below the first road layer, so that roads and labels stay on top
  const firstRoad = map.getStyle().layers.find((l) => l.id.startsWith("roads"));
  map.addLayer({
    id: "hillshade",
    type: "hillshade",
    source: "terrain",
    paint: {
      "hillshade-exaggeration": 0.4,
      "hillshade-shadow-color": "#5b6470",
      "hillshade-highlight-color": "#ffffff",
      "hillshade-accent-color": "#5b6470"
    }
  }, firstRoad && firstRoad.id);
});
  • The second argument of addLayer is the layer to draw under. In our styles, and in the Protomaps styles they are based on, the road layers start with roads and come after land use and water. Parks and forests get shaded, and roads and labels stay sharp.
  • In a style JSON, add the same source to sources and the layer to layers at that position.
  • For hillshading together with 3D terrain, use a second source with the same URL for the 3D surface; MapLibre warns when both use one source. The 3D terrain guide shows both.

Colours and strength

PropertyDefaultWhat it does
hillshade-exaggeration0.5Strength, from 0 to 1. Values from 0.3 to 0.45 keep the map readable.
hillshade-shadow-color#000000Slopes that face away from the light. A grey or brown that matches the style looks softer than black.
hillshade-highlight-color#FFFFFFSlopes that face the light.
hillshade-accent-color#000000Cliffs and gorges.
hillshade-illumination-direction335Where the light comes from, in degrees; 335 is from the north-west, as on most printed maps.
hillshade-illumination-anchorviewportviewport keeps the light at the top of the screen when the map turns; map keeps it in the north.
hillshade-illumination-altitude45Height of the light, from 0 (sunset) to 90 (noon).
hillshade-methodstandardThe algorithm, below.

On a dark style, the colour of the lit slopes must stay dark too, or the mountains glow. Our dark style uses:

"hillshade-exaggeration": 0.35,
"hillshade-shadow-color": "#000000",
"hillshade-highlight-color": "#4b5563",
"hillshade-accent-color": "#000000"

The five methods

Since MapLibre GL JS 5.5, hillshade-method chooses the algorithm. Switch between them in the map above:

  • standard: the method MapLibre has always used, and the default.
  • basic: light in proportion to the angle between light and slope, like the default of gdaldem hillshade.
  • combined: shading that grows with the slope, like gdaldem -combined; flat land stays light.
  • igor: shades as little as possible of what lies beneath, like gdaldem -igor. Good under busy maps.
  • multidirectional: several lights at once. Direction, altitude and both colours then take one value per light:
map.setPaintProperty("hillshade", "hillshade-method", "multidirectional");
map.setPaintProperty("hillshade", "hillshade-illumination-direction", [270, 315, 0]);
map.setPaintProperty("hillshade", "hillshade-illumination-altitude", [30, 30, 30]);
map.setPaintProperty("hillshade", "hillshade-highlight-color", ["#ffffff", "#ffffff", "#ffffff"]);
map.setPaintProperty("hillshade", "hillshade-shadow-color", ["#5b6470", "#7a8290", "#5b6470"]);

If you can, put the lists into the layer definition from the start. Changing a single value into a list later makes MapLibre warn that it cannot animate between arrays of different lengths.

Tiles and limits

  • The terrain tiles go up to zoom 10. Closer in, MapLibre enlarges them, so hillshading adds few tiles: the first view of Valais above, 1280 × 720 pixels at zoom 9.6, loaded 16 tiles in all, vector and terrain. Every tile counts once; every account includes 100'000 a month.
  • The terrain comes from a grid with 90 m spacing. It shows hills, valleys and mountains, and is too coarse for small slopes in towns.
  • The sea is flat at 0 m. For a shaded sea floor, see the nautical map guide. For lines of equal height, see contour lines.