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

Traffic area analytics module

The Traffic Area Analytics Module visualizes historical traffic data for a geographic region on the map. Data is fetched from the TomTom Move Portal API and rendered using one of five visualization modes: 3D or flat hexagonal grids, 3D or flat square grids, or a density heatmap.

What this module owns: the grid and heatmap layers it renders, and how they look — the colour, height and layer-position configuration below. It never restyles the base map underneath, which belongs to the Styling module, and styling knobs never touch these layers. See Who owns what.

Initialization

import { TomTomMap, TrafficAreaAnalyticsModule } from '@tomtom-org/maps-sdk/map';

const map = new TomTomMap({ mapLibre: { container: 'map' } });
const analyticsModule = await TrafficAreaAnalyticsModule.create(map);

Configure the module at initialization time using a config object:

const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    displayMode: 'hexgrid-3d',
    activeMetric: 'congestionLevel',
    metricConfig: {
        congestionLevel: { palette: 'trafficLight' },
    },
});

Fetching and displaying data

Fetch area analytics data using the trafficAreaAnalytics service and pass the response directly to show().

import { geocodeOne, geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';
import { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';

// 1. Geocode a city to get its boundary
const place = await geocodeOne('Amsterdam, Netherlands');
const boundary = await geometryData({ geometries: [place] });
const regionGeometry = boundary.features[0].geometry;

// 2. Fetch analytics for the last 7 days
const endDate = new Date();
endDate.setDate(endDate.getDate() - 3); // the latest date that always has data
const startDate = new Date(endDate);
startDate.setDate(startDate.getDate() - 6);
const fmt = (d) => d.toISOString().slice(0, 10);

const analytics = await trafficAreaAnalytics({
    apiKey: MOVE_PORTAL_KEY,
    name: 'Amsterdam, Netherlands',
    startDate: fmt(startDate),
    endDate: fmt(endDate),
    metrics: 'all',
    functionalRoadClasses: 'all',
    hours: 'all',
    geometry: regionGeometry,
});

// 3. Display on the map
await analyticsModule.show(analytics);

To remove data from the map:

await analyticsModule.clear();

Visualization modes

Five modes are available, set via displayMode at init time or changed at runtime with updateConfig({ displayMode }):

Mode Description
'hexgrid-3d' 3D extruded hexagonal cells colored and raised by metric value (default)
'hexgrid-2d' Flat hexagonal cells colored by metric value
'square-3d' 3D extruded square cells colored and raised by metric value
'square-2d' Flat square cells colored by metric value
'heatmap' MapLibre density heatmap from tile center points

Switch modes at any time:

analyticsModule.updateConfig({ displayMode: 'heatmap' });
analyticsModule.updateConfig({ displayMode: 'hexgrid-2d' });
analyticsModule.updateConfig({ displayMode: 'square-3d' });

Metrics

Five metrics are available to drive color and extrusion height. Set the active one via activeMetric. Per-metric style settings (palette or colour stops, height, filters) are configured via metricConfig.

Metric Description
'congestionLevel' Percentage increase in travel time above free-flow conditions (default active metric)
'speed' Average vehicle speed in km/h
'travelTime' Average travel time per 10 km in minutes
'freeFlowSpeed' Average speed under uncongested conditions in km/h
'networkLength' Total road length within each tile in metres — useful as a road density indicator
analyticsModule.updateConfig({ activeMetric: 'speed' });
analyticsModule.updateConfig({ activeMetric: 'travelTime' });
analyticsModule.updateConfig({ activeMetric: 'freeFlowSpeed' });

You can also pre-configure multiple metrics at init time:

const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    activeMetric: 'congestionLevel',
    metricConfig: {
        congestionLevel: { palette: 'trafficLight' },
        speed: { palette: 'heat' },
        travelTime: { height: { maxHeightMeters: 300 } },
    },
});

Color

Each metric takes its colours from metricConfig.[metricKey].palette, a named palette, or from metricConfig.[metricKey].colorStops, your own stops. When both are set, colorStops wins; with neither, the metric draws 'trafficLight'. setPalette() and setColorStops() update every metric, or only the metrics you pass as their second argument, and each one changes only its own field.

Palettes

areaAnalyticsPalettes lists the palette names. Each one runs from good traffic to bad:

analyticsModule.setPalette('trafficLight'); // green → amber → red (default)
analyticsModule.setPalette('heat');         // blue → orange → red
analyticsModule.setPalette('monochrome');   // light grey → dark grey
analyticsModule.setPalette('viridis');      // yellow → green → blue → purple
analyticsModule.setPalette('plasma');       // yellow → orange → magenta → purple

analyticsModule.setPalette('viridis', ['speed', 'freeFlowSpeed']); // only these metrics

A palette spans the metric's predefined range, and on networkLength the loaded range, since its per-tile values vary too widely for a fixed one. For speed and freeFlowSpeed its order is inverted, so that slow speeds render as bad (red) and fast speeds as good (green). getConfig() keeps the palette as its name.

Custom color stops

Provide an AreaAnalyticsColorStopsConfig object with a valueType and stops array to setColorStops(), or as colorStops in the config. Stops are used as given, with no inversion for speed.

'raw' (default) — stop value fields are actual data values:

analyticsModule.setColorStops({
    valueType: 'raw',
    stops: [
        { value: 0,   color: '#2dc653' },  // 0% congestion
        { value: 30,  color: '#f5a623' },  // 30% congestion
        { value: 100, color: '#e03030' },  // 100% congestion
    ],
});

'relativeToPredefinedRangePCT' — stop value fields are 0–100 percentages relative to the SDK's predefined metric ranges (congestion 0–100, speed 0–120 km/h, travelTime 0–20 s/km). Useful for consistent coloring across datasets:

analyticsModule.setColorStops({
    valueType: 'relativeToPredefinedRangePCT',
    stops: [
        { value: 0,   color: '#2dc653' },  // 0% of predefined range
        { value: 50,  color: '#f5a623' },  // 50% of predefined range
        { value: 100, color: '#e03030' },  // 100% of predefined range
    ],
});

'relativeToActualRangePCT' — stop value fields are 0–100 percentages relative to the live data range actually present in the loaded tiles (0 % = loaded minimum, 100 % = loaded maximum). Maximises contrast regardless of absolute values:

analyticsModule.setColorStops({
    valueType: 'relativeToActualRangePCT',
    stops: [
        { value: 0,   color: '#2dc653' },  // 0% of loaded range
        { value: 50,  color: '#f5a623' },  // 50% of loaded range
        { value: 100, color: '#e03030' },  // 100% of loaded range
    ],
});

Pass undefined to clear a field. Clearing the stops brings back the palette; clearing both draws 'trafficLight':

analyticsModule.setColorStops(undefined); // back to the metric's palette
analyticsModule.setPalette(undefined);    // and with no palette, to 'trafficLight'

Height

Height (extrusion) configuration is per-metric under metricConfig.[metricKey].height. setHeight() updates it for every metric, or only for the metrics you pass as its second argument.

// Predefined range (default): metric normalized to SDK predefined range, visual max = maxHeightMeters
analyticsModule.setHeight({ maxHeightMeters: 200 });

// Set a minimum height floor
analyticsModule.setHeight({ maxHeightMeters: 100, minHeightMeters: 10 });

// Current range: normalizes to live data range — maximises contrast for whatever data is loaded
analyticsModule.setHeight({ maxHeightMeters: 150, scaleMode: 'currentRange' });

// Raw: each unit of the metric adds metersPerUnit metres of height (default 1)
analyticsModule.setHeight({ scaleMode: 'raw', metersPerUnit: 10 });

Filtering

Filter visible tiles by value range using setFilters(), for every metric or only for the metrics you pass as its second argument. The active metric's filter decides which tiles are rendered.

// Show only tiles with congestion ≥ 50
analyticsModule.setFilters({ min: 50 });

// Show only tiles within a range
analyticsModule.setFilters({ min: 20, max: 80 });

// Clear the filter
analyticsModule.setFilters(undefined);

Region boundary

When show() is called, all region polygons in the analytics response are rendered alongside the analytics cells as a subtle fill with an outline. Customize their appearance using regionPolygon:

const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    regionPolygon: {
        color: '#0052a5',      // fill and outline color (default: black on a light style, white on a dark one)
        fillOpacity: 0.08,     // default: 0
        outlineOpacity: 1.0,   // default: 0.5
        outlineWidth: 3,       // default: 2
    }
});

Layer positioning

Control where analytics layers are placed in the map's rendering stack using beforeLayerConfig. Use 'top' to draw above all layers, or supply a well-known style layer ID to insert the analytics layers below it.

Give one target for every display mode, or a record keyed by display mode — heatmap, hexgrid-2d, hexgrid-3d, square-2d, square-3d — plus all for the modes it leaves out. By default the heatmap and the 2D modes sit below lowestLabel, and the 3D modes below lowestPlaceLabel. The region boundary always sits below the labels.

// Place everything on top
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    beforeLayerConfig: 'top',
});

// Flat layers below all labels, extruded ones below place names
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
    beforeLayerConfig: { all: 'lowestLabel', 'hexgrid-3d': 'lowestPlaceLabel', 'square-3d': 'lowestPlaceLabel' },
});

// Reposition after initialization
analyticsModule.updateConfig({ beforeLayerConfig: { 'hexgrid-3d': 'top' } });

A mode the record leaves out, with no all to cover it, goes back to its default position. A target the style lacks (Satellite has no lowestRoadLine or lowestBuilding) puts that mode's layers on top.

Layer overrides

For what the settings above don't cover, layers takes a partial MapLibre layer for each layer the module draws. Each display mode draws its own: heatmap; hexgridFill and hexgridOutline (the outline of a hovered or clicked cell) in hexgrid-2d; hexgridExtrusion and hexgridExtrusionHighlight in hexgrid-3d; the same four square* entries for the square grid; and regionFill and regionLine in every mode. paint and layout merge per property onto the module's own, and what you set takes precedence over the active metric's colours and heights:

analyticsModule.updateConfig({
    layers: {
        hexgridFill: { paint: { 'fill-opacity': 0.85 } },
        hexgridExtrusion: { paint: { 'fill-extrusion-vertical-gradient': false } },
        regionLine: { paint: { 'line-dasharray': [2, 2] } },
    },
});

Visibility

Control overall layer visibility. isVisible() reports this visible setting, not whether any tile is drawn: a module showing nothing still returns true. Read getShown() to know whether data is shown.

// Hidden at startup
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, { visible: false });

// Toggle later
analyticsModule.setVisible(true);

// Check the setting, and whether there is data to draw
console.log(analyticsModule.isVisible()); // true
console.log(analyticsModule.getShown().hexgrid.features.length > 0);

Events

Listen to click and hover events on hexgrid and square cells. Events fire for whichever mode is currently active.

analyticsModule.events.on('click', (feature, lngLat) => {
    console.log('Congestion:', feature.properties.congestionLevel);
    console.log('Speed:', feature.properties.speed);
});

analyticsModule.events.on('hover', (feature) => {
    console.log('Hovered cell:', feature.properties);
});

analyticsModule.events.on('long-hover', (feature) => {
    console.log('Long-hovered cell:', feature.properties);
});

// Remove a specific listener
analyticsModule.events.off('click');

A hovered or clicked cell is highlighted. Highlight one from your own UI, or read which ones are, with the module's event state methods:

analyticsModule.putEventState({ index: 0, state: 'click' });
const { click = [] } = analyticsModule.getEventStates();

Custom tooltip on hover

Use events.on('hover') to build a hover tooltip:

import { Popup } from 'maplibre-gl';

const popup = new Popup({ closeButton: false, anchor: 'left', offset: 12 });

analyticsModule.events.on('hover', (feature, lngLat) => {
    if (!feature) { popup.remove(); return; }
    const { congestionLevel, speed } = feature.properties;
    popup
        .setLngLat(lngLat)
        .setHTML(`<strong>Congestion:</strong> ${congestionLevel ?? 0}%<br/><strong>Speed:</strong> ${speed ?? 0} km/h`)
        .addTo(map.mapLibreMap);
});

map.mapLibreMap.getCanvas().addEventListener('mouseleave', () => popup.remove());

Changing the configuration

applyConfig(config) replaces the whole configuration: what it omits returns to its default, not to what an earlier call set. updateConfig(partial) changes the given properties and keeps the rest, one level deep — a metricConfig you pass replaces the whole record, so the metrics it leaves out return to their defaults.

To change one metric and keep the others, use the setters setPalette, setColorStops, setHeight and setFilters, which take the metrics to target:

analyticsModule.applyConfig({ displayMode: 'heatmap' }); // everything else back to its default
analyticsModule.updateConfig({ activeMetric: 'speed' }); // displayMode stays 'heatmap'
analyticsModule.setPalette('viridis', ['speed']);        // the other metrics keep their colours

Config change events

Subscribe to config changes, emitted by every setter and by applyConfig and updateConfig:

const unsub = analyticsModule.events.on('config-change', (config) => {
    console.log('Active metric:', config?.activeMetric);
});
// Later: unsub();

Querying shown features

Use getShown() to access the GeoJSON feature collections currently rendered for each visualization mode:

const { heatmap, hexgrid, square } = analyticsModule.getShown();
console.log(`Hexagonal cells in view: ${hexgrid.features.length}`);

Every setting as data

trafficAreaAnalyticsKnobCatalogue lists the module's plain-valued settings with their kind, range or options and, where one value holds everywhere, their default. A UI can build its controls from it, with no map loaded:

import { trafficAreaAnalyticsKnobCatalogue } from '@tomtom-org/maps-sdk/map';

const speedKnobs = trafficAreaAnalyticsKnobCatalogue.filter(({ id }) => id.startsWith('metricConfig.speed.'));
// [{ id: 'metricConfig.speed.palette', kind: 'enum', description: '…', options: ['trafficLight', 'heat', …], default: 'trafficLight' }, …,
//  { id: 'metricConfig.speed.filters.min', kind: 'number', description: '…', range: { min: 0, max: 120, bounds: 'hard-min' } }, …]

The module-wide knobs come first, then the same seven for each metric, <metric> standing for any of areaAnalyticsMetricKeys:

Id Kind
visible toggle
displayMode enum
activeMetric enum
regionPolygon.color color
regionPolygon.fillOpacity, regionPolygon.outlineOpacity number, 0 to 1
regionPolygon.outlineWidth number, 0 to 10 pixels
regionPolygon.inverted toggle
metricConfig.<metric>.palette enum: areaAnalyticsPalettes, default trafficLight
metricConfig.<metric>.height.scaleMode enum
metricConfig.<metric>.height.maxHeightMeters, metricConfig.<metric>.height.minHeightMeters number, 0 to 5000 metres
metricConfig.<metric>.height.metersPerUnit number, 0 to 100 metres per unit of the metric, in the raw scale mode
metricConfig.<metric>.filters.min, metricConfig.<metric>.filters.max number, over the metric's predefined range

API reference

For complete documentation of all properties, methods, and types, see the TrafficAreaAnalyticsModule API Reference.

Related guides and examples

Traffic overview

Related traffic modules

Related examples