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: { color: '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 setMode():
| 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 using setMode():
analyticsModule.setMode('heatmap');
analyticsModule.setMode('hexgrid-2d');
analyticsModule.setMode('square-3d');
Metrics
Five metrics are available to drive color and extrusion height. Set the active one via activeMetric. Per-metric style settings (color, 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.setMetric('speed');
analyticsModule.setMetric('travelTime');
analyticsModule.setMetric('freeFlowSpeed');
You can also pre-configure multiple metrics at init time:
const analyticsModule = await TrafficAreaAnalyticsModule.create(map, {
activeMetric: 'congestionLevel',
metricConfig: {
congestionLevel: { color: 'trafficLight' },
speed: { color: 'heat' },
travelTime: { height: { maxHeightMeters: 300 } },
},
});
Color
Color configuration is per-metric and set under metricConfig.[metricKey].color. You can use a preset theme name or provide custom color stops.
Preset themes
// Apply a preset theme to all metrics
analyticsModule.setColor('trafficLight'); // green → amber → red (default)
analyticsModule.setColor('heat'); // blue → orange → red
analyticsModule.setColor('monochrome'); // light grey → dark grey
analyticsModule.setColor('viridis'); // purple → blue → green → yellow
analyticsModule.setColor('plasma'); // purple → magenta → orange → yellow
Custom color stops
Provide an AreaAnalyticsColorStopsConfig object with a valueType and stops array.
'raw' (default) — stop value fields are actual data values:
analyticsModule.setColor({
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.setColor({
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.setColor({
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 revert to the default 'trafficLight' theme:
analyticsModule.setColor(undefined);
For speed, the color order is automatically inverted so that slow speeds render as "bad" (red) and fast speeds as "good" (green).
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: scaleFactor is multiplied directly with the raw metric value to obtain height in meters
analyticsModule.setHeight({ scaleMode: 'raw', scaleFactor: 10 });
Filtering
Filter visible tiles by value range using filter(), 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.filter({ min: 50 });
// Show only tiles within a range
analyticsModule.filter({ min: 20, max: 80 });
// Clear the filter
analyticsModule.clearFilter();
analyticsModule.filter(undefined); // equivalent
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.moveBeforeLayer({ 'hexgrid-3d': 'top' });
moveBeforeLayer leaves a mode it names no target for where it is; applyConfig puts it back at
its default.
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');
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 setColor, setHeight and filter, which take the metrics to target:
analyticsModule.applyConfig({ displayMode: 'heatmap' }); // everything else back to its default
analyticsModule.updateConfig({ activeMetric: 'speed' }); // displayMode stays 'heatmap'
analyticsModule.setColor('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}`);
API reference
For complete documentation of all properties, methods, and types, see the TrafficAreaAnalyticsModule API Reference.
Related guides and examples
Traffic overview
- Traffic Overview - Overview of all traffic visualization options
Related traffic modules
- Traffic Flow Module - Real-time traffic speed conditions across the road network
- Traffic Incidents Module - Real-time traffic events and incidents
Related examples
- Traffic Area Analytics - Basic area analytics with a geocoded location
- Traffic Area Analytics Playground - Full-featured playground with city search, legend, and tooltips
- Traffic Area Analytics Config Playground - 2D/3D mode toggle and custom color pickers