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 } from 'maplibre-gl';
import { API_KEY } from './config';
import { buildAddStopHTML, buildRemoveStopHTML, createStopPopup } from './popup';
import './style.css';
import { initTogglePanel } from './togglePanel';
TomTomConfig.instance.put({ apiKey: API_KEY });
(async () => {
const [origin, destination] = await Promise.all(['Washington', 'New York'].map(geocodeOne));
const map = new TomTomMap({
mapLibre: {
container: 'sdk-map',
bounds: bboxFromGeoJSON([origin, destination]),
fitBoundsOptions: { padding: 80 },
},
});
const [routingModule, baseModule] = await Promise.all([RoutingModule.get(map), BaseMapModule.get(map)]);
let stops: [number, number][] = [];
let currentRoute: Route;
let activePopup: ReturnType<typeof createStopPopup> | null = null;
let isUpdating = false;
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);
}
};
const initialResult = await calculateRoute({ locations: [origin, destination] });
currentRoute = initialResult.features[0];
routingModule.showWaypoints([origin, destination]);
routingModule.showRoutes(initialResult);
routingModule.events.user.waypoints.on('click', (waypoint: Waypoint<WaypointDisplayProps>, lngLat: LngLat) => {
if (waypoint.properties.indexType !== MIDDLE_INDEX || isUpdating) return;
closeActivePopup();
const stopIndex = waypoint.properties.index - 1;
const popup = createStopPopup().setLngLat(lngLat).setHTML(buildRemoveStopHTML()).addTo(map.mapLibreMap);
activePopup = popup;
popup
.getElement()
.querySelector('.stop-popup-btn')
?.addEventListener('click', () => {
closeActivePopup();
removeStop(stopIndex);
});
});
baseModule.events.on('click', (_: unknown, lngLat: LngLat) => {
if (isUpdating) return;
if (activePopup) {
closeActivePopup();
return;
}
const popup = createStopPopup().setLngLat(lngLat).setHTML(buildAddStopHTML()).addTo(map.mapLibreMap);
activePopup = popup;
popup
.getElement()
.querySelector('.stop-popup-btn')
?.addEventListener('click', () => {
closeActivePopup();
addStop(lngLat.toArray());
});
});
initTogglePanel();
})();