import { bboxFromGeoJSON, type CountrySectionProps, type SectionType, TomTomConfig } from '@tomtom-org/maps-sdk/core';
import type { DrawnSectionType } from '@tomtom-org/maps-sdk/map';
import { drawnSectionTypes, RoutingModule, sectionSourceKey, TomTomMap } from '@tomtom-org/maps-sdk/map';
import { calculateRoute, createLatestRequest } from '@tomtom-org/maps-sdk/services';
import { API_KEY } from './config';
import { PICKABLE_ROUTES } from './routes';
import { buildConfig, buildInitialState } from './sectionsConfig';
import { initSectionsPanel, type PanelCounts } from './sectionsPanel';
import './style.css';
TomTomConfig.instance.put({ apiKey: API_KEY });
const FIT_PADDING = { top: 60, bottom: 60, left: 60, right: 340 };
const STRETCH_ZOOM = 17;
const SECTION_TYPES: SectionType[] = [...drawnSectionTypes, 'country'];
(async () => {
const [firstRoute] = PICKABLE_ROUTES;
const routes = await calculateRoute({ locations: firstRoute.locations, sectionTypes: SECTION_TYPES });
const map = new TomTomMap({
mapLibre: {
container: 'sdk-map',
bounds: bboxFromGeoJSON(routes),
fitBoundsOptions: { padding: FIT_PADDING },
hash: true,
},
});
const state = buildInitialState();
const routingModule = await RoutingModule.create(map, buildConfig(state));
await routingModule.showWaypoints(firstRoute.locations);
await routingModule.showRoutes(routes);
const panelCounts = (): PanelCounts => {
const shown = routingModule.getShown();
return {
sections: Object.fromEntries(
drawnSectionTypes.map((type) => [type, shown[sectionSourceKey(type)]?.features.length ?? 0]),
) as Record<DrawnSectionType, number>,
crossings: shown.countryCrossings.features.length,
};
};
const nextStretchIndex = new Map<DrawnSectionType, number>();
const showNextStretch = (type: DrawnSectionType): void => {
const stretches = routingModule.getShown()[sectionSourceKey(type)]?.features ?? [];
if (!stretches.length) return;
const index = ((nextStretchIndex.get(type) ?? -1) + 1) % stretches.length;
nextStretchIndex.set(type, index);
const [longitude, latitude] = stretches[index].geometry.coordinates[0];
map.mapLibreMap.flyTo({ center: [longitude, latitude], zoom: STRETCH_ZOOM, padding: FIT_PADDING });
};
const panel = initSectionsPanel({
map,
state,
counts: panelCounts(),
apply: () => routingModule.applyConfig(buildConfig(state)),
showNextStretch,
});
routingModule.events.countryCrossings.on('click', (crossing) => {
const { fromSection, toSection, label } = crossing.properties;
const extent = (section?: CountrySectionProps) =>
section ? `${section.startPointIndex}–${section.endPointIndex}` : '?';
panel.setCrossingDetail(`${label} · points ${extent(fromSection)} → ${extent(toSection)}`);
});
const latestPick = createLatestRequest();
const showPickedRoute = async (index: number): Promise<void> => {
const { locations } = PICKABLE_ROUTES[index];
const pick = await latestPick.run((signal) =>
calculateRoute({ locations, sectionTypes: SECTION_TYPES, signal }),
);
if (!pick.current) return;
const picked = pick.value;
await routingModule.showWaypoints(locations);
await routingModule.showRoutes(picked);
nextStretchIndex.clear();
const bounds = bboxFromGeoJSON(picked);
if (bounds) map.mapLibreMap.fitBounds(bounds, { padding: FIT_PADDING });
panel.setCounts(panelCounts());
};
const picker = document.getElementById('route-picker') as HTMLSelectElement;
picker.innerHTML = PICKABLE_ROUTES.map(({ label }, index) => `<option value="${index}">${label}</option>`).join('');
picker.addEventListener('change', () => showPickedRoute(Number(picker.value)));
})();