Traffic area analytics
Important note Traffic Area Analytics is unavailable on a Freemium or Pay As You Grow (PAYG) basis. Click the Request Access button above to contact our Sales team.
trafficAreaAnalytics runs a historical traffic report for a polygon or multi-polygon region: give it the region, a date range or a list of dates, and the metrics you want, and one call returns the aggregates, their time series and per-tile values — no polling.
Introduction
- Any region: a neighbourhood to a country, as a GeoJSON
PolygonorMultiPolygon. - Any period: a continuous range of up to 31 days, or a list of separate days.
- Several metrics in one call: speed, free-flow speed, congestion level, travel time and network length.
- Breakdowns: aggregates for the whole period, time series from hourly to yearly, and per-tile values that the Traffic Area Analytics Module draws as grids or a heatmap.
- For: planners measuring an infrastructure or policy change, consultants benchmarking areas, analysts and researchers reporting on mobility.
Core concepts
A report is described by:
- Region (
geometry): a GeoJSONPolygonorMultiPolygon; one region per call. - Date selection, one of:
startDateand optionalendDate: a continuous range.days: a list of dates, consecutive or not.
- Metrics (
metrics): the metrics to compute, or'all'. - Functional road classes (
functionalRoadClasses): the road classes to include, or'all'. - Hours (
hours): the hours of the day in UTC (0–23) to include, or'all'.
Basic usage
Traffic Area Analytics is a TomTom Move Portal product, so it takes a Move Portal API key, not the Maps API key the rest of the SDK uses. Keep your Maps key in TomTomConfig and pass the Move Portal key per call (per-call overrides):
import { TomTomConfig } from '@tomtom-org/maps-sdk/core';
import { trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';
TomTomConfig.instance.put({ apiKey: 'YOUR_API_KEY' }); // Maps API key, for everything else
const result = await trafficAreaAnalytics({
apiKey: 'YOUR_MOVE_PORTAL_KEY', // this service only
startDate: '2024-08-06', // YYYY-MM-DD string or Date
endDate: '2024-08-12', // optional: defaults to three days ago
metrics: ['speed', 'congestionLevel'],
functionalRoadClasses: ['MOTORWAY', 'MAJOR_ROAD', 'OTHER_MAJOR_ROAD', 'SECONDARY_ROAD'],
hours: [5, 6, 7, 15, 16], // UTC: Amsterdam's rush hours in summer time (UTC+2)
geometry: {
type: 'Polygon',
coordinates: [
[[4.896128, 52.382402], [4.875701, 52.368459], [4.923611, 52.36341], [4.896128, 52.382402]],
],
},
});
const region = result.features[0];
console.log(region.properties.baseData.speed); // km/h
console.log(region.properties.baseData.congestionLevel); // %
A 401 or 403 on this service usually means the call went out with your Maps API key.
Available metrics
areaAnalyticsMetricKeys lists them:
metrics value |
Description | Unit |
|---|---|---|
'speed' |
Average speed on the road network | km/h |
'freeFlowSpeed' |
Average speed under uncongested conditions | km/h |
'congestionLevel' |
Increase in travel time above free flow | % |
'travelTime' |
Average travel time per 10 km | minutes |
'networkLength' |
Total length of the road segments with data | metres |
Parameters
Date selection
Give either startDate, with an optional endDate, or days. Both take Date objects or 'YYYY-MM-DD' strings, read as UTC calendar days.
Continuous range (startDate / endDate)
endDateis 1 to 31 days afterstartDate, so equal dates fail validation.- The latest date the API accepts is two days ago, but it publishes a day 24 to 48 hours after it ends. An omitted
endDatedefaults to three days ago, the latest date that always has data.
// The last week with data: endDate defaults to three days ago
const startDate = new Date();
startDate.setUTCDate(startDate.getUTCDate() - 9);
const lastWeek = await trafficAreaAnalytics({ startDate, /* ... */ });
const firstWeekOfAugust = await trafficAreaAnalytics({ startDate: '2024-08-01', endDate: '2024-08-07', /* ... */ });
Specific dates (days)
Each of days is two days ago or earlier.
// Every Monday of August 2024
const result = await trafficAreaAnalytics({
days: ['2024-08-05', '2024-08-12', '2024-08-19', '2024-08-26'],
metrics: ['speed', 'congestionLevel'],
functionalRoadClasses: 'all',
hours: [7, 8, 9, 17, 18],
geometry: { /* ... */ },
});
Functional road classes
The functionalRoadClasses parameter selects the road classes, or 'all'. The importable functionalRoadClasses catalogue lists them, most important first:
| Value | Description |
|---|---|
'MOTORWAY' |
Motorways, freeways, and major roads |
'MAJOR_ROAD' |
Major roads, less important than motorways |
'OTHER_MAJOR_ROAD' |
Other major roads connecting neighbouring regions |
'SECONDARY_ROAD' |
Secondary roads linking parts of the same region |
'LOCAL_CONNECTING_ROAD' |
Local roads providing settlement access |
'LOCAL_ROAD_HIGH_IMPORTANCE' |
Local roads of high importance within a settlement |
'LOCAL_ROAD' |
Local roads within settlement sections |
'LOCAL_ROAD_MINOR_IMPORTANCE' |
Local roads of minor importance (dead-ends, alleys) |
'OTHER_ROAD' |
Other roads (paths, cycle routes, pedestrian infrastructure) |
Region geometry
geometry takes one GeoJSON Polygon or MultiPolygon, in [longitude, latitude] order, each ring closed (its first and last positions equal):
const geometry = {
type: 'MultiPolygon',
coordinates: [
[[[4.89, 52.37], [4.91, 52.37], [4.91, 52.39], [4.89, 52.39], [4.89, 52.37]]],
[[[4.95, 52.40], [4.97, 52.40], [4.97, 52.42], [4.95, 52.42], [4.95, 52.40]]],
],
};
A city's boundary comes from geocodeOne and geometryData:
import { geocodeOne, geometryData, trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';
const place = await geocodeOne('Amsterdam, Netherlands');
const boundaries = await geometryData({ geometries: [place] });
const result = await trafficAreaAnalytics({
startDate: '2024-08-06',
endDate: '2024-08-12',
metrics: ['speed', 'congestionLevel'],
functionalRoadClasses: 'all',
hours: [7, 8, 9, 17, 18],
geometry: boundaries.features[0].geometry,
});
name optionally names the report; the SDK names one when you don't.
Response structure
The result is a TrafficAreaAnalytics FeatureCollection with one feature, the submitted region.
Top-level properties
const { startDate, endDate, metrics, frcs, ranges } = result.properties;
startDateandendDateare the first and last analysed days, asDates at midnight UTC.frcsholds the road classes as the API's numeric indices, 0 (MOTORWAY) to 8 (OTHER_ROAD).rangesholds each metric's minimum and maximum over the tiles, such asranges.congestionLevel.
Feature properties
const region = result.features[0];
const { baseData, timedData, tiledData, anomalies } = region.properties;
Base data
baseData aggregates each requested metric over the whole period; the metrics not requested are absent:
const { speed, freeFlowSpeed, congestionLevel, travelTime, networkLength } = region.properties.baseData;
Timed data
timedData breaks the metrics down per granularity. Each has its own entry type, with the metric fields of baseData and its time identifiers always set:
| Granularity | Entry type | Time identifiers |
|---|---|---|
yearly |
AreaAnalyticsYearlyEntry |
year |
monthly |
AreaAnalyticsMonthlyEntry |
year, month (1–12) |
weekly |
AreaAnalyticsWeeklyEntry |
year, week (ISO 8601, from Monday) |
daily |
AreaAnalyticsDailyEntry |
date (midnight UTC) |
hourly |
AreaAnalyticsHourlyEntry |
date, hour (0–23) |
average |
AreaAnalyticsAverageEntry |
day (1 = Monday to 7 = Sunday), hour (0–23) |
const { daily, hourly, average } = region.properties.timedData;
daily?.forEach((entry) => {
console.log(entry.date.toISOString().slice(0, 10), entry.speed, entry.congestionLevel);
});
// hourly spans every day of the range, so read the day with the hour
hourly?.forEach((entry) => {
console.log(`${entry.date.toISOString().slice(0, 10)} ${entry.hour}:00: ${entry.speed} km/h`);
});
average?.forEach((entry) => {
console.log(`Day ${entry.day}, ${entry.hour}:00: ${entry.congestionLevel}%`);
});
- Which granularities come back depends on the date range and the hours.
- Every time identifier is in UTC, like
hours. averageholds one entry per day of the week and hour with data, Monday first: a range shorter than a week, or anhoursfilter, yields fewer than 168. Readdayandhour, not the index.- A
weeklyentry'syearis the ISO 8601 year its week counts in: the week from Monday 30 December 2024 is week 1 of 2025.
Tiled data
tiledData holds per-tile values, when the response has them: each tile's tileCentre ([longitude, latitude]) and the requested metrics, networkLength being the road length within that tile.
region.properties.tiledData?.tiles.forEach((tile) => {
const [lon, lat] = tile.tileCentre;
console.log(`Tile at [${lon}, ${lat}]: congestion=${tile.congestionLevel}%`);
});
The Traffic Area Analytics Module draws them: pass it the whole response.
Anomalies
anomalies maps a metric to its anomalies: runs of UTC hours, each labelled with the public holidays it overlaps. It is currently always empty: the service detects anomalies only over 35 days or more, beyond a request's 31-day limit.
const speedAnomalies = region.properties.anomalies?.speed ?? [];
speedAnomalies.forEach((anomaly) => {
console.log(anomaly.startDate, anomaly.endDate, anomaly.labels);
});
Error handling
A failed call rejects with an SDKServiceError, carrying the HTTP status and the service; a request breaking the date rules rejects before it is sent (Error handling).
API reference
trafficAreaAnalyticsTrafficAreaAnalyticsParams– Request parametersTrafficAreaAnalytics– Response type- Area Analytics API – API documentation
Related guides and examples
Related examples
- Traffic area analytics NodeJS – Server-side analytics for a geocoded city
- Traffic area analytics – The response drawn as extruded hexagons
Related guides
- Traffic area analytics module – Draw the response on a map
- Traffic incident details – Live incidents of an area
- Geometry data – A place's boundary, for the region