import { bboxFromGeoJSON, type CountrySectionProps, TomTomConfig } from '@tomtom-org/maps-sdk/core';
import type { DrawnSectionType, MapColors, StandardStyleID } from '@tomtom-org/maps-sdk/map';
import {
drawnSectionTypes,
RoutingModule,
StylingFoundationsModule,
sectionSourceKey,
TomTomMap,
} from '@tomtom-org/maps-sdk/map';
import { calculateRoute, createLatestRequest, type InputSectionTypes } 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: InputSectionTypes = [...drawnSectionTypes, 'country'];
const MAP_LOOKS: Record<string, { style: StandardStyleID; colors?: MapColors }> = {
light: { style: 'streetLight' },
dark: { style: 'streetDark' },
amber: { style: 'streetDark', colors: { land: '#12161D', label: '#FFB547', labelHalo: '#12161D' } },
};
(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 styling = await StylingFoundationsModule.get(map);
const lookPicker = document.getElementById('map-look-picker') as HTMLSelectElement;
lookPicker.addEventListener('change', async () => {
const look = lookPicker.value;
const { style, colors } = MAP_LOOKS[look];
if (style !== map.getStyleInput()) await map.setStyle(style);
if (lookPicker.value !== look) return;
styling.applyConfig(colors ? { colors } : {});
});
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)));
})();