Routing module

The Routing module draws the routes calculateRoute returns, with their waypoints, guidance and traffic along the way, and handles their interactions.

The module owns the route lines, waypoints, section overlays, instruction arrows and summary bubbles it draws, and how they look (Route styling). It never restyles the roads underneath, which belong to the Styling Foundations module. Three looks reach it from outside:

No other knob reaches route layers. See Who owns what.

The code below geocodes four stops, calculates the route through them and draws both. The response goes to showRoutes unchanged. Change an address to re-plan it:

import type { Waypoint } from '@tomtom-org/maps-sdk/core';
import { bboxFromGeoJSON, TomTomConfig } from '@tomtom-org/maps-sdk/core';
import { RoutingModule, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { calculateRoute, geocodeOne } from '@tomtom-org/maps-sdk/services';
import './style.css';
import { API_KEY } from './config';

// (Set your own API key when working in your own environment)
TomTomConfig.instance.put({ apiKey: API_KEY });

(async () => {
    const locations: Waypoint[] = await Promise.all([
        geocodeOne('W Houston St 51, NY'),
        geocodeOne('Lincoln Square, NY'),
        geocodeOne('Carnegie Hill, NY'),
        geocodeOne('Terminal C Departures LaGuardia Airport, NY'),
    ]);

    const map = new TomTomMap({
        mapLibre: {
            container: 'sdk-map',
            bounds: bboxFromGeoJSON(locations),
            fitBoundsOptions: { padding: 100 },
        },
    });

    const routingModule = await RoutingModule.create(map);
    await routingModule.showWaypoints(locations);
    await routingModule.showRoutes(
        await calculateRoute({
            locations,
            costModel: { traffic: 'historical' },
        }),
    );
})();

Two features of the module have a guide of their own:

GuideCovers
Route stylingColour, width, waypoint size, layers, the knob catalogue and layer positioning
Route sectionsThe overlays drawn per stretch of road: tunnels, ferries, traffic, speed limit signs, border crossings and the rest

Initialization

import { TomTomMap, RoutingModule } from '@tomtom-org/maps-sdk/map';
const map = new TomTomMap({ mapLibre: { container: 'map' } });
const routingModule = await RoutingModule.create(map, {
displayUnits: { distance: { type: 'metric' } }, // optional
waypoints: { entryPoints: 'main-when-available' } // optional
});
  • Without displayUnits, the labels use the global units (TomTomConfig.instance.put({ displayUnits })) in force when they are drawn. Shown routes pick up a change on their next showRoutes or configuration change.
  • getConfig() returns only what you passed, never the global units.

Displaying routes

import { calculateRoute } from '@tomtom-org/maps-sdk/services';
const locations = [
[4.9041, 52.3676], // Amsterdam
[4.4777, 51.9244] // Rotterdam
];
const routes = await calculateRoute({ locations });
await routingModule.showRoutes(routes);
await routingModule.showWaypoints(locations); // showRoutes draws no pins

showRoutes draws the lines, sections, guidance and summary bubbles, but no pins. showWaypoints draws the start, stop and destination pins. Locations describes what locations takes. Route planning parameters covers every other request option.

Displaying guidance on map

A route calculated with guidance: { type: 'coded' } draws its instruction lines and arrows with no extra configuration:

const route = await calculateRoute({ locations, guidance: { type: 'coded' } });
await routingModule.showRoutes(route);

Route selection

With alternatives, the module draws one route as selected, the first by default:

const routes = await calculateRoute({ locations, maxAlternatives: 2 });
await routingModule.showRoutes(routes, { selectedIndex: 1 });
await routingModule.selectRoute(0); // re-marks the shown routes, no recalculation

Waypoints

showWaypoints draws the pins, and clearWaypoints removes them. Neither touches the routes, and clearRoutes leaves the pins.

A stop’s pin shows its name. If the stop has a pauseDurationSeconds, the pin also shows how long the route waits there, in the SDK’s display units:

const locations = [
[2.1734, 41.3851],
{
type: 'Feature',
geometry: { type: 'Point', coordinates: [2.4467, 41.5381] },
properties: { pauseDurationSeconds: 30 * 60 } // the pin reads "30 min" under the stop name
},
[2.8214, 41.9794]
];
await routingModule.showRoutes(await calculateRoute({ locations }));
await routingModule.showWaypoints(locations);

The origin’s pin shows its wait too, though the routing service never gets an origin’s wait.

Waypoints or places

  • A waypoint is any location a route is calculated through. showWaypoints draws it with its role (start, stop or finish), its stop mark and the module’s waypoints events.
  • A place the user is still choosing among, such as a search result or a candidate stop, belongs to the Places module. Once it joins the route, clear it there and pass it to showWaypoints, so no location is drawn twice:
places.events.on('click', async (place) => {
const locations = [origin, place, destination];
await places.clear();
await routingModule.showRoutes(await calculateRoute({ locations }));
await routingModule.showWaypoints(locations);
});

If several routing modules share a location, such as the one destination of routes from several origins, you can mark it once with a Places module instead.

When you change any setting below through applyConfig, the waypoints already shown update and keep their ids and event states. Try it all on the waypoints playground:

Pins per role

waypoints.icon.style styles every pin. waypoints.icon.start, middle and finish each style one role on top of it. A pin without a fillColor takes the route’s color:

const routingModule = await RoutingModule.create(map, {
waypoints: {
icon: {
style: { outlineColor: '#FFFFFF' },
start: { style: { fillColor: '#1B7A3E' } },
finish: { style: { fillColor: '#C0392B' } }
}
}
});

Stop marks

  • A stop’s pin carries its number among the stops, starting at 1. waypoints.stopNumbering can also be 'letters' (A to Z, then AA) or 'none'.
  • The count includes stops that a mapping draws its own icon for, so the marks keep matching the stop order. Those icons and a middle.image carry no mark.
  • The start and finish pins draw their own glyph instead.
  • The mark is the waypoint’s stopDisplayLabel property, which events.waypoints handlers receive.

Labels

Each pin’s label shows its POI name, otherwise its address, and its wait. waypoints.label.visible: false drops the labels. To write your own, set waypoints.label.title to a function of the waypoint or a MapLibre expression, like label.title for places:

import { buildWaypointTitle } from '@tomtom-org/maps-sdk/map';
routingModule.applyConfig({
waypoints: {
label: {
title: (waypoint) => (waypoint.properties.indexType === 'finish' ? 'Home' : buildWaypointTitle(waypoint) ?? '')
}
}
});

Route styling covers the label’s colour, halo and size.

Hover and click

Once a handler covers the waypoints, a hovered or clicked pin and its stop mark grow by waypoints.highlight.sizeFactor. It defaults to 1.2 and is clamped to 1 to 1.5, and 1 draws the pin like any other. The waypoints scope has the event state methods to set that state from your own UI:

routingModule.events.waypoints.on('hover', (waypoint) => showStopCard(waypoint.properties));
routingModule.events.waypoints.setEventState({ index: 1, state: 'click' });

A waypoint keeps the id you give it, a string or a number, and gets a generated string otherwise. The handlers, setEventState and getEventStates carry that same id, so a waypoint with the id 5 is 5 in all of them.

Icons of your own

A role’s image replaces its pin, and waypoints.icon.mapping picks a pin per waypoint from its data, as icon.mapping does for places:

  • to: 'poiCategory' draws the SDK’s pin for the category fn returns, so a stop that came from search shows what kind of place it is.
  • to: 'imageID' draws the image fn names, from the style’s sprite or from waypoints.icon.customIcons.
  • If fn returns undefined for a waypoint, it keeps its role’s pin and stop mark.
const routingModule = await RoutingModule.create(map, {
waypoints: {
icon: {
finish: { image: { image: chequeredFlagSVG } },
mapping: { to: 'poiCategory', fn: (waypoint) => waypoint.properties.poi?.categories?.[0] }
}
}
});
  • The module draws an image at pixel ratio 2 unless it sets pixelRatio. It stands on the waypoint by its bottom middle, like the SDK’s 120 × 140 pixel pins.
  • RoutingModuleSerializableConfig keeps the styles, the stop numbering and images given as a URL, data URI or SVG text. It leaves out mapping, a function title and an HTMLImageElement image.

Showing and hiding

setVisible(false) hides everything the module draws (route lines, waypoints, sections, guidance, charging stops, summary bubbles and border crossings) and keeps what it shows:

const routingModule = await RoutingModule.create(map, { visible: false }); // starts hidden
await routingModule.showRoutes(route); // kept, not drawn
routingModule.setVisible(true); // draws the route
routingModule.isVisible(); // true
  • showRoutes, showWaypoints, the clear methods and a style change leave a hidden module hidden.
  • It sets the config’s visible, so isVisible() reads the setting back and config-change reports it.
  • While the module is shown, each part’s own visible (sections.<type>.visible, summaryBubbles.visible, …) still decides whether that part draws.

Events

Each part of a route has its own scope on the user events surface:

routingModule.events.mainLines.on('click', (route, lngLat) => console.log(route.properties));
routingModule.events.waypoints.on('click', (waypoint) => console.log(waypoint.properties.indexType));
routingModule.events.incidents.on('hover', (section) => console.log(section.properties.delayInSeconds));
routingModule.events.instructionLines.on('click', (instruction) => console.log(instruction.properties));
routingModule.events.summaryBubbles.on('click', (summary) => console.log(summary.properties));
// Removes every click handler of the scope; `on` returns an unsubscribe for one handler
routingModule.events.mainLines.off('click');
  • A scope’s off also stops the click handlers of routingModule.events on the route lines, as user events explains.
  • Route sections covers the section scopes: ferries, tollRoads, tunnels, vehicleRestricted, countryCrossings and one <type>Sections per generated type.
  • The instruction arrows have no scope. A click on one is a click on its instruction.

Once a handler covers the routes, a hovered or clicked route draws its outline wider, selected or not. The module’s event state methods act on the routes, and each named scope has them for its own features.

To select a route on click, call selectRoute. It clears the click state of the route lines and summary bubbles, since the selected style marks the route from then on. The route you clicked loses its wider outline with the click, and takes it again once the pointer moves on it, if a handler covers the selected route. To keep the selected route from reacting to the pointer at all, narrow the handler to the alternatives:

routingModule.events.mainLines
.where((route) => route.properties.routeState === 'deselected')
.on('click', (route) => routingModule.selectRoute(route.properties.index));
routingModule.setEventState({ index: 1, state: 'hover' });

route.properties.index is the route’s index in the routing response, while selectRoute takes its position among the shown routes. The two match when you show every route of the response. When you show only some, pass the route’s position in the collection you gave showRoutes.

Keep the selected route hoverable when hovering it does something of its own, such as pointing at its card in a list.

highlight.outline.widthFactor sets how much wider. It defaults to 1.2 and is clamped to 1 to 1.25. A section halo beneath the route is wider still, so it keeps a thin edge while the route is hovered and shows in full again once the pointer leaves. 1 draws a hovered route like any other. An outline width you set through layers.mainLines.routeOutline stays as you set it, in every state.

routingModule.applyConfig({ highlight: { outline: { widthFactor: 1.25 } } });

Multiple module instances

Each instance has its own sources, layers, configuration and events, for comparing route types or showing routes for several users:

const carRoutes = await RoutingModule.create(map, { color: '#2ECC71' });
const truckRoutes = await RoutingModule.create(map, { color: '#E67E22' });
await carRoutes.showRoutes(carRoute);
await truckRoutes.showRoutes(truckRoute);

Reading shown data

getShown() returns what the module draws, as prepared for display, split by what draws it:

const shown = routingModule.getShown();
if (shown.mainLines.features.length > 0) {
console.log(`Showing ${shown.mainLines.features.length} routes, ${shown.waypoints.features.length} waypoints`);
}

Its entries are mainLines, waypoints, incidents, ferries, tollRoads, tunnels, vehicleRestricted, instructionLines, instructionArrows, summaryBubbles, countryCrossings and sectionFocus, plus one <type>Sections per generated section type, such as urbanSections and motorwaySections.

API reference

RoutingModule lists every method, and RoutingModuleConfig every setting.

Services integration