Traffic incident details module
The Traffic Incident Details Module renders incidents fetched from the
trafficIncidentDetails() REST
service as a developer-controlled overlay. Use this module when you need typed
incident objects in your app code — for popups, list views, route correlation,
or interactive focus — and want to control exactly when incidents render.
What this module owns: the incident lines and icons it draws from the details you fetched, and how they look — including the focus treatment below. The live incident tiles belong to the Traffic Incidents Module, and their colours to the Styling module. The module borrows those colours, so the traffic.incidents.*Color knobs recolour it too (Borrowed looks). No other knob reaches it. See Who owns what.
Looking for something else?
- The default vector-tile Traffic Incidents Module is the recommended default. It auto-renders all incidents on the map, with full road-class-aware styling and declutter.
- Use the details module when your application already fetches incidents via the REST service and you want the on-map rendering to mirror that exact set — no more, no less.
When to use the details module vs TrafficIncidentsModule
| You need… | Use |
|---|---|
| Live, full-coverage incidents with no fetch | TrafficIncidentsModule |
| Typed incident objects in your app code | TrafficIncidentDetailsModule |
| Render exactly the result of a single REST call | TrafficIncidentDetailsModule |
| Per-feature focus / highlight a subset | TrafficIncidentDetailsModule |
| Maximum visual fidelity (per-road-class styling, declutter) | TrafficIncidentsModule |
The vector-tile incidents are hidden in the default style, so the details module
does not collide with them out of the box. If you've explicitly enabled
TrafficIncidentsModule elsewhere in your app, hide it again before using
the details module to avoid rendering the same incidents twice.
Initialization
import { TomTomMap, TrafficIncidentDetailsModule } from '@tomtom-org/maps-sdk/map';
const map = new TomTomMap({ mapLibre: { container: 'map' } });
const incidentDetailsModule = await TrafficIncidentDetailsModule.create(map);
Rendering incidents
Fetch incidents with the service, then pass the result to show():
import { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';
const result = await trafficIncidentDetails({
bbox: [4.85, 52.34, 4.95, 52.40],
timeValidityFilter: ['present'],
});
await incidentDetailsModule.show(result);
show() replaces any previously rendered snapshot. Call clear() to remove
all incidents.
Focusing a subset
setFocus(ids) writes MapLibre feature-state on each rendered incident.
The default visual treatment widens focused incidents and paints a black
outline beneath them; unfocused incidents are unchanged — the focus
treatment adds emphasis, it does not dim the other incidents. Pass
null to clear focus.
incidentDetailsModule.setFocus(['incident-id-1', 'incident-id-2']);
incidentDetailsModule.setFocus(null); // clear
A hovered or clicked incident draws the same treatment once a handler covers the module, so a
click handler alone highlights what the pointer is on. Its state is the module's
event state, which putEventState puts from your own UI.
Customising or disabling the focus treatment
The default visual is opinionated but not baked in — pass a focus config to
TrafficIncidentDetailsModule.create(map, …):
// Override individual fields (the others fall back to defaults).
const incidentDetailsModule = await TrafficIncidentDetailsModule.create(map, {
focus: { outlineColor: '#1976d2', widthFactor: 1.8 },
});
widthFactor multiplies the width of focused incidents. It defaults to 1.6 and is
clamped to INCIDENT_FOCUS_WIDTH_FACTOR_RANGE, 1–1.8: 1 keeps the outline without
widening, and a wider stripe would cover the outline at high zoom.
// Disable the visual entirely, for focused, hovered and clicked incidents alike.
// setFocus() still writes `feature-state.focused`, and user events `eventState`,
// so you can drive your own styling — e.g. extra MapLibre layers reading the
// same states, or a sidebar overlay.
const incidentDetailsModule = await TrafficIncidentDetailsModule.create(map, { focus: false });
focus also changes on a module already showing incidents, its fields and false alike, the
focused ones repainting in place:
incidentDetailsModule.updateConfig({ focus: { outlineColor: '#c2185b', widthFactor: 1.2 } });
incidentDetailsModule.updateConfig({ focus: false });
Visibility and layer ordering
isVisible() reports the visible setting, not whether any incident is drawn: a module showing
nothing still returns true. Read getShown() to know whether incidents are shown. While hidden,
show() updates the data without drawing it.
incidentDetailsModule.setVisible(false);
incidentDetailsModule.setVisible(true);
incidentDetailsModule.isVisible(); // true
incidentDetailsModule.getShown().incidents.features.length > 0; // whether incidents are shown
// Place the incidents above all other layers (default: below labels):
incidentDetailsModule.updateConfig({ beforeLayerConfig: 'top' });
beforeLayerConfig takes a single target, 'top' or a key of
mapStyleLayerIDs — the same shape every module accepts for the whole module. The module has no
parts to position apart, so there is no record form.
Layer overrides
For what the settings above don't cover, layers takes a partial MapLibre layer for each layer the
module draws, bottom to top: focusHalo, outline, innerSolid, innerChevron, innerPattern,
then the incidentMarker, jamMarker and closedRoadMarker icons. paint and layout merge per
property onto the module's own, and what you set takes precedence over the incident colours and the
focus treatment:
incidentDetailsModule.updateConfig({
layers: {
incidentMarker: { minzoom: 11 }, // the category icons from zoom 11
jamMarker: { layout: { 'text-field': '' } }, // jam icons without their delay
innerChevron: { layout: { visibility: 'none' } }, // no direction chevrons
},
});
A layer an entry hides through layout.visibility stays hidden while the module is shown, and a
beforeID is ignored: beforeLayerConfig places the layers.
Every setting as data
trafficIncidentDetailsKnobCatalogue lists the module's plain-valued settings with their kind,
range and, where one value holds everywhere, their default, readable with no map loaded:
import { trafficIncidentDetailsKnobCatalogue } from '@tomtom-org/maps-sdk/map';
const widthFactor = trafficIncidentDetailsKnobCatalogue.find(({ id }) => id === 'focus.widthFactor');
// { id: 'focus.widthFactor', kind: 'factor', description: '…', range: { min: 1, max: 1.8, step: 0.1, bounds: 'hard' }, default: 1.6 }
- It holds
visible,focus.outlineColorandfocus.widthFactor. The outline colour has no default: unset, it follows the map's light/dark theme. trafficIncidentDetailsKnobIdslists the ids,TrafficIncidentDetailsKnobIdis their union, andTrafficIncidentDetailsKnobValueOf<ID>the plain value one takes.- Not in it:
beforeLayerConfig,layers,focus: false, which turns the treatment off, andevents. The incident lines take the style's incident colours, which the styling catalogue'straffic.incidents.*knobs set. - The demo above builds its panel from the catalogue: click an incident to focus it, then turn the focus knobs.
Limitations
The Incident Details REST API returns a feature list, not a rendering-ready tile. Several tags that the vector-tile pipeline injects per-feature are absent from the REST response, so the module renders with reduced visual fidelity:
- No road-class-aware width/offset (every incident is a uniform stripe centred on the road).
- No declutter — every incident shows above the layer minzoom.
- No secondary cause icon overlay (only the primary category icon renders).
If you need full visual fidelity, use TrafficIncidentsModule.
Overlapping incidents: when multiple incidents share the same point or
stack along the same road segment, MapLibre's symbol collision culling keeps
only the highest-sort-key feature. Culled features are not reachable via
queryRenderedFeatures. If you need to surface multiple incidents at one
location, work directly with the FeatureCollection you passed to show().