Map styling

The Styling Foundations Module holds what every part of the map shares as semantic knobs. They cover label, icon and road sizes, the label typeface, the ten map colours and the accent, and a globe with a sky.

  • Each knob reaches across the whole style. labels.sizeFactor resizes every label, POIs and road shields included, and colors.roadMajor recolours every shade of the major roads. The other style-owned modules each hold the look of one part.
  • The knobs are semantic, not layer ids. Like every module’s settings, they are listed as data in a knob catalogue, and setKnob checks each value against it. MapLibre layers you edit by hand, through styling.layers or map.mapLibreMap, have no catalogue and depend on the layer ids of one style version.

The playground below builds one control per knob from the catalogues of this module and the other style-owned ones.

Each feature built on the knobs has its own guide:

GuideCovers
Map colours and the accentsetMapColors over the ten map colours, and the accent the modules’ own drawing defaults to
Map typefaceThe labels.typeface knob, and your own font files for every label on the map
Globe and skyThe view knobs: globe projection, sky, horizon and the colour behind the map
Borrowed looksThe parts of places, routes and fetched incidents that follow the style’s own looks
Editing style layersstyling.layers, for what no knob covers

Initialization

import { TomTomMap, StylingFoundationsModule } from '@tomtom-org/maps-sdk/map';
const map = new TomTomMap({ mapLibre: { container: 'map' } });
const styling = await StylingFoundationsModule.get(map);

Pass settings to apply them right away. The configuration nests each setting along its knob id, so labels.sizeFactor is labels: { sizeFactor }:

const styling = await StylingFoundationsModule.get(map, { labels: { sizeFactor: 1.2 }, colors: { water: '#0fa8c8' } });
  • A map has one module. A second get() returns the same instance and merges the settings you pass to it over what it holds, one level deep, as updateConfig does.
  • The map is themed from the first frame. get resolves once the style has loaded, before the map draws it. Knobs you pass to get or set right after it land on that frame instead of fading in from the style’s own look (Themed map).

Knobs

Every adjustable thing is a knob with a stable id, the setting’s path in the configuration. Set one with setKnob, several at once with setKnobs, read one with getKnob, and restore the style’s own value with resetKnob, as on every module:

import { getKnob, resetKnob, setKnob, setKnobs, stylingFoundationsKnobCatalogue } from '@tomtom-org/maps-sdk/map';
const knobs = stylingFoundationsKnobCatalogue;
setKnob(styling, knobs, 'labels.sizeFactor', 1.3); // every base-map label 30% bigger
setKnob(styling, knobs, 'roads.widthFactor', 0.8); // thinner roads, hierarchy kept
setKnobs(styling, knobs, { 'colors.roadMajor': '#f59e0b', 'view.projection': 'globe' }); // one restyle
getKnob(styling, knobs, 'roads.widthFactor'); // 0.8
resetKnob(styling, knobs, 'roads.widthFactor'); // back to the style's width
styling.resetConfig(); // everything back to the style as shipped

Use the knob functions for values held as data, from a control, storage or a language model, since they check each value against the catalogue. Use applyConfig and updateConfig for settings your code writes, typed. They don’t check values at run time, and updateConfig({ colors: { water } }) replaces every colour you set before. Changing a setting compares the two.

How knobs work

  • Most knobs write the loaded style’s layers. The view knobs write the map itself. colors.accent writes nothing, and the modules that draw their own layers read it.
  • One engine per map applies the knobs, and the look knobs of the POIs, traffic and terrain modules restyle the same loaded style through it. Knobs on one layer property compose, whichever module set them. For example, labels.sizeFactor here and sizeFactor on POIsModule multiply on the POI labels. All of them land again after a style switch.
  • A knob writes the whole value of each property it reaches, worked out from the style’s own value. If you set one of those properties yourself with map.mapLibreMap.setPaintProperty, even to an expression that reads feature-state, the knob replaces your value whenever it applies. That happens when you set or reset it, and after a style switch.
  • No knob shows or hides a layer group (water, 3D buildings, road shields). The Base Map Module does that.
setKnob · setKnobs · applyMapPreset · setMapColors · applyConfigmap.setStyle(…)StylingFoundationsModule: the knobs you set, by kindPOIsModule · TrafficFlowModuleTrafficIncidentsModule · TerrainModuletheir look knobs: size · zoom · colours · widthmap.mapColors.accentread by routes, places,geometries and rangesMapLibre mapfactorwraps the style's own expressiona style expression E becomes 1.3 × Ecolourthe colour, and every shadethe style derived from itview.*projection · sky · space colourcolors.accentpaints no layerThe loaded style's layerspaint · layout · filters · visibilityMap viewprojection · sky · background re-applied last, after every switchsame engine:factors multiply with the ones here
KindTakesWhat it does
factora multiplier, 1 = as the style shipsmultiplies the style’s own value, so zoom ramps and the road hierarchy are kept. A zoom ramp has each stop’s output scaled, and any other expression E becomes ["*", 1.3, E]
numbera value in the knob’s range and unitssets that part of the map’s view, such as view.light.direction in degrees
colourany CSS colourreplaces the colour and re-derives the shades the style built from it. Recolouring the major roads recolours their darker outline by the same offset. colors.accent paints no layer, and the modules read it
toggletrue / falseview.sky.visible turns the sky on or off
enumone of the knob’s optionspicks a mode, such as the projection

Every knob also follows the same rules:

  • A value outside its range or of the wrong kind throws a RangeError naming the knob and what it takes. An unknown id throws an Error. A failed call changes nothing.
  • getKnob reads the value in force, which is the one you set or the loaded style’s own. It returns undefined where the style computes the value from the data, and resetKnob still restores it.
  • The SDK re-applies the knobs after every setStyle, and a clean switch drops them (Style changes).
  • getConfig() returns the knobs you set as plain data, ready to store and re-apply (Settings).
  • The ten map colours follow the TomTom styles’ taxonomy, re-verified on every style release. Every other knob needs no knowledge of the style (How durable is this?).
  • Knobs reach only the style’s layers and the map’s view. What the SDK draws itself (routes, places, geometries) is configured on its own module. It defaults to the accent, and a few of its parts borrow a look from the style.

Every knob

GroupKnobsKind
Sizeslabels.sizeFactor, symbols.sizeFactor, roads.widthFactorfactor 0.5–1.5
Typefacelabels.typeface (sans | sans-italic | serif | serif-italic)enum
Map colourscolors.land, colors.water, colors.vegetation, colors.park, colors.artificial, colors.roadMajor, colors.road, colors.roadOutline, colors.label, colors.labelHalo, and colors.accentcolour
Viewview.projection (mercator | globe)enum
view.sky.visibletoggle
view.sky.color, view.sky.horizonColor, view.space.colorcolour
Lightview.light.direction (0–359°), view.light.altitude (0–90°), view.light.intensity (0–1)number
view.light.alignment (viewport | map)enum
view.light.colorcolour

The module that shows a part holds its look, which you set with the same setKnob:

PartModuleIts look knobs
Road markingsBaseMapModulethe exit numbers, arrows, restricted and under-construction roads, each shown or hidden
POIsPOIsModulesize, the zoom they appear at, zoom shift, label colours, micro markers
Traffic flowTrafficFlowModulethe colour of each flow level, the line width and opacity
Traffic incidentsTrafficIncidentsModulethe colour of each severity, the line width and opacity
HillshadeTerrainModulemethod, light direction, strength, zoom range and colours

The knob catalogue

stylingFoundationsKnobCatalogue lists every knob as static data, readable with no map loaded. Its entries share the shape of every module’s catalogue (Every setting as data):

import { getKnob, stylingFoundationsKnobCatalogue } from '@tomtom-org/maps-sdk/map';
for (const knob of stylingFoundationsKnobCatalogue) {
// knob.id, knob.kind ('factor' | 'number' | 'toggle' | 'color' | 'enum'), knob.description,
// knob.range (factor, number), knob.options (enum), knob.appliesTo
const current = getKnob(styling, stylingFoundationsKnobCatalogue, knob.id);
}
  • Entries have no default, because the loaded style decides. On a live map, getKnob reads the value in force.
  • stylingFoundationsKnobIds lists the ids on their own, typed StylingFoundationsKnobId. StylingFoundationsKnobValueOf<ID> is the value one takes, and stylingFoundationsKnobKinds lists the kinds.
  • Each control of the playground at the top of this guide shows the value getKnob reads.

Every colour a knob sets on the loaded style, whichever module holds it, also has a map-wide id. It is the module’s prefix followed by its id in that module’s catalogue. The Styling knobs keep their own ids (colors.water). The others take pois., traffic.flow., traffic.incidents. or terrain. (pois.label.color, traffic.flow.colors.slow, terrain.hillshade.colors.shaded). styleColorKnobIds lists them, typed StyleColorKnobId. getStyleColor reads the current colour of any of them (the one set, one the style derives from the map colours, or the style’s own), whether or not its module exists:

import { getStyleColor } from '@tomtom-org/maps-sdk/map';
getStyleColor(map, 'colors.water');
getStyleColor(map, 'traffic.incidents.colors.major'); // undefined where the style colours it by an expression

Name a map colour through them rather than hard-coding it, as the map effects plugin does to scope its bloom. StyleKnobId and StyleKnobValueOf<ID> type every style knob by its map-wide id.

When you wire controls to the catalogue, copy two things from the playground:

  • Call setKnob once per animation frame at most. A slider or a colour picker fires input far faster than a restyle runs, so coalesce the calls (last value wins) and apply the final value on change.
  • Rebuild the panel on external changes only. setKnob emits config-change, so rebuilding from it mid-drag replaces the input under the pointer. Skip the changes your own controls made, and rebuild on a reset, a setStyle or an agent.

Presets

A map preset is a named bundle of knob settings for a common intent. It sets knobs across the modules that hold the look of the map, which are this one, BaseMapModule’s road parts and POIsModule’s look. mapPresetCatalogue lists them with the settings each one applies, per module, by that module’s catalogue ids (MapPresetSettings):

PresetIntent
data-vizA quiet base for your own data on top: smaller labels and icons, thinner roads, no exit badges or road arrows, no POI dots, POIs from zoom 14
night-drivingBigger labels and icons, wider roads, exit badges and road arrows shown, no POI dots, POIs from zoom 13
minimalThe bare map: smaller labels, no road markings or exit badges, no POI dots, POIs from zoom 16
globeGlobe projection with a sky
import { applyMapPreset, setKnob, stylingFoundationsKnobCatalogue } from '@tomtom-org/maps-sdk/map';
await applyMapPreset(map, 'data-viz'); // replaces what the other presets set
setKnob(styling, stylingFoundationsKnobCatalogue, 'labels.sizeFactor', 1.1); // then adjust
await applyMapPreset(map, 'globe', { merge: true }); // lay a preset over what is there
  • applyMapPreset is async, and creates the Styling, Base Map and POIs modules if the map has none yet. Their get() returns those same instances.
  • By default, the knobs any preset sets are reset before this one applies, so nothing of the last preset stays. Every other knob keeps its value. { merge: true } skips the reset.
  • Whole layer groups stay as the style ships them. To hide buildings or shields, use BaseMapModule.
  • An unknown id throws an Error listing the known ones, mapPresetIds. MapPresetId is their union, and MapPreset an entry of the catalogue.

A preset is exactly as durable as the knobs it sets. For the visual half of the data-viz intent, dimming and desaturating what remains, pair it with the map effects plugin’s grade and tint.

Exporting the style

exportStyle() returns the map’s style as currently rendered, as a MapLibre style specification. This is the loaded style with every knob applied, plus whatever the SDK’s data modules (places, routes) have added to it. Save it, diff it or serve it yourself.

  • Tile URLs carry the API key the map was created with, so strip or rotate it before publishing the file.
  • map.getStyleInput() returns the style input instead, which is the id or object the map was given (Map styles).

Settings: persist, share, re-apply

getConfig() returns the overridden knobs as a plain object (StylingFoundationsModuleConfig), each nested along its id. applyConfig() replaces every knob with one, and updateConfig() replaces the top-level groups given, such as colors, and keeps the rest. It serializes cleanly, so styling can live in local storage, a URL or a pull request. It keeps working on a later SDK version for as long as the knob ids do:

localStorage.setItem('map-styling', JSON.stringify(styling.getConfig()));
// …later
styling.applyConfig(JSON.parse(localStorage.getItem('map-styling')));
styling.events.on('config-change', (settings) => save(settings));

applyConfig doesn’t check what it reads back. If the settings come from somewhere you don’t control, such as a shared URL, keep them as knob values by id and restore them with setKnobs, which refuses them all if one is out of range:

setKnobs(styling, stylingFoundationsKnobCatalogue, JSON.parse(new URLSearchParams(location.search).get('styling') ?? '{}'));

Style changes

The style (style on the map, or setStyle) decides which map document loads. Knobs adjust whichever one is loaded.

  • map.setStyle(...) re-applies the knobs once the new style has loaded, after the other modules have restored themselves, so they land on top, on the new style’s first frame.
  • A clean switch (resetState: true) drops them, with the rest of the SDK state (Keeping or dropping state).

How durable is this?

There are two kinds of knobs, told apart by appliesTo in the catalogue:

  • any-style knobs need no knowledge of the style, so they hold whatever the style version:
    • the factors, which wrap whatever the style already does;
    • the view knobs, which set the map’s projection, sky and background rather than a layer;
    • colors.accent, which paints no layer at all.
  • tomtom-styles knobs, the ten map colours, reach their layers through a taxonomy the TomTom styles declare. The SDK re-verifies them against the styles it ships with. On a style that doesn’t declare the taxonomy, setting the knob warns once in the console and does nothing.

Nobody re-tests a setPaintProperty against a layer id when the style changes version. A knob is re-verified on every style release the SDK ships.

Interplay with other modules

Who owns what states which module owns which layers. In short:

  • This module holds the foundations that reach across every part: sizes, map colours, the accent, projection and sky.
  • The other style-owned modules each hold one part: whether it is shown, which of its features show and how it looks (Every knob). Where a foundation and a part’s own knob reach the same layer, they compose, and a map preset sets both at once.
  • Data-owned modules (routes, places, geometries, your own GeoJSON) draw their own layers, styled through their own configuration. No knob writes them.

API reference

For complete documentation, see the StylingFoundationsModule API Reference.

  • Map styling playground — every look knob of the map on a panel built from the catalogues, and the map presets
  • Themed map — a map that opens in its own colours, themed from the first frame
  • Map colours — the ten map colours, each recolouring every shade derived from it
  • Map typeface — every label in one typeface, from open-source font files or the style’s faces
  • Map theme — the base map, an area, pins and a route themed in one call
  • Four themes per style — four maps of one place side by side, moving together, with four colour themes for each standard style
  • Borrowed looks — places, a route and fetched traffic incidents following the knobs that restyle the style’s POIs and incidents
  • Globe and 3D terrain — the view knobs, then 3D terrain as you zoom in
  • 3D terrain playground — sky and relief that outlive a style switch