Skip to content
Make with

MapLibre GL JS 6.18

Blend modes

From version 6.18, MapLibre GL JS can blend a fill, line or raster layer into the map beneath it, the way layers blend in CSS and design tools. Land cover keeps the relief beneath it, night falls over the globe, streets glow, and old maps print onto new ones. Every map here is live, with its code.

Loading the map…

  • multiply

Land cover that keeps the relief

A hillshade usually goes on top of the land cover, half see-through, and grays out its colors. Here it goes underneath, and the land cover is multiplied onto it, like ink on paper. Forests, meadows and glaciers keep their colors, and every ridge keeps its shadow, on 3D terrain too.

  • A hillshade underneath
  • 3D terrain
  • OpenFreeMap land cover
Map code

Plain MapLibre GL JS 6.18, ready to paste into a module. createMap("map") makes the map in the element with the id map. With a bundler, point MapLibre at its worker first, as in the quick start.

import * as maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";

// The land cover's colors, by OpenMapTiles class.
const landColor: maplibregl.ExpressionSpecification = [
  "match",
  ["get", "class"],
  "wood",
  "#9fc68a",
  "grass",
  "#d3e6ad",
  "farmland",
  "#eee6b8",
  "wetland",
  "#c4ddd0",
  "sand",
  "#f0e3b5",
  "rock",
  "#e2dcd2",
  "ice",
  "#e6f1f6",
  "#dfe9c8",
];

export function createMap(container: string | HTMLElement) {
  return new maplibregl.Map({
    container,
    center: [7.98, 46.56],
    zoom: 11.9,
    pitch: 60,
    bearing: -20,
    style: {
      version: 8,
      glyphs: "https://tiles.openfreemap.org/fonts/{fontstack}/{range}.pbf",
      sources: {
        openmaptiles: { type: "vector", url: "https://tiles.openfreemap.org/planet" },
        // Mapterhorn's elevation twice, once to shade and once to lift the map into 3D.
        relief: { type: "raster-dem", url: "https://tiles.mapterhorn.com/tilejson.json" },
        terrain: { type: "raster-dem", url: "https://tiles.mapterhorn.com/tilejson.json" },
      },
      terrain: { source: "terrain", exaggeration: 1.2 },
      layers: [
        { id: "land", type: "background", paint: { "background-color": "#f5f2ea" } },
        // The relief comes first, under everything else.
        {
          id: "hillshade",
          type: "hillshade",
          source: "relief",
          paint: {
            "hillshade-method": "igor",
            "hillshade-shadow-color": "#2f3b4d",
            "hillshade-highlight-color": "#ffffff",
          },
        },
        // Land cover on top of it, multiplied. Like ink on paper, it tints the relief instead of hiding it.
        {
          id: "landcover",
          type: "fill",
          source: "openmaptiles",
          "source-layer": "landcover",
          paint: { "fill-color": landColor, "fill-layer-blend": "multiply" },
        },
        {
          id: "landuse",
          type: "fill",
          source: "openmaptiles",
          "source-layer": "landuse",
          filter: [
            "in",
            ["get", "class"],
            ["literal", ["residential", "suburb", "village", "commercial", "industrial"]],
          ],
          paint: { "fill-color": "#e9dccf", "fill-layer-blend": "multiply" },
        },
        // Lakes and rivers are flat, so they cover the relief.
        {
          id: "water",
          type: "fill",
          source: "openmaptiles",
          "source-layer": "water",
          paint: { "fill-color": "#9cc9e6" },
        },
        {
          id: "rivers",
          type: "line",
          source: "openmaptiles",
          "source-layer": "waterway",
          paint: {
            "line-color": "#9cc9e6",
            "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.6, 15, 3],
          },
        },
        {
          id: "roads",
          type: "line",
          source: "openmaptiles",
          "source-layer": "transportation",
          filter: [
            "in",
            ["get", "class"],
            ["literal", ["motorway", "trunk", "primary", "secondary", "tertiary", "minor"]],
          ],
          paint: {
            "line-color": "#ffffff",
            "line-width": ["interpolate", ["linear"], ["zoom"], 10, 0.6, 15, 4],
          },
        },
        {
          id: "railways",
          type: "line",
          source: "openmaptiles",
          "source-layer": "transportation",
          filter: ["==", ["get", "class"], "rail"],
          paint: { "line-color": "#7a6f66", "line-width": 1, "line-dasharray": [3, 2] },
        },
        {
          id: "peaks",
          type: "symbol",
          source: "openmaptiles",
          "source-layer": "mountain_peak",
          filter: ["<=", ["get", "rank"], 3],
          layout: {
            "text-field": [
              "format",
              ["get", "name"],
              {},
              "\n",
              {},
              ["concat", ["get", "ele"], " m"],
              { "font-scale": 0.85 },
            ],
            "text-font": ["Noto Sans Italic"],
            "text-size": 11,
          },
          paint: {
            "text-color": "#4b3a28",
            "text-halo-color": "rgba(255, 255, 255, 0.75)",
            "text-halo-width": 1.2,
          },
        },
        {
          id: "places",
          type: "symbol",
          source: "openmaptiles",
          "source-layer": "place",
          filter: ["in", ["get", "class"], ["literal", ["city", "town", "village"]]],
          layout: {
            "text-field": ["get", "name"],
            "text-font": ["Noto Sans Regular"],
            "text-size": ["match", ["get", "class"], "village", 11, 13],
          },
          paint: {
            "text-color": "#22262e",
            "text-halo-color": "rgba(255, 255, 255, 0.85)",
            "text-halo-width": 1.5,
          },
        },
      ],
    },
  });
}

Raster layers blend too

With raster-layer-blend, image tiles blend into the map too. Shaded relief, aerial photos, old maps and satellite data no longer have to cover the map or fade over it.

Loading the map…

  • multiply

Tyrol's mountains, shaded into the map

Mapping agencies publish their shaded relief as image tiles, and today such a raster can only fade over a map, which grays it out. Multiplied, basemap.at's relief of Austria only darkens the map, most where the slopes face away from the light, so Innsbruck's streets, forests and meadows keep their colors.

  • Relief image tiles
  • Under the labels
  • basemap.at
Map code

Plain MapLibre GL JS 6.18, ready to paste into a module. createMap("map") makes the map in the element with the id map. With a bundler, point MapLibre at its worker first, as in the quick start.

import * as maplibregl from "maplibre-gl";
import "maplibre-gl/dist/maplibre-gl.css";

export function createMap(container: string | HTMLElement) {
  const map = new maplibregl.Map({
    container,
    style: "https://tiles.openfreemap.org/styles/liberty",
    center: [11.4, 47.28],
    zoom: 11.4,
  });
  map.on("load", () => {
    // Austria's shaded relief from basemap.at, as gray image tiles.
    map.addSource("relief", {
      type: "raster",
      tiles: ["https://mapsneu.wien.gv.at/basemap/bmapgelaende/grau/google3857/{z}/{y}/{x}.jpeg"],
      tileSize: 256,
      maxzoom: 17,
      attribution: '<a href="https://basemap.at">basemap.at</a>',
    });
    // Over the roads and under the labels, multiplied into the map. White leaves the map as it is and the
    // shadows darken it, so the relief shows without graying the colors out.
    map.addLayer(
      {
        id: "relief",
        type: "raster",
        source: "relief",
        paint: { "raster-layer-blend": "multiply" },
      },
      "waterway_line_label",
    );
  });
  return map;
}

Seven modes

The same seven as CSS mix-blend-mode, with the same formulas. The swatches are CSS too, an orange disc in each mode over a colored ramp.

  • normal

    Paints the layer over the map, as before.

    Everything that doesn't blend.

  • multiply

    Multiplies the colors. It only darkens, so what's beneath shows through, like ink on paper.

    Hillshades, highlighters, overprinted maps.

    ReliefDay and nightHighlighterShaded reliefSteep slopesThen and nowRainMask

  • screen

    The inverse of multiply. It only lightens, like two slides projected on one screen.

    Glows, haze, color on dark maps.

    TransitNight lights

  • overlay

    Multiplies where the map is dark and screens where it's light, which adds contrast.

    Tints that keep the texture beneath.

    London

  • color

    The layer's hue and saturation, with the lightness of the map beneath.

    Coloring a hillshade or a gray image.

    AtlasSea temperature

  • luminosity

    The layer's lightness, with the hue and saturation of the map beneath. The inverse of color.

    Shading a colorful map.

    Aerial photo

  • plus-lighter

    Adds the colors together, like light. Overlaps glow toward white.

    Glows, lights, heat.

    Day and nightCity lights

How it works

One paint property per layer.

  1. Step 1

    Pick a mode

    Set fill-layer-blend, line-layer-blend or raster-layer-blend in the paint of a fill, line or raster layer, to "multiply", "screen", "overlay", "color", "luminosity" or "plus-lighter". Leave it out, or say "normal", for the way layers have always drawn.

  2. Step 2

    The layer draws on its own

    MapLibre first draws the whole layer by itself, its features over each other as usual. So a layer never blends with itself, only with the map beneath it.

  3. Step 3

    Then it blends in

    The finished layer is blended onto everything drawn before it, such as rasters, hillshades, labels and other layers. The formulas are the W3C's, the same as CSS mix-blend-mode, so a mode looks as it does in a browser or a design tool.

What blends with what

Fill, line and raster layers can blend. Every kind of layer can be beneath them, and on a flat map or the globe a blended layer blends onto all of it.

LayersCan blendCan be blended ontoAnd on 3D terrain
fill, line, rasterYesYesYes
background, hillshade, color-reliefNoYesYes
circle, symbol, heatmap, fill-extrusionNoYesNo, and it cuts the blended layer off from the layers beneath it

On 3D terrain, keep circles, labels, heatmaps and extrusions above the blended layers. MapLibre draws the other layers into flat textures and lays those over the mountains, and a blended layer blends inside its texture. Circles, labels, heatmaps and extrusions go straight onto the screen. When one of them sits between a blended layer and the layers beneath it, the blended layer starts in a new, empty texture, finds nothing to blend with, and draws as a normal layer.

Good to know

  • A layer blends as a whole, so its own features never blend with each other, and crossing streets in one layer don't add up. Draw them in two layers to make them, as the city lights do.
  • The mode is a constant, so it takes no expressions and no zoom functions.
  • Each blended layer is one more pass on the GPU. multiply, overlay, color and luminosity also copy the map beneath first; screen and plus-lighter don't need to. Blend a few layers, not every layer.
  • It works with fill-layer-opacity and line-layer-opacity, which fade the blended layer as a whole, on 3D terrain and on the globe. A raster's raster-opacity fades it before it blends.
  • MapLibre Native doesn't blend layers yet.

Go further