This is the documentation of version 1.0.0-rc.2. Go to the latest version, 1.0.0-rc.5.

Reachable ranges module

ReachableRangesModule draws the areas reachable from one or more origins within a budget of time or distance, as calculateReachableRanges returns them. Each range is one band, titled from its budget, nested around its own origin and coloured from the map. Calculating the ranges needs an API key with Calculate Reachable Range access, which is in private preview. The module draws them on any map.

The demo shows 10, 20 and 30 minutes of driving from two stations, with every module setting on its panel:

Showing ranges

import { TomTomConfig } from '@tomtom-org/maps-sdk/core';
import { ReachableRangesModule, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { calculateReachableRanges } from '@tomtom-org/maps-sdk/services';

TomTomConfig.instance.put({ apiKey: 'YOUR_API_KEY' });
const origin: [number, number] = [4.9041, 52.3676];
const map = new TomTomMap({ mapLibre: { container: 'map', center: origin, zoom: 9 } });
const rangesModule = await ReachableRangesModule.create(map);

await rangesModule.show(
    await calculateReachableRanges([10, 20, 30].map((value) => ({ origin, budget: { type: 'timeMinutes', value } }))),
);

show replaces the ranges shown, and clear removes them. The module draws what calculateReachableRanges returns unchanged:

const origins: [number, number][] = [[4.9003, 52.3791], [5.1109, 52.0894]];
await rangesModule.show(
    await calculateReachableRanges(
        origins.flatMap((origin) => [10, 20, 30].map((value) => ({ origin, budget: { type: 'timeMinutes', value } }))),
    ),
);

Titles

A range's title is the text of the labels along its border. By default, it is the range's budget with its unit, such as 10 min or 5 km, otherwise its position in what show was given, from 1. A lineLabel.title function of your own replaces it, and changing it relabels the bands shown:

rangesModule.updateConfig({ lineLabel: { title: (feature, index) => `Zone ${index + 1}` } });
rangesModule.updateConfig({ lineLabel: { ...rangesModule.getConfig()?.lineLabel, size: 13, color: '#1d3f8a' } });

Fill styles

fill.style Draws
'filled' (default) each band coloured, the innermost on top
'outline' only each range's border, thick and coloured
'inverted' what lies out of reach: the world beyond the largest range of every origin, then a ring outside each smaller one
const outOfReach = await ReachableRangesModule.create(map, { fill: { style: 'inverted' } });

These are the geometries module's fill styles, applied per band.

Band colours

By default, the bands ramp from the map's accent on the innermost band toward its land colour on the outermost, both read from map.mapColors.

import { StylingFoundationsModule } from '@tomtom-org/maps-sdk/map';
import { deriveMapColors } from '@tomtom-org/maps-sdk-plugin-map-theme';

const styling = await StylingFoundationsModule.get(map);
styling.setMapColors(deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#f4f1ea'])); // the bands follow

fill.palette sets colours of your own instead, innermost band first, and starts over after the last. Map colour changes don't touch them, and an empty list hands the colours back to the map. updateConfig replaces the whole fill, so spread the current one to keep its style:

rangesModule.applyConfig({ fill: { palette: ['#b3001b', '#e85d04', '#faa307'] } });
rangesModule.updateConfig({ fill: { ...rangesModule.getConfig()?.fill, palette: [] } });

Bands are ranked per origin, so with several origins the ranges of the same rank share a colour.

Hovered and clicked bands

Once a handler covers the bands, the band under the pointer draws a denser fill and a wider border in its own colour. So does a clicked band. The module's event state methods set that state from your own UI.

rangesModule.updateConfig({ highlight: { fill: { opacityFactor: 1 }, line: { widthFactor: 2.5 } } }); // a bolder border alone

Events

events covers the bands and their labels. A handler receives the band as drawn, with the title and color the module gave it in its properties:

rangesModule.events.on('click', (band) => console.log(band.properties.title)); // '20 min'

Two named scopes each cover one surface, with the same handlers and event states. events.geometry covers the bands, their borders and the labels along them, which are drawn from the same features. events.geometryLabel covers the title points getShown() lists under geometryLabel.

rangesModule.events.geometry.on('hover', (band) => showTooltip(band.properties.title));

The user events guide covers the event types and their timings.

Showing and hiding

setVisible hides and shows the bands, borders and labels together, and visible: false at creation starts the module hidden. show() and clear() never change it, so ranges shown while hidden appear on setVisible(true). The state survives a style change.

rangesModule.setVisible(!rangesModule.isVisible());

Layer position

The bands sit below the map's labels unless beforeLayerConfig says otherwise. A single target places the fill and the borders together, and the record form { all?, fill?, line? } places them separately, as on the geometries module:

const ranges = await ReachableRangesModule.create(map, {
    beforeLayerConfig: { fill: 'lowestRoadLine', line: 'lowestLabel' }, // fill under the roads
});

Layer overrides

For anything the settings don't cover, layers takes a partial MapLibre layer for the bands (fill), their borders (line) and labels (label, lineLabel), the same entries as the geometries module's. paint and layout merge per property onto the module's own, in every fill style:

ranges.updateConfig({ layers: { line: { paint: { 'line-dasharray': [2, 1] } } } });

Restyling and saving the settings

A config change restyles the bands already shown, without calculating the ranges again. updateConfig changes only what you give it, applyConfig replaces the whole config, and resetConfig returns to the defaults.

getConfig() returns the settings to store. All of it survives JSON except the function lineLabel.title. ReachableRangesModuleSerializableConfig types what you parse back, and on replay you add lineLabel.title in code:

import type { ReachableRangesModuleSerializableConfig } from '@tomtom-org/maps-sdk/map';

localStorage.setItem('ranges', JSON.stringify(rangesModule.getConfig()));

const saved: ReachableRangesModuleSerializableConfig = JSON.parse(localStorage.getItem('ranges') ?? '{}');
rangesModule.applyConfig({ ...saved, lineLabel: { ...saved.lineLabel, title: (feature, index) => `Zone ${index + 1}` } });

Every setting as data

reachableRangesKnobCatalogue lists every plain-valued setting of the module with its kind, its range or options and, where one value holds everywhere, its default. Each id is the setting's path in the config, so a panel can build its controls from it and set each with setKnob:

import { reachableRangesKnobCatalogue, setKnob } from '@tomtom-org/maps-sdk/map';

setKnob(rangesModule, reachableRangesKnobCatalogue, 'lineLabel.size', 13);
Id Kind
visible, fill.style, fill.palette toggle, enum, colours
highlight.fill.opacityFactor, highlight.line.widthFactor factor, 1 to 4 and 1 to 3
lineLabel.size, lineLabel.color, lineLabel.haloColor, lineLabel.haloWidth, lineLabel.opacity, lineLabel.font, lineLabel.typeface number, colour, colour, number, number, enums, enum

Reading shown data

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

const { geometry } = rangesModule.getShown();
console.log(`Bands: ${geometry.features.length}`);

Multiple module instances

The module is data-owned, so each instance has its own sources and layers. Two can show two sets of ranges side by side, apart from any GeometriesModule on the same map.

const byCar = await ReachableRangesModule.create(map);
const byBike = await ReachableRangesModule.create(map, { fill: { style: 'outline' } });

Related guides and examples

Related guides

Related examples

Related map modules