Maps SDK for JavaScript
Back to all examples
Add Stops to Route
Dynamically add and remove intermediate stops on a route by clicking the map
import type { Route, Waypoint, WaypointLike } from '@tomtom-org/maps-sdk/core'; import { bboxFromGeoJSON, TomTomConfig, withInsertedWaypoint } from '@tomtom-org/maps-sdk/core'; import type { WaypointDisplayProps } from '@tomtom-org/maps-sdk/map'; import { BaseMapModule, MIDDLE_INDEX, RoutingModule, TomTomMap } from '@tomtom-org/maps-sdk/map'; import { calculateRoute, geocodeOne } from '@tomtom-org/maps-sdk/services'; import { LngLat, Popup } from 'maplibre-gl'; import { API_KEY } from './config'; import './style.css'; import { initTogglePanel } from './togglePanel'; // (Set your own API key when working in your own environment) TomTomConfig.instance.put({ apiKey: API_KEY }); (async () => { const [origin, destination] = await Promise.all([geocodeOne('Washington'), geocodeOne('New York')]); const map = new TomTomMap({ mapLibre: { container: 'sdk-map', bounds: bboxFromGeoJSON([origin, destination]), fitBoundsOptions: { padding: 50 }, }, }); const [routingModule, baseModule] = await Promise.all([RoutingModule.get(map), BaseMapModule.get(map)]); // --- State --- let stops: [number, number][] = []; let currentRoute: Route; let activePopup: Popup | null = null; let isUpdating = false; // --- Helpers --- const getLocations = (): WaypointLike[] => [origin, ...stops, destination]; const closeActivePopup = () => { activePopup?.remove(); activePopup = null; }; const setLoading = (loading: boolean) => { isUpdating = loading; map.mapLibreMap.getCanvas().style.cursor = loading ? 'wait' : ''; }; const recalculate = async () => { const routeResult = await calculateRoute({ locations: getLocations() }); currentRoute = routeResult.features[0]; routingModule.showWaypoints(getLocations()); routingModule.showRoutes(routeResult); }; const addStop = async (pos: [number, number]) => { if (isUpdating) return; setLoading(true); try { const updated = withInsertedWaypoint(currentRoute, getLocations(), pos); stops = updated.slice(1, -1) as [number, number][]; await recalculate(); } finally { setLoading(false); } }; const removeStop = async (index: number) => { if (isUpdating) return; setLoading(true); try { stops = stops.filter((_, i) => i !== index); await recalculate(); } finally { setLoading(false); } }; // --- Initial route --- const initialResult = await calculateRoute({ locations: [origin, destination] }); currentRoute = initialResult.features[0]; routingModule.showWaypoints([origin, destination]); routingModule.showRoutes(initialResult); // --- Waypoint click: show "Remove stop" popup for intermediate stops --- routingModule.events.waypoints.on('click', (waypoint: Waypoint<WaypointDisplayProps>, lngLat: LngLat) => { if (waypoint.properties.indexType !== MIDDLE_INDEX || isUpdating) return; closeActivePopup(); // waypoint.properties.index is its position in [origin, ...stops, destination], // so the corresponding index in the stops array is index - 1. const stopIndex = waypoint.properties.index - 1; const popup = new Popup({ closeButton: false, anchor: 'bottom', className: 'stop-action-popup', }) .setLngLat(lngLat) .setHTML( `<button class="sdk-example-button sdk-example-button-secondary stop-popup-btn">Remove stop</button>`, ) .addTo(map.mapLibreMap); activePopup = popup; popup .getElement() .querySelector('.stop-popup-btn') ?.addEventListener('click', () => { closeActivePopup(); removeStop(stopIndex); }); }); // --- Map click: show "Add Stop" popup (or dismiss existing popup) --- baseModule.events.on('click', (_: unknown, lngLat: LngLat) => { if (isUpdating) return; if (activePopup) { closeActivePopup(); return; } const popup = new Popup({ offset: 15, closeButton: false, anchor: 'bottom', className: 'stop-action-popup', }) .setLngLat(lngLat) .setHTML(`<button class="sdk-example-button stop-popup-btn">Add stop</button>`) .addTo(map.mapLibreMap); activePopup = popup; popup .getElement() .querySelector('.stop-popup-btn') ?.addEventListener('click', () => { closeActivePopup(); addStop(lngLat.toArray()); }); }); initTogglePanel(); })();
Related examples
Route progress playground
Hover and click along the route line to see time and distance progress
Routing
Web
Playground
Utilities
Map route reconstruction playground
Interactive playground for route reconstruction features
Routing
Playground
Web
Map autocomplete fuzzy search playground
Combine autocomplete search with fuzzy search functionality
Places and Search
Playground
Web
User Interaction Events
EV charging stations search
Interactive exploration of charging station availability
Playground
Places and Search
Electric Vehicles
Web