Maps SDK for JavaScript
Back to all examples
Route Leg Options Playground
Give each leg of a calculated route its own route type and avoid list, and recalculate on every change.
Route Leg Options Playground
import { bboxFromGeoJSON, formatDistance, formatDuration, type Place, TomTomConfig } from '@tomtom-org/maps-sdk/core'; import { RoutingModule, TomTomMap } from '@tomtom-org/maps-sdk/map'; import { calculateRoute, type GeocodingProps, geocodeOne } from '@tomtom-org/maps-sdk/services'; import { API_KEY } from './config'; import { buildLegPanel, type LegPanel, type LegStats } from './legPanel'; import './style.css'; import { initTogglePanel } from './togglePanel'; TomTomConfig.instance.put({ apiKey: API_KEY }); const STOP_QUERIES = ['Barcelona, Spain', 'Mataró, Spain', 'Girona, Spain', 'Figueres, Spain']; // Fixed in code, not a control: the panel is about per-leg options, and a route that waits // somewhere is what makes each leg's reported stop time worth showing. The destination cannot wait. const WAIT_SECONDS_BY_STOP: Record<number, number> = { 1: 10 * 60, 2: 5 * 60 }; const element = <T extends HTMLElement>(selector: string): T => { const found = document.querySelector<T>(selector); if (!found) throw new Error(`Missing element: ${selector}`); return found; }; const legContainer = element<HTMLElement>('#ui-legs'); const totalsList = element<HTMLElement>('#ui-totals'); const statusText = element<HTMLElement>('#ui-status'); const placeName = (place: Place<GeocodingProps>): string => place.properties.poi?.name ?? place.properties.address?.freeformAddress ?? 'Stop'; const renderTotals = (rows: [string, string][]): void => { totalsList.innerHTML = rows .map(([label, value]) => `<dt class="ui-summary-label">${label}</dt><dd class="ui-summary-value">${value}</dd>`) .join(''); }; // A leg's options live on the stop it *arrives at*, so leg 0 carries them on places[1]. That keeps // each leg's settings with the right stop if another one is inserted earlier in the list. const buildLocations = (places: Place<GeocodingProps>[], costModels: LegPanel['costModels']) => places.map((place, stopIndex) => { const legCostModel = stopIndex > 0 ? costModels[stopIndex - 1] : undefined; const pauseDurationSeconds = WAIT_SECONDS_BY_STOP[stopIndex]; const hasLegCostModel = legCostModel && Object.keys(legCostModel).length > 0; if (!hasLegCostModel && !pauseDurationSeconds) return place; return { ...place, properties: { ...place.properties, ...(hasLegCostModel && { legCostModel }), ...(pauseDurationSeconds && { pauseDurationSeconds }), }, }; }); const calculateAndShow = async ( routingModule: RoutingModule, places: Place<GeocodingProps>[], costModels: LegPanel['costModels'], ): Promise<LegStats[]> => { const locations = buildLocations(places, costModels); const routes = await calculateRoute({ locations }); // Show the line and the pins sequentially — never in parallel. await routingModule.showRoutes(routes); await routingModule.showWaypoints(locations); const route = routes.features[0]; const summary = route.properties.summary; renderTotals([ ['Distance', formatDistance(summary.lengthInMeters)], ['Travel time', formatDuration(summary.travelTimeInSeconds) ?? '—'], ]); return route.properties.sections.leg.map((leg) => ({ lengthInMeters: leg.summary.lengthInMeters, travelTimeInSeconds: leg.summary.travelTimeInSeconds, stopTimeInSeconds: leg.summary.stopTimeInSeconds, })); }; (async () => { initTogglePanel(); const places = await Promise.all(STOP_QUERIES.map(geocodeOne)); const map = new TomTomMap({ mapLibre: { container: 'sdk-map', bounds: bboxFromGeoJSON(places), fitBoundsOptions: { padding: 80 }, }, }); const routingModule = await RoutingModule.create(map); // The panel is driven by the legs of the calculated route, so it is only built once the first // route is in — the route says how many legs there are. statusText.textContent = 'Calculating…'; const initialStats = await calculateAndShow(routingModule, places, []); statusText.textContent = ''; const legNames = initialStats.map((_stats, legIndex): [string, string] => [ placeName(places[legIndex]), placeName(places[legIndex + 1]), ]); let panel: LegPanel; let pending = false; const recalculate = async (): Promise<void> => { if (pending) return; pending = true; panel.setBusy(true); statusText.textContent = 'Calculating…'; try { panel.setStats(await calculateAndShow(routingModule, places, panel.costModels)); statusText.textContent = ''; } catch (error) { statusText.textContent = error instanceof Error ? error.message : 'Could not calculate the route.'; } finally { pending = false; panel.setBusy(false); } }; panel = buildLegPanel(legContainer, legNames, () => void recalculate()); panel.setStats(initialStats); })();
Related examples
Add Stops to Route (Playground)
Dynamically add and remove intermediate stops on a route by clicking the map
Routing
Web
Playground
Utilities
Along route search playground
Interactively search for places along a route by query or POI category
Playground
Routing
Places and Search
Web
LDEVR Preferences Playground
Tune the charging preferences of an EV route and watch where the planned charging time goes.
Routing
Playground
Web
Map Chat Agent
Interactive map assistant using AI to control maps and services
AI
Places and Search
Map Style
Base Map
Geometry
Traffic
Routing
Web
Playground
Plugins