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
addLayeris the layer to draw under. In our styles, and in the Protomaps styles they are based on, the road layers start withroadsand 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
sourcesand the layer tolayersat 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
| Property | Default | What it does |
|---|---|---|
hillshade-exaggeration | 0.5 | Strength, from 0 to 1. Values from 0.3 to 0.45 keep the map readable. |
hillshade-shadow-color | #000000 | Slopes that face away from the light. A grey or brown that matches the style looks softer than black. |
hillshade-highlight-color | #FFFFFF | Slopes that face the light. |
hillshade-accent-color | #000000 | Cliffs and gorges. |
hillshade-illumination-direction | 335 | Where the light comes from, in degrees; 335 is from the north-west, as on most printed maps. |
hillshade-illumination-anchor | viewport | viewport keeps the light at the top of the screen when the map turns; map keeps it in the north. |
hillshade-illumination-altitude | 45 | Height of the light, from 0 (sunset) to 90 (noon). |
hillshade-method | standard | The 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 ofgdaldem hillshade.combined: shading that grows with the slope, likegdaldem -combined; flat land stays light.igor: shades as little as possible of what lies beneath, likegdaldem -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.