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:

import { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';
import { RoutingModule, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { calculateRoute, geocodeOne } from '@tomtom-org/maps-sdk/services';

const locations = 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: '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' } }));

The demo below runs it:

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

Guide Covers
Route styling Colour, width, waypoint size, layers, the knob catalogue and layer positioning
Route sections The 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
});

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

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

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:

const routingModule = await RoutingModule.create(map, {
    waypoints: {
        icon: {
            finish: { image: { image: chequeredFlagSVG } },
            mapping: { to: 'poiCategory', fn: (waypoint) => waypoint.properties.poi?.categories?.[0] }
        }
    }
});

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

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');

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 } } });

Editing a route by dragging

The waypoints and mainLines scopes take the drag events. Drag a pin to move its stop, or drag a route line to pull a new stop onto it. The module reports the drag and plans nothing, so you call calculateRoute and showRoutes with the new waypoints:

import { findBestWaypointInsertionIndex } from '@tomtom-org/maps-sdk/core';
import { calculateRoute } from '@tomtom-org/maps-sdk/services';

let dragged = -1;
routingModule.events.waypoints.on('dragstart', (waypoint) => {
    dragged = waypoint.properties.index;
});
routingModule.events.mainLines.on('dragstart', (route, lngLat) => {
    dragged = findBestWaypointInsertionIndex(route, waypoints, lngLat.toArray());
    waypoints.splice(dragged, 0, lngLat.toArray());
});

const moveStop = async (_, lngLat) => {
    waypoints[dragged] = lngLat.toArray();
    await routingModule.showWaypoints(waypoints);
};
const planAgain = async () => routingModule.showRoutes(await calculateRoute({ locations: waypoints }));
for (const scope of [routingModule.events.waypoints, routingModule.events.mainLines]) {
    scope.on('drag', moveStop);
    scope.on('dragend', planAgain);
}

The route dragging plugin does all of this for you. It moves the pins, inserts the stops pulled off a line, and re-plans on a throttle while you drag, showing the answers in order. The React route planner drags its stops through it, and looks up the address where you drop a stop.

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.

Related guides and examples

Services integration

Related examples

Related map modules