Traffic area analytics module
TrafficAreaAnalyticsModule draws a trafficAreaAnalytics response — a region's historical traffic — as hexagon or square grids, flat or extruded, or as a density heatmap, with the region's boundary.
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 Foundations 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, {
displayMode: 'hexgrid-3d',
activeMetric: 'congestionLevel',
metrics: { congestionLevel: { palette: 'trafficLight' } },
});
- Every setting is optional;
getConfig()returns the whole configuration, defaults filled in, fromcreate()on. - It is data-owned: every
create()returns a new, independent instance.
Fetching and displaying data
Pass a trafficAreaAnalytics response to show() as it arrives. Ask for metrics: 'all' to switch metrics later without fetching again:
import { geocodeOne, geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';
const place = await geocodeOne('Amsterdam, Netherlands');
const boundary = await geometryData({ geometries: [place] });
const analytics = await trafficAreaAnalytics({
apiKey: 'YOUR_MOVE_PORTAL_KEY',
startDate: '2024-08-06',
endDate: '2024-08-12',
metrics: 'all',
functionalRoadClasses: 'all',
hours: 'all',
geometry: boundary.features[0].geometry,
});
await analyticsModule.show(analytics);
await analyticsModule.clear(); // removes it again
show()draws every tile and every region of the response, in all three cell sources, and the active display mode picks which one shows.- The service needs a Move Portal key and gated access (Basic usage).
Visualization modes
displayMode sets one of five modes, at create() or through updateConfig({ displayMode }):
| Mode | Draws |
|---|---|
'hexgrid-3d' |
Hexagons coloured and raised by the metric (default) |
'hexgrid-2d' |
Flat hexagons coloured by the metric |
'square-3d' |
Squares coloured and raised by the metric |
'square-2d' |
Flat squares coloured by the metric |
'heatmap' |
A MapLibre density heatmap from the tile centres |
analyticsModule.updateConfig({ displayMode: 'heatmap' });
Metrics
activeMetric picks the metric that colours the cells and raises them; congestionLevel by default. Each metric's look — palette or colour stops, height, filters — sits under metrics.<metric>, so several can be set up front and switched between:
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
activeMetric: 'congestionLevel',
metrics: {
congestionLevel: { palette: 'trafficLight' },
speed: { palette: 'heat' },
travelTime: { height: { maxHeightMeters: 300 } },
},
});
analyticsModule.updateConfig({ activeMetric: 'speed' });
The metrics are areaAnalyticsMetricKeys, whose units are the service's. On a tile, networkLength is the road length within that tile, a road-density indicator.
Color
A metric takes its colours from metrics.<metric>.palette, a named palette, or metrics.<metric>.colorStops, your own stops.
colorStopswins when both are set; with neither, the metric draws'trafficLight'.setPalette()andsetColorStops()set every metric, or only those in their second argument, and each changes only its own field.
Palettes
areaAnalyticsPalettes lists the names (AreaAnalyticsPalette). Each 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
networkLengththe loaded range, since its per-tile values vary too widely for a fixed one. - On
speedandfreeFlowSpeedit runs the other way, so slow traffic takes the bad end. getConfig()keeps the palette as its name.
Custom color stops
setColorStops(), or colorStops in the config, takes an AreaAnalyticsColorStopsConfig: stops, each a value and a CSS color, and the scaleMode that says what the values are.
scaleMode |
A stop's value is |
|---|---|
'raw' (default) |
a metric value |
'predefinedRange' |
0–100 % of the SDK's range for the metric: congestion 0–100, speed and free-flow speed 0–120 km/h, travel time 0–20 minutes per 10 km, network length 0–5000 m |
'currentRange' |
0–100 % of the range in the loaded tiles, 0 % being the loaded minimum |
analyticsModule.setColorStops({
scaleMode: 'raw',
stops: [
{ value: 0, color: '#2dc653' }, // 0 % congestion
{ value: 30, color: '#f5a623' },
{ value: 100, color: '#e03030' },
],
}, ['congestionLevel']);
- Stops are used as given — never reversed for speed — in any order: the ramp sorts them by value, and of stops sharing a value the last one wins.
predefinedRangecolours datasets alike;currentRangegives the most contrast for whatever is loaded.undefinedclears a field: clearing the stops brings the palette back, and clearing both draws'trafficLight'.
analyticsModule.setColorStops(undefined); // back to the metric's palette
analyticsModule.setPalette(undefined); // and with no palette, 'trafficLight'
Reading a metric's colours
resolveColorStops(metric, metricConfig) returns the stops a metric is painted with, as an AreaAnalyticsColorStopsConfig: its colorStops when set, else its palette spread from 0 to 100 % of its range, reversed on the speed metrics. A legend or chart drawn from it follows whatever the config sets:
import { resolveColorStops } from '@tomtom-org/maps-sdk/map';
const { scaleMode, stops } = resolveColorStops('speed', analyticsModule.getConfig()?.metrics?.speed);
const legendColors = stops.map((stop) => stop.color);
Height
In the 3D modes, metrics.<metric>.height (AreaAnalyticsHeightConfig) raises the cells. setHeight() sets every metric, or only those in its second argument.
analyticsModule.setHeight({ maxHeightMeters: 200 }); // tallest cell 200 m
analyticsModule.setHeight({ maxHeightMeters: 100, minHeightMeters: 10 }); // and a floor
analyticsModule.setHeight({ scaleMode: 'currentRange', maxHeightMeters: 150 }, ['speed']);
analyticsModule.setHeight({ scaleMode: 'raw', metersPerUnit: 10 }); // 10 m per unit of the metric
scaleModetakes the colour stops' three modes. InpredefinedRange, the default, andcurrentRange, the tallest cell reachesmaxHeightMeters,1000by default.- In
raw, a cell risesmetersPerUnitmetres,1by default, per unit of the metric. networkLengthscales its height to the loaded range by default, as its palette does.- On
speed, a cell rises as traffic slows.
Filtering
setFilters() hides the cells outside a value range, for every metric or only those in its second argument:
analyticsModule.setFilters({ min: 50 }); // congestion ≥ 50
analyticsModule.setFilters({ min: 20, max: 80 }, ['congestionLevel']);
analyticsModule.setFilters(undefined); // clears them
Only the active metric's filter hides cells, and never in the heatmap.
Region boundary
show() draws each region polygon of the response alongside the cells, as a fill and an outline. regionPolygon styles it:
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
regionPolygon: {
color: '#0052a5', // fill and line; default black on a light style, white on a dark one
fill: { opacity: 0.08 }, // default 0
line: { opacity: 1, width: 3 }, // defaults 0.5 and 2 px
fillStyle: 'inverted', // tint the world outside the region; default 'filled'
},
});
Layer positioning
beforeLayerConfig (AreaAnalyticsBeforeLayerConfig) takes one target for every display mode — 'top' or a key of mapStyleLayerIDs — or a record keyed by display mode, plus all for the modes it leaves out:
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
beforeLayerConfig: { all: 'lowestLabel', 'hexgrid-3d': 'lowestPlaceLabel', 'square-3d': 'lowestPlaceLabel' },
});
analyticsModule.updateConfig({ beforeLayerConfig: { 'hexgrid-3d': 'top' } });
- By default the heatmap and the 2D modes sit below
lowestLabel, the 3D modes belowlowestPlaceLabel; the region boundary always sits below the labels. - A mode the record leaves out, with no
all, goes back to its default. - A target the style lacks (Satellite has no
lowestRoadLineorlowestBuilding) puts that mode's layers on top.
Layer overrides
For what the settings above don't cover, layers (TrafficAreaAnalyticsLayersConfig) takes a partial MapLibre layer per layer the module draws. Each display mode draws its own:
| Mode | Layers |
|---|---|
heatmap |
heatmap |
hexgrid-2d |
hexgridFill, hexgridOutline (the outline of a hovered or clicked cell) |
hexgrid-3d |
hexgridExtrusion, hexgridExtrusionHighlight |
square-2d, square-3d |
the same four square* entries |
| every mode | regionFill, regionLine |
analyticsModule.updateConfig({
layers: {
hexgridFill: { paint: { 'fill-opacity': 0.85 } },
hexgridExtrusion: { paint: { 'fill-extrusion-vertical-gradient': false } },
regionLine: { paint: { 'line-dasharray': [2, 2] } },
},
});
paintandlayoutmerge per property onto the module's own, and win over the active metric's colours and heights.- A cell layer's
filterapplies together with the active metric's filters: both must hold. - An extrusion highlight follows its extrusion's height, so it stays on top of the prism you raised.
- A layer an entry hides through
layout.visibilitystays hidden in its display mode, and abeforeIDis ignored.
Visibility
isVisible() reports the visible setting, true by default, not whether any cell is drawn. Hiding keeps the data, and show() while hidden updates it without drawing.
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, { visible: false });
analyticsModule.setVisible(true);
analyticsModule.getShown().hexgrid.features.length > 0; // whether there are cells to draw
Events
click, hover, long-hover and the other user events fire on the hexagon and square cells of the active mode; the handler receives the cell (AreaAnalyticsTileFeature), its metric values in properties:
analyticsModule.events.on('click', (cell, lngLat) => {
console.log(cell.properties.congestionLevel, cell.properties.speed);
});
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.setEventState({ index: 0, state: 'click' });
const { click = [] } = analyticsModule.getEventStates();
Custom tooltip on hover
import { Popup } from 'maplibre-gl';
const popup = new Popup({ closeButton: false, anchor: 'left', offset: 12 });
analyticsModule.events.on('hover', (cell, lngLat) => {
const { congestionLevel, speed } = cell.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.updateConfig(partial)keeps the rest, one level deep: ametricsit is given replaces the whole record, so the metrics left out return to their defaults.- To change one metric and keep the others, use
setPalette,setColorStops,setHeightorsetFilterswith that metric. AREA_ANALYTICS_DEFAULTSholds every default, to read or to spread as the base of a config.
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
Every setter, applyConfig and updateConfig emit config-change, and each show() emits shown-features with the response (module events):
const unsubscribe = analyticsModule.events.on('config-change', (config) => {
console.log('Active metric:', config?.activeMetric);
});
Querying shown features
getShown() returns the GeoJSON each source holds — heatmap points, hexgrid and square polygons — whichever mode shows:
const { heatmap, hexgrid, square } = analyticsModule.getShown();
console.log(`Hexagonal cells: ${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('metrics.speed.'));
// [{ id: 'metrics.speed.palette', kind: 'enum', description: '…', options: ['trafficLight', 'heat', …], default: 'trafficLight' }, …,
// { id: 'metrics.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, true by default |
displayMode |
enum, hexgrid-3d by default |
activeMetric |
enum, congestionLevel by default |
regionPolygon.color |
color; unset, black on a light style, white on a dark one |
regionPolygon.fill.opacity, regionPolygon.line.opacity |
number, 0 to 1 |
regionPolygon.line.width |
number, 0 to 10 pixels |
regionPolygon.fillStyle |
enum: filled, inverted |
metrics.<metric>.palette |
enum: areaAnalyticsPalettes, default trafficLight |
metrics.<metric>.height.scaleMode |
enum: predefinedRange, currentRange, raw |
metrics.<metric>.height.maxHeightMeters, metrics.<metric>.height.minHeightMeters |
number, 0 to 5000 metres |
metrics.<metric>.height.metersPerUnit |
number, 0 to 100 metres per unit of the metric, in the raw scale mode |
metrics.<metric>.filters.min, metrics.<metric>.filters.max |
number, over the metric's predefined range |
trafficAreaAnalyticsKnobIdslists the ids,TrafficAreaAnalyticsKnobIdis their union, andTrafficAreaAnalyticsKnobValueOf<ID>the plain value one takes.- A filter's range is the one the module scales colours and heights over, not a bound on the data.
- Heights, line width and the filters have
hard-minranges: a larger value thanmaxstill applies. - Not in it:
colorStops,beforeLayerConfig,layersandevents, which take structured values. The entry shape and its kinds are the modules guide's.
API reference
TrafficAreaAnalyticsModule, TrafficAreaAnalyticsModuleConfig and AreaAnalyticsMetricConfig.
Related guides and examples
Related guides
- Traffic area analytics service - The request and its response
- Traffic overview - Every way the SDK draws traffic
Related examples
- Traffic area analytics - A geocoded city's past week as extruded hexagons
- Traffic area analytics playground - City search, legend, tooltips, and the most congested cell selected with
setEventState - Traffic area analytics config playground - Palettes, colour stops, heights and layer positions from a panel