This is the documentation of version 1.0.0-rc.4. Go to the latest version, 1.0.0-rc.5.

Map effects plugin

The Map Effects plugin adds practical post-processing to a TomTomMap. Its effects are computed from the rendered map's pixels, not its style, so they work on every style and survive style releases. MapLibre itself has no post-processing. The playground below puts every effect on one panel:

Each effect is named after the problem it solves, is off by default and has its own guide:

Guide Effects Solves
Bloom bloom.* Emphasis on dark and driving styles. Lit roads, traffic tubes and route lines glow, optionally only in the colours you name
Glare glare.* Lights that read as lights. The colours you name, the major roads by default, cast streaks as lamps do through a lens
Depth of field depthOfField.* Depth on a tilted map. One band of distance stays sharp, and the rest defocuses as through a lens
Grade, tint, fog, edge blur and vignette grade.*, tint.*, fog.*, edgeBlur.*, vignette.* Muting the base map under your data, and depth and focus at the rim of the view
Custom GPU passes addPass, addLayer Your own fragment shaders inside the effect chain, with every other effect working on their result, and your own 2D layers between the overlays

Installation

npm install @tomtom-org/maps-sdk maplibre-gl @tomtom-org/maps-sdk-plugin-map-effects

@tomtom-org/maps-sdk and maplibre-gl are peer dependencies.

Usage

import { resetKnob, setKnob, setKnobs, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { effectKnobCatalogue, MapEffects } from '@tomtom-org/maps-sdk-plugin-map-effects';

const map = new TomTomMap({
    style: 'streetDark',
    mapLibre: { container: 'map' },
});

const effects = new MapEffects(map); // or new MapEffects(map, config)

effects.updateConfig({ grade: { saturation: 0.4, brightness: 0.85 }, tint: { opacity: 0.15 } }); // a quiet base
setKnob(effects, effectKnobCatalogue, 'bloom.intensity', 0.5);                     // one knob
setKnobs(effects, effectKnobCatalogue, { 'bloom.radius': 14, 'bloom.threshold': 0.6 }); // several, the rest kept

resetKnob(effects, effectKnobCatalogue, 'bloom.intensity'); // one effect off
effects.resetConfig();                                      // everything off

How it renders

No effect knob writes the MapLibre style. Every effect reads the map's rendered pixels, so the knobs change the frame rather than what the map draws. The one link to the style is the colour scopes, bloom.only and glare.only. Each keys on the colours the style paints for the style colour knobs it names, whichever module holds them:

Style colour knobsStylingFoundationsModule · POIsModule · traffic modules · TerrainModuleMapLibre mapMapEffects: its own knobs, effectKnobCatalogueThe frame on screen, and capture()The loaded styleno effect knob writes itThe rendered canvasread in its render event1. GPU chainyour passes · depthOfField.* · glare.* · bloom.* with bloom.only2. Bloom without a scopebloom.*3. Overlaysgrade.* · fog.* · edgeBlur.* · tint.* · vignette.*your layers in between rendersreads the chain's outputcolour knobsreads the pixels bloom.only and glare.only key on the coloursthe style paints for named colour knobs(getStyleColor)

The effects draw in three kinds of layer, in this order:

  1. The GPU chain is a WebGL2 canvas under everything else. It runs your passes, the depth of field, the glare and a bloom scoped by colour on the map's pixels, in that order. Each pass reads the previous pass's output without the picture leaving the GPU.
  2. Bloom without a scope is a small canvas blended in screen mode over that result.
  3. Overlays are CSS layers over both: grade, fog, edge blur, tint and vignette. Your own 2D layers from addLayer sit between them, each right above the overlay its placement names. A layer at after-bloom sits above a scoped bloom too, since that glow is part of the chain.

As a result:

What each effect costs

An effect costs nothing while it's off. Once it's on, its cost each frame depends on the layer it draws in:

Effect Draws as Each frame
Grade, tint, vignette A CSS layer The browser composites one more full-size layer. The plugin draws nothing.
Fog, edge blur A CSS layer with a backdrop-filter The browser blurs what's under the layer, then composites it.
Bloom A canvas at a quarter of the pixels The plugin draws the map into it through a CSS filter, and the browser blends it in screen mode.
Bloom with bloom.only Three stages of the GPU chain Three full-size passes. The scope's colours reach the GPU only when the scope or the style changes.
Glare Two stages of the GPU chain Two full-size passes. The second reads 32 samples per pixel for each pair of opposite rays.
Depth of field One stage of the GPU chain One full-size pass, which samples the pixels around each pixel.
Your own passes One stage of the GPU chain each One full-size pass, as costly as your shader.

The catalogue

effectKnobCatalogue lists every knob as static data, which you can read without a map. Each entry has an id, a kind (number, color or colors), a description, a default and a useCase. It shares the entry shape of the SDK's knob catalogues, and the playground above builds its panel from it.

effectKnobIds lists the ids, EffectKnobId is their union and EffectKnobValueOf<ID> is the value one takes.

Each effect's guide lists its ranges and defaults:

Effect Knob ids
Bloom bloom.intensity, bloom.radius, bloom.threshold, bloom.only, bloom.onlyTolerance
Glare glare.intensity, glare.length, glare.rays, glare.angle, glare.threshold, glare.only, glare.onlyTolerance
Grade grade.brightness, grade.contrast, grade.saturation
Fog fog.intensity, fog.reach, fog.color
Edge blur edgeBlur.intensity, edgeBlur.reach
Depth of field depthOfField.intensity, depthOfField.focus, depthOfField.band, depthOfField.bokeh
Tint tint.color, tint.opacity
Vignette vignette.intensity, vignette.reach

Capture

capture({ pixelRatio }) renders the current view, map and effects, into one canvas. The plugin re-renders the map at the requested ratio rather than resampling it, reads it back once every tile is drawn, and then sets it back to its previous ratio. MapLibre clamps to its maxCanvasSize, so measure the ratio you got from the returned canvas.

const canvas = await effects.capture({ pixelRatio: 3 });
canvas.toBlob((blob) => upload(blob), 'image/png');

Relation to the Styling Foundations module

The Styling Foundations module changes the style (what the map draws), and effects change the frame (how the drawn map looks). The two combine. The data-viz map preset quiets the map's content, and grade.saturation and tint.opacity mute what remains.

API reference

For complete documentation see the MapEffects API Reference and effectKnobCatalogue.

Related guides and examples

Related guides

Related examples