@tomtom-org/maps-sdk 0.49.1 → 0.50.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +40 -0
- package/core/dist/core.es.js +1 -1
- package/core/dist/core.es.js.map +1 -1
- package/core/dist/index.d.ts +32 -0
- package/map/dist/index.d.ts +134 -152
- package/map/dist/map.es.js +1 -1
- package/map/dist/map.es.js.map +1 -1
- package/package.json +1 -1
- package/services/dist/services.es.js +1 -1
- package/services/dist/services.es.js.map +1 -1
package/map/dist/map.es.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"map.es.js","names":[],"sources":["../src/traffic/util/trafficFlowMapping.ts","../src/traffic/util/trafficIncidentMapping.ts","../src/shared/errorMessages.ts","../src/shared/imageUtils.ts","../src/shared/resources/index.ts","../src/shared/assertionUtils.ts","../src/shared/featureId.ts","../src/shared/TomTomMapSource.ts","../src/shared/SourceWithLayers.ts","../src/shared/mapUtils.ts","../src/shared/AbstractMapModule.ts","../src/shared/CombinedEvents.ts","../src/shared/AbstractEventProxy.ts","../src/shared/eventUtils.ts","../src/shared/EventsProxy.ts","../src/shared/layers/layerIDs.ts","../src/shared/layers/sourcesIDs.ts","../src/shared/ModuleEvents.ts","../src/shared/UserEvents.ts","../src/pois/layers/poisLayers.ts","../src/pois/util/poiCategoryMapping.ts","../src/shared/layers/commonLayerProps.ts","../src/shared/layers/symbolLayers.ts","../src/shared/layers/utils.ts","../src/places/layers/clusterLayers.ts","../src/places/layers/connectionsLayers.ts","../src/places/utils/customIconScales.ts","../src/places/utils/evAvailabilityHelpers.ts","../src/places/utils/textOffsetCalculator.ts","../src/places/utils/themeAdaptation.ts","../src/places/utils/layerConfiguration.ts","../src/shared/layers/eventState.ts","../src/places/utils/layerSpecBuilders.ts","../src/places/layers/placesLayers.ts","../src/places/resources/index.ts","../src/places/utils/prepareConnectionsForDisplay.ts","../src/places/utils/toPinImageID.ts","../src/places/utils/preparePlacesForDisplay.ts","../src/places/PlacesModule.ts","../src/shared/mapLibreFilterUtils.ts","../src/pois/types/poiCategoryGroups.ts","../src/pois/util/poisMapping.ts","../src/pois/POIsModule.ts","../src/base/layerGroups.ts","../src/base/BaseMapModule.ts","../src/base/types/baseMapModuleConfig.ts","../src/custom/CustomGeoJSONModule.ts","../src/geometry/layers/constants.ts","../src/geometry/layers/geometryLayers.ts","../src/geometry/layers/colorPalettes.ts","../src/geometry/prepareGeometryForDisplay.ts","../src/geometry/GeometriesModule.ts","../src/geometry/prepareReachableRangesForDisplay.ts","../src/geometry/geometryConfigs.ts","../src/geometry/types/geometryTheme.ts","../src/hillshade/HillshadeModule.ts","../src/init/types/mapInit.ts","../src/utils/colorUtils.ts","../src/routing/layers/shared.ts","../src/routing/layers/chargingStopLayers.ts","../src/routing/layers/guidanceLayers.ts","../src/routing/layers/routeFerrySectionLayers.ts","../src/routing/layers/routeMainLineLayers.ts","../src/routing/layers/routeTollRoadLayers.ts","../src/traffic/util/trafficIncidentStyle.ts","../src/routing/layers/routeTrafficSectionLayers.ts","../src/routing/layers/routeTunnelSectionLayers.ts","../src/routing/layers/routeVehicleRestrictedLayers.ts","../src/routing/layers/summaryBubbleLayers.ts","../src/routing/types/waypointDisplayProps.ts","../src/routing/layers/waypointLayers.ts","../src/routing/layers/routingLayers.ts","../src/routing/resources/index.ts","../src/routing/util/config.ts","../src/routing/util/displayChargingStops.ts","../src/routing/util/displayTrafficSectionProps.ts","../src/routing/util/displayWaypoints.ts","../src/routing/util/guidance.ts","../src/routing/util/routeSections.ts","../src/routing/util/routeSelection.ts","../src/routing/util/routes.ts","../src/routing/RoutingModule.ts","../src/init/styleInputBuilder.ts","../src/init/buildMapOptions.ts","../src/shared/localization.ts","../src/TomTomMap.ts","../src/traffic/types/trafficAreaAnalyticsConfig.ts","../src/traffic/layers/areaAnalyticsLayers.ts","../src/traffic/util/areaAnalyticsGeoJSONTiles.ts","../src/traffic/TrafficAreaAnalyticsModule.ts","../src/traffic/filters/trafficFilters.ts","../src/traffic/TrafficFlowModule.ts","../src/traffic/util/incidentDirectionChevron.ts","../src/traffic/util/readIncidentPalette.ts","../src/traffic/layers/incidentDetailsLayers.ts","../src/traffic/TrafficIncidentOverlayModule.ts","../src/traffic/TrafficIncidentsModule.ts","../src/traffic/types/trafficCommonConfig.ts","../src/utils/paddedBounds.ts"],"sourcesContent":["import type { LineString } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { TrafficFlowModuleFeature } from '../types/trafficFlowFeature';\n\n/**\n * @ignore\n */\nexport const FLOW_TAGS = [\n 'road_category',\n 'road_subcategory',\n 'relative_speed',\n 'left_hand_traffic',\n 'road_closure',\n 'absolute_speed',\n 'part_of_two_way_road',\n 'openlr',\n 'display_class',\n] as const;\n\n/**\n * @ignore\n */\nexport const trafficFlowMapping = (feature: MapGeoJSONFeature): TrafficFlowModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry as LineString,\n properties: {\n roadCategory: properties?.road_category,\n leftHandTraffic: Boolean(properties?.left_hand_traffic),\n roadClosure: Boolean(properties?.road_closure),\n relativeSpeed: properties?.relative_speed,\n ...(properties?.road_subcategory && { roadSubcategory: properties.road_subcategory }),\n ...(properties?.absolute_speed && { absoluteSpeed: properties.absolute_speed }),\n ...(properties?.part_of_two_way_road && { partOfTwoWayRoad: Boolean(properties.part_of_two_way_road) }),\n ...(properties?.openlr && { openlr: properties.openlr }),\n ...(properties?.display_class && { displayClass: properties.display_class }),\n },\n };\n};\n","import {\n generateId,\n iconToTrafficIncidentCategory,\n indexedMagnitudes,\n type TrafficIncidentCategory,\n} from '@tomtom-org/maps-sdk/core';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { TrafficIncidentsModuleFeature } from '../types/trafficIncidentsFeature';\n\n/**\n * @ignore\n */\nexport const INCIDENT_TAGS = [\n 'icon_category',\n 'left_hand_traffic',\n 'magnitude_of_delay',\n 'road_category',\n 'road_subcategory',\n 'id',\n 'description',\n 'delay',\n 'part_of_two_way_road',\n 'start_time',\n 'end_time',\n 'probability_of_occurrence',\n 'number_of_reports',\n 'last_report_time',\n 'average_speed_kmph',\n 'openlr',\n 'time_validity',\n 'display_class',\n] as const;\n\n/**\n * @ignore\n */\nexport const incidentToIconCategoryMapping: Record<TrafficIncidentCategory, number> = {\n other: 0,\n accident: 1,\n fog: 2,\n danger: 3,\n rain: 4,\n frost: 5,\n jam: 6,\n 'lane-closed': 7,\n 'road-closed': 8,\n roadworks: 9,\n wind: 10,\n flooding: 11,\n 'animals-on-road': 12,\n 'narrow-lanes': 13,\n 'broken-down-vehicle': 14,\n} as const;\n\n/**\n * @ignore\n */\nexport const trafficIncidentMapping = (feature: MapGeoJSONFeature): TrafficIncidentsModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry,\n properties: {\n id: properties.id ?? generateId(), // generateID is a failsafe but properties.id should always be present in the vector tile data\n description: properties?.description_0 ?? '',\n category: iconToTrafficIncidentCategory(properties?.icon_category_0),\n magnitudeOfDelay: indexedMagnitudes[properties?.magnitude_of_delay],\n roadCategory: properties?.road_category,\n roadSubcategory: properties?.road_subcategory,\n leftHandTraffic: Boolean(properties?.left_hand_traffic),\n ...(properties?.delay && { delayInSeconds: properties.delay }),\n ...(properties?.part_of_two_way_road && { partOfTwoWayRoad: Boolean(properties.part_of_two_way_road) }),\n ...(properties?.start_time && { startTime: new Date(properties.start_time) }),\n ...(properties?.end_time && { endTime: new Date(properties.end_time) }),\n ...(properties?.probability_of_occurrence && {\n probabilityOfOccurrence: properties.probability_of_occurrence,\n }),\n ...(properties?.number_of_reports && { numberOfReports: properties.number_of_reports }),\n ...(properties?.last_report_time && { lastReportTime: new Date(properties.last_report_time) }),\n ...(properties?.average_speed_kmph && { averageSpeedKmph: properties.average_speed_kmph }),\n ...(properties?.openlr && { openlr: properties.openlr }),\n ...(properties?.time_validity && { timeValidity: properties.time_validity }),\n ...(properties?.display_class && { displayClass: properties.display_class }),\n ...(properties?.point_type && { pointType: properties.point_type }),\n },\n };\n};\n","import type { StyleModule } from '../init';\n\n/**\n * @ignore\n */\nexport const notInTheStyle = (actionText: string): Error =>\n new Error(`Trying to ${actionText} while it is not in the map style. Did you exclude it when loading the map?`);\n\n/**\n * @ignore\n */\nexport const cannotAddStyleModuleToCustomStyle = (styleModule: StyleModule): Error =>\n new Error(`Trying to add style module ${styleModule} to the custom style!`);\n","/**\n * @ignore\n */\nexport const isDOMImageSupported = (): boolean =>\n typeof document != 'undefined' && typeof DOMParser != 'undefined' && typeof btoa != 'undefined';\n\n/**\n * @ignore\n */\nexport const svgToImg = (svgDomElement: SVGElement): HTMLImageElement => {\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const img = document.createElement('img');\n img.src = `data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgDomElement))}`;\n return img;\n};\n","import { SVGIconStyleOptions } from '../types';\nimport pinSvgRaw from './pin.svg?raw';\n\n/**\n * Helper function to parse SVG string (typically imported from ...svg?raw) to SVGElement\n * @ignore\n */\nexport const parseSvg = (svgString: string): SVGElement =>\n new DOMParser().parseFromString(svgString, 'image/svg+xml').documentElement as unknown as SVGElement;\n\n// Applies `fillColor` / `outlineColor` / `outlineOpacity` overrides to an SVG\n// that follows the `#background` / `#outline` id convention.\nconst applySvgStyleOptions = (element: SVGElement, options: SVGIconStyleOptions | undefined): SVGElement => {\n if (options?.fillColor) {\n element.querySelector('#background')?.setAttribute('fill', options.fillColor);\n }\n if (options?.outlineColor) {\n element.querySelector('#outline')?.setAttribute('fill', options.outlineColor);\n }\n if (options?.outlineOpacity !== undefined) {\n element.querySelector('#outline')?.setAttribute('fill-opacity', options.outlineOpacity.toString());\n }\n return element;\n};\n\n/**\n * @ignore\n */\nexport const pinSvg = (options: SVGIconStyleOptions | undefined): SVGElement =>\n applySvgStyleOptions(parseSvg(pinSvgRaw), options);\n","import { isNil } from 'lodash-es';\n\n/**\n * Throws an error if the given value is not defined.\n * @ignore\n */\nexport function assertDefined<T>(value: T | null | undefined): asserts value is T {\n if (isNil(value)) {\n throw new Error(`${value} must not be null/undefined. Something went wrong with its initialization.`);\n }\n}\n\n/**\n * Returns the given value as its defined type, or throws an error if it isn't defined.\n * @ignore\n * @throws\n */\nexport const asDefined = <T>(value: T | null | undefined): T => {\n assertDefined(value);\n return value;\n};\n","/**\n * Class-independent feature-id recovery and lookup, shared by the event subsystem and the\n * source/layer caches. Operate on the minimal {@link RenderedRef} shape, so they accept\n * MapLibre `MapGeoJSONFeature`s, plain GeoJSON `Feature`s, and cached originals alike.\n */\n\n/** Minimal shape needed to look a feature up by id. @ignore */\nexport type RenderedRef = { id?: string | number; properties?: { id?: unknown } | null };\n\n/** A feature together with its index in the array it was found in. @ignore */\nexport type IndexedFeature<F> = { feature: F; index: number };\n\n/**\n * Recover the SDK-assigned id of a feature, preferring `properties.id` over the top-level `id`.\n *\n * The SDK always stamps the real id into `properties.id` and uses `promoteId: 'id'`, so MapLibre\n * mirrors it onto the top-level `id` — the two are normally identical. The exception is **clustered**\n * GeoJSON sources: clustering disables `promoteId`, and MapLibre's supercluster then assigns its own\n * **synthetic** numeric top-level `id` (0, 1, 2, …) to rendered features. Preferring the top-level\n * `id` there would return that synthetic value and miss the cache, whereas `properties.id` is still\n * the real id. So read `properties.id` first, falling back to the top-level `id` (e.g. vector-tile\n * features that only carry a top-level id). Returns `undefined` when neither is set.\n * @ignore\n */\nexport const renderedRefId = (rendered: RenderedRef): string | number | undefined => {\n const propId = (rendered.properties as { id?: unknown } | null)?.id;\n // `properties.id` is `unknown`: trust it as the ref id only when it's actually a string/number,\n // else fall back to the (already-typed) top-level id. Keeps the declared return type honest.\n return typeof propId === 'string' || typeof propId === 'number' ? propId : rendered.id;\n};\n\n/**\n * Find the first feature whose {@link renderedRefId} equals `id`, with its index, or\n * `undefined` (also for a nullish `id`).\n * @ignore\n */\nexport const findFeatureByRefId = <F extends RenderedRef>(\n features: readonly F[],\n id: string | number | undefined,\n): IndexedFeature<F> | undefined => {\n if (id === undefined || id === null) return undefined;\n for (let index = 0; index < features.length; index++) {\n if (renderedRefId(features[index]) === id) return { feature: features[index], index };\n }\n return undefined;\n};\n","import type { Map, Source, SourceSpecification } from 'maplibre-gl';\n\n/**\n * Contains a source relevant for TomTom Maps SDK JS.\n * * The source might already be initialized from the map style, or it might be initialized here.\n * @ignore\n */\nexport class TomTomMapSource<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> {\n constructor(\n readonly id: string,\n readonly spec?: SOURCE_SPEC,\n public runtimeSource?: RUNTIME_SOURCE,\n ) {}\n\n ensureAddedToMap(map: Map) {\n if (!this.runtimeSource) {\n if (!map.getSource(this.id) && this.spec) {\n map.addSource(this.id, this.spec);\n }\n this.runtimeSource = map.getSource(this.id) as RUNTIME_SOURCE;\n }\n }\n}\n","import type { FeatureCollection } from 'geojson';\nimport type {\n GeoJSONSource,\n GeoJSONSourceSpecification,\n LayerSpecification,\n Map,\n Source,\n SourceSpecification,\n} from 'maplibre-gl';\nimport { asDefined } from './assertionUtils';\nimport { findFeatureByRefId, type IndexedFeature } from './featureId';\nimport { TomTomMapSource } from './TomTomMapSource';\nimport type {\n ByIdOrIndex,\n CleanEventStateOptions,\n CleanEventStatesOptions,\n LayerSpecFilter,\n LayerSpecWithSource,\n PutEventStateOptions,\n SourceWithLayerIDs,\n SourceWithLayers,\n ToBeAddedLayerSpec,\n ToBeAddedLayerSpecWithoutSource,\n} from './types';\n\n/**\n * Contains a source and the layers to render its data.\n * @ignore\n */\nexport abstract class AbstractSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n LAYER_SPEC extends LayerSpecification = LayerSpecification,\n> {\n readonly _layerSpecs: LAYER_SPEC[];\n private _sourceAndLayerIDs!: SourceWithLayerIDs;\n\n constructor(\n readonly map: Map,\n readonly source: TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>,\n layerSpecs: LAYER_SPEC[],\n ) {\n this._layerSpecs = layerSpecs;\n this._updateSourceAndLayerIDs();\n }\n\n isAnyLayerVisible(filter?: LayerSpecFilter): boolean {\n return this.getLayerSpecs(filter).some((layer) => this.isLayerVisible(layer));\n }\n\n areAllLayersVisible(filter?: LayerSpecFilter): boolean {\n return this.getLayerSpecs(filter).every((layer) => this.isLayerVisible(layer));\n }\n\n private getLayerSpecs(filter?: LayerSpecFilter) {\n return filter ? this._layerSpecs.filter(filter) : this._layerSpecs;\n }\n\n _updateSourceAndLayerIDs(): void {\n this._sourceAndLayerIDs = { sourceID: this.source.id, layerIDs: this._layerSpecs.map((layer) => layer.id) };\n }\n\n private isLayerVisible(layer: LayerSpecification): boolean {\n // If the layer is no longer attached to the map (e.g. an out-of-band style swap removed\n // it before this snapshot got refreshed), `getLayoutProperty` returns `undefined` and\n // also fires a MapLibre error. Treat a missing layer as hidden, mirroring\n // `setLayersVisible` which skips missing layers silently.\n if (!this.map.getLayer(layer.id)) return false;\n return this.map.getLayoutProperty(layer.id, 'visibility') !== 'none';\n }\n\n setLayersVisible(visible: boolean, filter?: LayerSpecFilter): void {\n for (const layerSpec of this.getLayerSpecs(filter)) {\n // The layer may have been removed by an out-of-band style swap\n // (e.g. `setStyle()` between calls) — `setLayoutProperty` on a\n // missing id throws inside MapLibre. Skip silently; the next\n // `show()` will re-add the layer via `ensureAddedToMap`.\n if (!this.map.getLayer(layerSpec.id)) continue;\n this.map.setLayoutProperty(layerSpec.id, 'visibility', visible ? 'visible' : 'none', { validate: false });\n }\n }\n\n get sourceAndLayerIDs(): SourceWithLayerIDs {\n return this._sourceAndLayerIDs;\n }\n\n equalSourceAndLayerIDs(other: SourceWithLayers): boolean {\n return (\n this.sourceAndLayerIDs.sourceID === other.sourceAndLayerIDs.sourceID &&\n this.sourceAndLayerIDs.layerIDs.length === other.sourceAndLayerIDs.layerIDs.length &&\n this.sourceAndLayerIDs.layerIDs.every((id, index) => id === other.sourceAndLayerIDs.layerIDs[index])\n );\n }\n}\n\n/**\n * @ignore\n * @param loadedMap\n * @param sourceIDs\n */\nexport const filterLayersBySources = (loadedMap: Map, sourceIDs: string[]): LayerSpecWithSource[] =>\n loadedMap\n .getStyle()\n .layers.filter((layer) => sourceIDs.includes((layer as LayerSpecWithSource)?.source)) as LayerSpecWithSource[];\n\n/**\n * Source with layers which are coming from the downloaded TT map style.\n * * Examples are parts of the base map, traffic, pois, and hillshade.\n * @ignore\n */\nexport class StyleSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> extends AbstractSourceWithLayers<SourceSpecification, RUNTIME_SOURCE> {\n constructor(map: Map, runtimeSource: RUNTIME_SOURCE, filter?: LayerSpecFilter) {\n let layers = filterLayersBySources(map, [runtimeSource.id]);\n if (filter) {\n layers = layers.filter(filter);\n }\n super(\n map,\n new TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>(\n runtimeSource.id,\n map.getStyle().sources[runtimeSource.id] as SOURCE_SPEC,\n runtimeSource,\n ),\n layers,\n );\n }\n}\n\n/**\n * Source with layers which originally is not in the map style, but it's to be added after the map is initialized.\n * @ignore\n */\nexport class AddedSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> extends AbstractSourceWithLayers<SOURCE_SPEC, RUNTIME_SOURCE, ToBeAddedLayerSpec> {\n constructor(map: Map, sourceId: string, sourceSpec: SOURCE_SPEC, layerSpecs: ToBeAddedLayerSpecWithoutSource[]) {\n super(\n map,\n new TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>(sourceId, sourceSpec),\n // We ensure the source ID is assigned to the layers:\n layerSpecs.map((layerSpec) => ({ ...layerSpec, source: sourceId }) as ToBeAddedLayerSpec),\n );\n }\n\n private ensureLayersAddedToMap(): void {\n for (const layerSpec of this._layerSpecs) {\n if (!this.map.getLayer(layerSpec.id)) {\n this.map.addLayer(layerSpec, layerSpec.beforeID);\n }\n }\n }\n\n ensureAddedToMapWithVisibility(visible: boolean, addLayersToMap: boolean): void {\n this.source.ensureAddedToMap(this.map);\n if (addLayersToMap) {\n this.ensureLayersAddedToMap();\n this.setLayersVisible(visible);\n }\n }\n}\n\nconst emptyFeatureCollection: FeatureCollection = { type: 'FeatureCollection', features: [] };\n\n/**\n * Options for the {@link GeoJSONSourceWithLayers.show} method.\n * @ignore\n */\nexport type ShowOptions = {\n /**\n * When `true` (default), layer visibility is automatically set based on whether\n * the feature collection is non-empty. Pass `false` from state-update calls\n * (e.g. `putEventState`, `cleanEventState`) to avoid unintentionally hiding or\n * showing layers when only feature state changes.\n *\n * @defaultValue true\n */\n automaticVisibility?: boolean;\n};\n\n/**\n * Subset of {@link GeoJSONSourceSpecification} fields that {@link GeoJSONSourceWithLayers}\n * can be configured with at construction time. Forwarded verbatim to MapLibre.\n * @ignore\n */\nexport type GeoJSONSourceClusterOptions = Pick<\n GeoJSONSourceSpecification,\n 'cluster' | 'clusterRadius' | 'clusterMaxZoom' | 'clusterMinPoints' | 'clusterProperties'\n>;\n\n/**\n * @ignore\n */\nexport class GeoJSONSourceWithLayers<T extends FeatureCollection = FeatureCollection> extends AddedSourceWithLayers<\n GeoJSONSourceSpecification,\n GeoJSONSource\n> {\n shownFeatures: T = emptyFeatureCollection as T;\n\n constructor(\n map: Map,\n sourceId: string,\n layerSpecs: ToBeAddedLayerSpecWithoutSource[],\n addLayersToMap = true,\n clusterOptions?: GeoJSONSourceClusterOptions,\n ) {\n // MapLibre does not reuse the given feature ID. Either we generate it on the fly or use the one from properties via promotedId value.\n // We must generate \"id\" property based on the feature id on the fly on \"prepareForDisplay\" functions.\n // `promoteId` is incompatible with `cluster: true` (MapLibre validates cluster sources without it),\n // so we omit it whenever clustering is enabled.\n const sourceSpec: GeoJSONSourceSpecification = clusterOptions?.cluster\n ? { type: 'geojson', data: emptyFeatureCollection, ...clusterOptions }\n : { type: 'geojson', data: emptyFeatureCollection, promoteId: 'id', ...(clusterOptions ?? {}) };\n super(map, sourceId, sourceSpec, layerSpecs);\n this.ensureAddedToMapWithVisibility(false, addLayersToMap);\n }\n\n show(featureCollection: T, { automaticVisibility = true }: ShowOptions = {}): void {\n this.shownFeatures = featureCollection;\n asDefined(this.source.runtimeSource).setData(featureCollection);\n if (automaticVisibility) {\n this.setLayersVisible(!!featureCollection.features.length);\n }\n }\n\n /**\n * Canonical id-based lookup into `shownFeatures.features` via {@link findFeatureByRefId}\n * (first match wins on duplicate ids). Returns `undefined` for a nullish or absent id.\n */\n findById(id: string | number | undefined): IndexedFeature<T['features'][number]> | undefined {\n return findFeatureByRefId(this.shownFeatures.features, id);\n }\n\n clear(): void {\n this.show(emptyFeatureCollection as T);\n }\n\n private findFeature(options: ByIdOrIndex) {\n if ('index' in options) return this.shownFeatures.features[options.index];\n if ('id' in options) return this.findById(options.id)?.feature;\n return undefined;\n }\n\n putEventState(options: PutEventStateOptions) {\n const mode = options.mode ?? 'put';\n if (mode === 'put') {\n for (const feature of this.shownFeatures.features) {\n if (feature.properties?.eventState === options.state) {\n delete feature.properties.eventState;\n }\n }\n }\n\n const feature = this.findFeature(options);\n if (feature) {\n feature.properties = { ...feature.properties, eventState: options.state };\n }\n\n if (options.show !== false) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n\n cleanEventState(options: CleanEventStateOptions) {\n const feature = this.findFeature(options);\n\n if (feature?.properties?.eventState) {\n delete feature?.properties?.eventState;\n if (options?.show !== false) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n }\n\n cleanEventStates(options?: CleanEventStatesOptions) {\n let changed = false;\n for (const feature of this.shownFeatures.features) {\n if (!options?.states || options.states.includes(feature.properties?.eventState)) {\n delete feature.properties?.eventState;\n changed = true;\n }\n }\n\n if (options?.show !== false && changed) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n}\n","import type { GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport { generateTomTomHeaders } from '@tomtom-org/maps-sdk/core';\nimport type {\n FilterSpecification,\n Map,\n MapGeoJSONFeature,\n RequestParameters,\n ResourceType,\n StyleImageMetadata,\n} from 'maplibre-gl';\nimport { InternalTomTomMapParams, StandardStyle, StandardStyleID, StyleInput, StyleModule } from '../init';\nimport type { TomTomMap } from '../TomTomMap';\nimport { FLOW_TAGS } from '../traffic/util/trafficFlowMapping';\nimport { INCIDENT_TAGS } from '../traffic/util/trafficIncidentMapping';\nimport { cannotAddStyleModuleToCustomStyle } from './errorMessages';\nimport { svgToImg } from './imageUtils';\nimport { parseSvg } from './resources';\nimport { AbstractSourceWithLayers, filterLayersBySources } from './SourceWithLayers';\nimport type { LightDark, ToBeAddedLayerSpec, ToBeAddedLayerSpecWithoutSource } from './types';\n\n/**\n * Wait until the map is ready.\n * @param tomtomMap The TomTomMap instance.\n * @returns {Promise<boolean>} Returns a Promise<boolean>\n */\nexport const waitUntilMapIsReady = async (tomtomMap: TomTomMap): Promise<void> => {\n if (!tomtomMap.mapReady) {\n await tomtomMap.mapLibreMap.once('styledata');\n // Recursively waiting for map to be ready (in case of style changes quickly in succession):\n await waitUntilMapIsReady(tomtomMap);\n }\n};\n\n/**\n * Wait until the source is ready.\n * @param tomtomMap The TomTomMap instance.\n * @param sourceId we want to check for.\n * @returns {Promise<boolean>} Returns a Promise<boolean>\n */\nexport const waitUntilSourceIsLoaded = async (tomtomMap: TomTomMap, sourceId: string): Promise<void> => {\n if (!tomtomMap.mapLibreMap.getSource(sourceId) || !tomtomMap.mapLibreMap.isSourceLoaded(sourceId)) {\n await tomtomMap.mapLibreMap.once('sourcedata');\n }\n};\n\nconst TOMTOM_DEFAULT_URL = 'https://api.tomtom.com';\n\nconst isTomTomHostname = (hostname: string): boolean =>\n hostname === 'api.tomtom.com' || hostname.endsWith('.api.tomtom.com');\n\n/**\n * Vector tiles are served from prefixed-subdomain CDN hosts\n * (a./b./c./d.api.tomtom.com) for browser-parallelism. Rewrite all of those\n * plus the bare `api.tomtom.com` host to the proxy. In demo-BFF mode\n * (`isDemoBffMode`) drop any inbound `key` param too: TomTom's edge bakes\n * the request's key into tile URLs returned in the style JSON, and the\n * proxy injects its own server-side — leaving the client-visible key would\n * just leak it to the browser console.\n *\n * Returns the original URL unchanged when it isn't a TomTom host or when\n * we're not in proxy mode.\n */\nconst rewriteForProxy = (url: string, baseURL: string, isProxyMode: boolean, isDemoBffMode: boolean): string => {\n if (!isProxyMode) return url;\n try {\n const parsed = new URL(url);\n if (!isTomTomHostname(parsed.hostname)) return url;\n if (isDemoBffMode) {\n parsed.searchParams.delete('key');\n }\n return baseURL + parsed.pathname + parsed.search + parsed.hash;\n } catch {\n return url;\n }\n};\n\n/**\n * For traffic-incident / traffic-flow tile endpoints, override the\n * server-side `tags` filter to the SDK's tracked-incident set so MapLibre\n * gets the data the SDK actually knows how to render.\n */\nconst injectTrafficTags = (url: URL): void => {\n if (url.pathname.includes('incidents')) {\n url.searchParams.set('tags', INCIDENT_TAGS.join(','));\n } else if (url.pathname.includes('flow')) {\n url.searchParams.set('tags', FLOW_TAGS.join(','));\n }\n};\n\ndeclare global {\n /**\n * Installed by the demo-BFF sandpack bootstrap. Resolves once the proxy\n * session cookie is valid and not about to expire, re-minting it first\n * when needed. See ensureFreshSession in proxyBootstrap.ts. Declared as a\n * global var (not a Window member) so it can be read off `globalThis`,\n * which also works on the main thread where window === globalThis.\n */\n var __DEMO_BFF_ENSURE_SESSION__: (() => Promise<void>) | undefined;\n}\n\n/**\n * Gate a request on a fresh demo-BFF session when the bootstrap installed\n * its hook. MapLibre awaits transformRequest results on the MAIN thread\n * before handing the request to its tile worker — which makes this the one\n * place that can hold back worker-fetched tiles until the session cookie is\n * renewed (the workers' own `fetch` is unreachable from here; they rely on\n * the browser attaching whatever cookie exists when the request starts).\n * Without the hook (direct mode, non-sandpack consumers) this stays fully\n * synchronous. A failed renewal lets the request proceed (it will 401, same\n * as without the gate) rather than wedging the map.\n * @ignore\n */\nconst gateOnDemoBffSession = (result: RequestParameters): RequestParameters | Promise<RequestParameters> => {\n const ensureSession = globalThis.__DEMO_BFF_ENSURE_SESSION__;\n if (typeof ensureSession !== 'function') return result;\n return ensureSession().then(\n () => result,\n () => result,\n );\n};\n\n/**\n * Inject TomTom custom headers (and, when a proxy `commonBaseURL` is\n * configured, rewrite tile URLs + attach credentials) on requests issued\n * by MapLibre.\n *\n * In \"proxy mode\" (commonBaseURL points away from api.tomtom.com), tile\n * URLs baked into the style JSON still arrive here as `api.tomtom.com/...`\n * because MapLibre fetches them directly. We rewrite them to flow through\n * the configured base URL, and — in demo-BFF mode — set\n * `credentials: 'include'` so the session cookie travels with each tile.\n *\n * @ignore\n * @param params Global SDK Map configuration\n */\nexport const transformRequest = (params: Partial<GlobalConfig>) => {\n const baseURL = params.commonBaseURL ?? TOMTOM_DEFAULT_URL;\n // commonBaseURL points at something other than TomTom — could be a\n // demo-BFF or a customer's own customServiceBaseURL. We always rewrite\n // tile hostnames to flow through it.\n const isProxyMode = baseURL !== TOMTOM_DEFAULT_URL;\n // Demo-BFF-style mode: no apiKey signals the proxy will inject the real\n // key server-side from a cookie-gated session. Customers using\n // customServiceBaseURL keep apiKey set so their backend can use it.\n // We only strip key= and attach `credentials: 'include'` in this mode;\n // doing it for customServiceBaseURL would break consumers whose backend\n // serves CORS as `Access-Control-Allow-Origin: *`.\n //\n // Falsy check (not `=== ''`) on purpose: an example may overwrite the\n // proxy bootstrap's `apiKey: ''` with `apiKey: undefined` via\n // `put({ apiKey: process.env.API_KEY_EXAMPLES })` when that env is unset\n // in a proxy build. Both empty and undefined mean \"no key\"; this matches\n // the URL builders' `if (apiKey)` key-append decision.\n const isDemoBffMode = isProxyMode && !params.apiKey;\n\n return (url: string, resourceType?: ResourceType): RequestParameters | Promise<RequestParameters> => {\n const rewrittenUrl = rewriteForProxy(url, baseURL, isProxyMode, isDemoBffMode);\n const isProxyUrl = isProxyMode && rewrittenUrl.startsWith(baseURL);\n const useCredentials = isDemoBffMode && isProxyUrl;\n\n // Hostname-based TomTom detection — a substring check like\n // `url.includes('tomtom.com')` would also match lookalikes\n // (`tomtom.com.evil.example`) or a `tomtom.com` in the query string,\n // and then run TomTom header/tag injection on unintended hosts. Match\n // the registrable domain (tomtom.com or any *.tomtom.com subdomain) by\n // hostname — broader than the api-only `isTomTomHostname` used for the\n // rewrite, since headers apply to all genuine TomTom hosts. The\n // try/catch guards against invalid/relative URLs.\n let isTomTomUrl = false;\n try {\n const host = new URL(url).hostname;\n isTomTomUrl = host === 'tomtom.com' || host.endsWith('.tomtom.com');\n } catch {\n isTomTomUrl = false;\n }\n\n if (!isTomTomUrl && !isProxyUrl) {\n return { url: rewrittenUrl };\n }\n\n if (resourceType === 'Image') {\n return useCredentials\n ? gateOnDemoBffSession({ url: rewrittenUrl, credentials: 'include' })\n : { url: rewrittenUrl };\n }\n\n const parsedUrl = new URL(rewrittenUrl);\n injectTrafficTags(parsedUrl);\n\n const result: RequestParameters = {\n url: parsedUrl.toString(),\n headers: { ...generateTomTomHeaders(params) },\n };\n if (useCredentials) {\n result.credentials = 'include';\n return gateOnDemoBffSession(result);\n }\n return result;\n };\n};\n\n/**\n * Compares two MapLibre features by ID.\n * @ignore\n */\nexport const areBothDefinedAndEqual = (\n featureA: MapGeoJSONFeature | undefined,\n featureB: MapGeoJSONFeature | undefined,\n): boolean => !!featureA && !!featureB && featureA.id === featureB.id;\n\ntype LayerProps = {\n id: string;\n layout?: any;\n paint?: any;\n minzoom?: number;\n maxzoom?: number;\n filter?: FilterSpecification;\n};\n\n/**\n * Applies the layout and paint properties from newLayoutPaint\n * while unsetting (setting as undefined) the ones from previousSpec which no longer exist in newLayoutPaint.\n * * This allows for a quick change of a layer visuals without removing-re-adding the layer.\n * @ignore\n * @param newLayerProps The new layer from which to apply layout/pain props.\n * @param prevLayerProps The previous layer to ensure layout/paint props are removed.\n * @param map\n */\nexport const changeLayerProps = (newLayerProps: LayerProps, prevLayerProps: LayerProps, map: Map) => {\n const layerId = newLayerProps.id;\n if (newLayerProps.maxzoom !== prevLayerProps.maxzoom || newLayerProps.minzoom !== prevLayerProps.minzoom) {\n map.setLayerZoomRange(\n layerId,\n newLayerProps.minzoom ?? map.getMinZoom(),\n newLayerProps.maxzoom ?? map.getMaxZoom(),\n );\n }\n map.setFilter(layerId, newLayerProps.filter, { validate: false });\n for (const property of Object.keys(prevLayerProps.layout ?? [])) {\n if (!newLayerProps.layout?.[property]) {\n map.setLayoutProperty(layerId, property, undefined, { validate: false });\n }\n }\n for (const property of Object.keys(prevLayerProps.paint ?? [])) {\n if (!newLayerProps.paint?.[property]) {\n map.setPaintProperty(layerId, property, undefined, { validate: false });\n }\n }\n for (const [property, value] of Object.entries(newLayerProps.paint ?? [])) {\n map.setPaintProperty(layerId, property, value, { validate: false });\n }\n\n for (const [property, value] of Object.entries(newLayerProps.layout ?? [])) {\n map.setLayoutProperty(layerId, property, value, { validate: false });\n }\n};\n\n/**\n * Applies the layer properties from each layer of newLayoutPaints\n * while unsetting (setting as undefined) the ones from the corresponding layer from prevLayoutPaints\n * which no longer exist in the new one.\n * * The two layer inputs are expected to be parallel arrays.\n * * This allows for quick changes of layer visuals without removing-re-adding the layers.\n * @ignore\n */\nexport const changeLayersProps = (newLayerProps: LayerProps[], prevLayerProps: LayerProps[], map: Map) => {\n newLayerProps.forEach((layoutPaint, index) => changeLayerProps(layoutPaint, prevLayerProps[index], map));\n};\n\n/**\n * Handles new layer specs for the provided source. It will remove layers no longer present,\n * update existing layers and add new one if needed to the source.\n * Adding layers to the map needs to be done correctly, so after calling this method, you should call addLayersInCorrectOrder.\n * If ID of layer to be added already is present on map, MapLibre will through exception.\n * @param newLayersSpecs new layer specification for provided source.\n * @param oldLayersSpecs current layer specification for provided source.\n * @param sourceWithLayers provided source that contains layers.\n * @param map provided map libre map object.\n * @ignore\n */\nexport const updateLayersAndSource = (\n newLayersSpecs: ToBeAddedLayerSpecWithoutSource[],\n oldLayersSpecs: ToBeAddedLayerSpecWithoutSource[],\n sourceWithLayers: AbstractSourceWithLayers,\n map: Map,\n): void => {\n // map layers by id in object for easier access, reduces number of loops\n const newLayersMap: Record<string, ToBeAddedLayerSpecWithoutSource> = newLayersSpecs.reduce(\n (acc, cur) => ({ ...acc, [cur.id]: cur }),\n {},\n );\n const oldLayersMap: Record<string, ToBeAddedLayerSpecWithoutSource> = oldLayersSpecs.reduce(\n (acc, cur) => ({ ...acc, [cur.id]: cur }),\n {},\n );\n\n // we need to store layers in four arrays, layers to add ID, layers to remove ID and layers to update\n const layersToAdd: string[] = [];\n const layersToRemove: string[] = [];\n const newLayersToUpdate: ToBeAddedLayerSpecWithoutSource[] = [];\n const oldLayersToUpdate: ToBeAddedLayerSpecWithoutSource[] = [];\n Object.keys(newLayersMap).forEach((key) => {\n if (oldLayersMap[key]) {\n newLayersToUpdate.push(newLayersMap[key]);\n oldLayersToUpdate.push(oldLayersMap[key]);\n } else {\n layersToAdd.push(key);\n }\n });\n Object.keys(oldLayersMap).forEach((key) => {\n if (!newLayersMap[key]) {\n layersToRemove.push(key);\n }\n });\n\n // remove the old layers no longer present in new layer specification\n const layerSpecs: ToBeAddedLayerSpec[] = sourceWithLayers._layerSpecs;\n layersToRemove.forEach((layerId) => {\n map.removeLayer(layerId);\n for (let i = 0; i < layerSpecs.length; i++) {\n if (layerSpecs[i].id === layerId) {\n layerSpecs.splice(i, 1);\n break;\n }\n }\n });\n // add new layers\n layersToAdd.forEach((layerId) => {\n // add layer spec and map\n const toBeAddedLayerSpec: ToBeAddedLayerSpec = {\n ...newLayersMap[layerId],\n source: sourceWithLayers.source.id,\n } as ToBeAddedLayerSpec;\n layerSpecs.push(toBeAddedLayerSpec);\n });\n sourceWithLayers._updateSourceAndLayerIDs();\n // update existing layers\n changeLayersProps(newLayersToUpdate, oldLayersToUpdate, map);\n};\n\n/**\n * Adds the given layers to the map ensuring they respect their \"beforeID\" properties.\n * * We need to make sure that layers are added in the correct Z order because one layer may depend on another layer.\n * @param layersToAdd\n * @param map MapLibre map\n * @ignore\n */\nexport const addLayers = (layersToAdd: ToBeAddedLayerSpec[], map: Map): void => {\n const layerIdsAlreadyOnMap = new Set<string>();\n const addLayer = (layer: ToBeAddedLayerSpec): void => {\n // we can safely add this layer\n if (!map.getLayer(layer.id)) {\n map.addLayer({ ...layer, layout: { ...layer.layout, visibility: 'none' } }, layer.beforeID);\n }\n layerIdsAlreadyOnMap.add(layer.id);\n };\n\n const mapIdDependency: Record<string, ToBeAddedLayerSpec[]> = {};\n\n layersToAdd.forEach((layer) => {\n if (layer.beforeID) {\n if (layerIdsAlreadyOnMap.has(layer.beforeID) || map.getLayer(layer.beforeID)) {\n layerIdsAlreadyOnMap.add(layer.beforeID);\n addLayer(layer);\n } else if (mapIdDependency[layer.beforeID]) {\n // we cannot add this layer yet\n mapIdDependency[layer.beforeID].push(layer);\n } else {\n mapIdDependency[layer.beforeID] = [layer];\n }\n } else {\n addLayer(layer);\n }\n });\n\n // try to process the rest of layers\n while (Object.keys(mapIdDependency).length > 0) {\n const idsWeCanProcess = Object.keys(mapIdDependency).filter((id) => layerIdsAlreadyOnMap.has(id));\n if (!idsWeCanProcess.length) {\n console.error(\n `Some layers cannot be added. Check for non-existing layers, or circular beforeID dependencies for the following: ${JSON.stringify(Object.keys(mapIdDependency))}`,\n );\n return;\n }\n idsWeCanProcess.forEach((id) => {\n mapIdDependency[id].forEach((layer) => addLayer(layer));\n delete mapIdDependency[id];\n });\n }\n};\n\n/**\n * Adding a style-based module to the given style input, if possible.\n * * This results in a style input which will include such style-based module (e.g. include traffic layers).\n * @param style which we want to update.\n * @param styleModule module we want to add.\n * @ignore\n */\nexport const updateStyleWithModule = (style: StyleInput | undefined, styleModule: StyleModule): StyleInput => {\n switch (typeof style) {\n case 'undefined':\n return { type: 'standard', include: [styleModule] };\n case 'string':\n // this is a standard style\n return { type: 'standard', id: style, include: [styleModule] };\n default:\n if (style.type === 'standard') {\n if (style.include) {\n return { ...style, include: [...style.include, styleModule] };\n }\n return { ...style, include: [styleModule] };\n }\n throw cannotAddStyleModuleToCustomStyle(styleModule);\n }\n};\n\n/**\n * Checks if the source is missing and try to add it to the map by reloading its style.\n * @param map the TomTom map instance.\n * @param sourceId id of the source.\n * @param styleModule style module of the source.\n * @ignore\n */\nexport const ensureAddedToStyle = async (map: TomTomMap, sourceId: string, styleModule: StyleModule): Promise<void> => {\n if (!map.mapLibreMap.getSource(sourceId)) {\n const mapLibreMap = map.mapLibreMap;\n if (!mapLibreMap.isStyleLoaded()) {\n // we let the map settle before changing its style again, so the previous style/data load goes smoother:\n await mapLibreMap.once('idle');\n }\n map.setStyle(updateStyleWithModule(map.getStyle(), styleModule));\n await waitUntilSourceIsLoaded(map, sourceId);\n\n await mapLibreMap.once('styledata');\n // we're loading a bunch of style layers to the map, and we hide them all by default:\n // see TomTomMap.handleStyleData for similar logic\n for (const layer of filterLayersBySources(mapLibreMap, [sourceId])) {\n mapLibreMap.setLayoutProperty(layer.id, 'visibility', 'none', { validate: false });\n }\n // Since we just changed the style visibility, we ensure to wait until the style data is changed before returning to prevent race conditions:\n await mapLibreMap.once('styledata');\n }\n};\n\n/**\n * Sets the given image on the map (loading it if necessary), either adding or updating it.\n * @ignore\n */\nexport const addOrUpdateImage = async (\n mode: 'if-not-in-sprite' | 'add-or-update',\n imageId: string,\n imageToLoad: string | HTMLImageElement,\n map: Map,\n options?: Partial<StyleImageMetadata>,\n) => {\n // defensive check (should not happen but we cannot let it crash):\n if (!imageToLoad) {\n console.warn(`addOrUpdateImage called with empty image for ID ${imageId}`);\n return;\n }\n\n // Helper function to add or update the image\n const addOrUpdateToMap = (imgElement: HTMLImageElement | ImageData | ImageBitmap) => {\n const imageExists = map.hasImage(imageId);\n if (imageExists && mode == 'add-or-update') {\n map.updateImage(imageId, imgElement);\n } else if (!imageExists) {\n map.addImage(imageId, imgElement, options);\n }\n };\n\n const ensureImageLoaded = (imgElement: HTMLImageElement) => {\n // An image is successfully loaded if it's complete AND has valid dimensions\n // (naturalWidth > 0 ensures the image didn't fail to load)\n if (imgElement.complete) {\n if (imgElement.naturalWidth > 0) {\n addOrUpdateToMap(imgElement);\n } else {\n // Image is complete but failed to load\n console.warn(`Failed to load image for ID ${imageId}`);\n }\n } else {\n imgElement.onload = () => addOrUpdateToMap(imgElement);\n imgElement.onerror = () => console.warn(`Failed to load image for ID ${imageId}`);\n }\n };\n\n if (typeof imageToLoad === 'string') {\n if (imageToLoad.includes('<svg')) {\n // Supporting raw SVGs:\n const imgElement = svgToImg(parseSvg(imageToLoad));\n ensureImageLoaded(imgElement);\n } else {\n // Expecting image URL, so the image needs to be downloaded first:\n addOrUpdateToMap((await map.loadImage(imageToLoad)).data);\n }\n } else {\n // Expecting HTMLImageElement, wait for it to be loaded\n ensureImageLoaded(imageToLoad);\n }\n};\n\n/**\n * Returns the light/dark theme for a known standard style.\n * @param standardStyleID\n */\nconst getStandardStyleTheme = (standardStyleID: StandardStyleID): LightDark => {\n switch (standardStyleID) {\n case 'standardDark':\n case 'drivingDark':\n case 'monoDark':\n case 'satellite':\n return 'dark';\n default:\n return 'light';\n }\n};\n\n/**\n * Returns the light/dark theme for a given style input.\n * * Unknown standard styles and custom styles are considered as 'light' theme.\n * @param styleInput The style input to check. If not provided, 'light' is returned.\n * @ignore\n */\nexport const getStyleLightDarkTheme = (styleInput?: StyleInput): LightDark => {\n if (typeof styleInput === 'string') {\n return getStandardStyleTheme(styleInput);\n }\n const standardStyle = styleInput as StandardStyle;\n if (standardStyle?.id) {\n return getStandardStyleTheme(standardStyle.id);\n }\n return 'light';\n};\n\n/**\n * Adds the large POI sprite to the map style.\n * @ignore\n */\nexport const addPinCategoriesSpriteToStyle = async (mapParams: InternalTomTomMapParams, mapLibreMap: Map) => {\n const params = new URLSearchParams();\n // Proxy deployments leave apiKey empty and let the proxy inject the real\n // key server-side. Skip the param entirely rather than emitting `key=`.\n if (mapParams.apiKey) {\n params.set('key', mapParams.apiKey);\n }\n params.set('poi', `poi_${getStyleLightDarkTheme(mapParams.style)}`);\n params.set('apiVersion', '1');\n params.set('apiChannel', 'preview');\n mapLibreMap.setSprite(`${mapParams.commonBaseURL}/maps/orbis/assets/sprites/2.*/sprite?${params}`, {\n validate: false,\n });\n};\n","import type { Map } from 'maplibre-gl';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { EventsProxy } from './EventsProxy';\nimport { waitUntilMapIsReady } from './mapUtils';\nimport type { MapModuleCommonConfig, SourcesWithLayers, SourceWithLayerIDs } from './types';\n\n/**\n * Whether a map module is based on a map style or on added GeoJSON data.\n */\ntype MapModuleSource = 'style' | 'geojson';\n\n/**\n * Base class for all Maps SDK map modules.\n *\n * This abstract class provides the foundation for creating map modules that can display\n * and manage various types of data on a TomTom map. It handles module lifecycle management,\n * including initialization, configuration updates, and automatic restoration after map style changes.\n *\n * @remarks\n * All map modules extend this class to ensure consistent behavior across the SDK.\n * The class manages the module's sources, layers, and configuration, automatically\n * handling map style changes by restoring the module's state when needed.\n *\n * @typeParam SOURCES_WITH_LAYERS - The type defining the sources and layers used by this module\n * @typeParam CFG - The configuration type for this module, or undefined if no configuration is needed. When defined, must extend MapModuleCommonConfig.\n *\n * @example\n * ```typescript\n * class CustomModule extends AbstractMapModule<MySourcesWithLayers, MyConfig> {\n * constructor(tomtomMap: TomTomMap, config?: MyConfig) {\n * super('geojson', tomtomMap, config);\n * }\n * // Implement abstract methods...\n * }\n * ```\n *\n * @group Shared\n */\nexport abstract class AbstractMapModule<\n SOURCES_WITH_LAYERS extends SourcesWithLayers,\n CFG extends MapModuleCommonConfig | undefined = undefined,\n> {\n private readonly sourceType: MapModuleSource;\n /**\n * @ignore\n */\n protected readonly tomtomMap: TomTomMap;\n /**\n * @ignore\n */\n protected readonly eventsProxy: EventsProxy;\n /**\n * @ignore\n */\n protected readonly mapLibreMap: Map;\n /**\n * @ignore\n */\n protected sourcesWithLayers!: SOURCES_WITH_LAYERS;\n /**\n * @ignore\n */\n protected _sourceAndLayerIDs!: Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs>;\n /**\n * @ignore\n */\n protected config?: CFG;\n /**\n * @ignore\n */\n protected _initializing = true;\n\n /**\n * Handlers registered via events.on('config-change', …).\n * Stored here so the array reference stays stable across multiple events getter calls.\n * @ignore\n */\n protected readonly configChangeHandlers: ((config: CFG | undefined) => void)[] = [];\n\n /**\n * Monotonically increasing index unique to this instance within its concrete module\n * class. Auto-assigned in the constructor based on `new.target`, so each subclass\n * has its own counter without per-subclass boilerplate. Use as a suffix on\n * auto-generated source and layer IDs to keep them stable across style changes and\n * unique across module instances.\n * @ignore\n */\n protected readonly instanceIndex: number;\n\n /**\n * Per-concrete-class instance counter. WeakMap-keyed so unloaded classes can be GC'd.\n * @ignore\n */\n private static readonly instanceCounters = new WeakMap<Function, number>();\n\n /**\n * Indicates that this module is currently adding its sources and layers to the map, so during this time it might not function properly.\n * @see waitUntilModuleReady\n * @private\n */\n private moduleReady = false;\n\n /**\n * Builds this module based on a given Maps SDK map.\n * @param tomtomMap The map. It may or may not be initialized at this stage,\n * but the module ensures to initialize itself once it is.\n * @param sourceType Whether the module is based on a map style or on added GeoJSON data.\n * @param config Optional configuration to initialize directly as soon as the map is ready.\n */\n protected constructor(sourceType: MapModuleSource, tomtomMap: TomTomMap, config?: CFG) {\n const concreteClass = new.target;\n const previousIndex = AbstractMapModule.instanceCounters.get(concreteClass) ?? -1;\n this.instanceIndex = previousIndex + 1;\n AbstractMapModule.instanceCounters.set(concreteClass, this.instanceIndex);\n\n this.sourceType = sourceType;\n this.tomtomMap = tomtomMap;\n this.eventsProxy = tomtomMap._eventsProxy;\n this.mapLibreMap = tomtomMap.mapLibreMap;\n // TODO: we need to find a cleaner separation between initSourcesWithLayers and applyConfig for most modules to prevent double work, particularly with adding layers\n this.initSourcesWithLayers(config);\n this.applyConfig(config);\n this.tomtomMap.addStyleChangeHandler({\n onStyleAboutToChange: () => {\n this.moduleReady = false;\n },\n onStyleChanged: () => this.restoreDataAndConfig(),\n });\n this._initializing = false;\n }\n\n /**\n * Initializes the sources with layers of this module.\n * @param config The optional configuration for the module.\n * @param restore Whether we are restoring an existing module after the map style got reloaded.\n * @protected\n * @ignore\n */\n protected initSourcesWithLayers(config?: CFG, restore?: boolean): void {\n this.moduleReady = false;\n this.sourcesWithLayers = this._initSourcesWithLayers(config, restore);\n this._sourceAndLayerIDs = Object.fromEntries(\n Object.entries(this.sourcesWithLayers).map(([name, sourceWithLayers]) => [\n name,\n sourceWithLayers.sourceAndLayerIDs,\n ]),\n ) as Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs>;\n if (restore) {\n this.eventsProxy.updateIfRegistered(this.sourcesWithLayers);\n }\n // Only if the map is still ready, we consider that the module is ready.\n // Otherwise, we assume there's a quick style change in progress and expect that'll trigger the module to restore itself again.\n if (this.tomtomMap.mapReady) {\n this.moduleReady = true;\n }\n }\n\n /**\n * Initializes the sources with layers for the specific module.\n * @protected\n * @ignore\n */\n protected abstract _initSourcesWithLayers(config?: CFG, restore?: boolean): SOURCES_WITH_LAYERS;\n\n protected async waitUntilModuleReady(): Promise<void> {\n await waitUntilMapIsReady(this.tomtomMap);\n if (!this.moduleReady) {\n await new Promise<void>((resolve) => {\n const interval = setInterval(() => {\n if (this.tomtomMap.mapReady && this.moduleReady) {\n clearInterval(interval);\n resolve();\n }\n }, 200);\n });\n }\n }\n\n /**\n * Applies a configuration to this module.\n *\n * This method updates the module's behavior and appearance based on the provided configuration.\n * The configuration is stored internally and will be automatically reapplied if the map style changes.\n *\n * @param config - The configuration object to apply to the module. Pass `undefined` to reset\n * the configuration to default values.\n *\n * @remarks\n * When a configuration is applied, the module updates its visual representation and behavior\n * accordingly. The configuration persists across map style changes, ensuring consistent\n * module behavior even when the map's base style is modified.\n *\n * @example\n * ```typescript\n * // Apply a new configuration\n * myModule.applyConfig({ visible: true, opacity: 0.8 });\n *\n * // Reset to default configuration\n * myModule.applyConfig(undefined);\n * ```\n *\n * @see {@link resetConfig} for a convenience method to reset configuration\n * @see {@link getConfig} to retrieve the current configuration\n */\n applyConfig(config: CFG | undefined) {\n this.config = this._applyConfig(config);\n this.emitConfigChange();\n }\n\n /**\n * Emits a `config-change` event to all registered handlers.\n * Skipped during module initialisation to avoid spurious events.\n * @ignore\n */\n protected emitConfigChange(): void {\n if (this._initializing) return;\n\n for (const handler of this.configChangeHandlers) {\n handler(this.config);\n }\n }\n\n /**\n * Internal implementation to apply config for the specific module.\n * @param config The config to apply. this.config contains the previous configuration (if any).\n * Once the method returns config, it will be assigned to this.config.\n * @protected\n * @ignore\n */\n protected abstract _applyConfig(config: CFG | undefined): CFG | undefined;\n\n /**\n * Resets the configuration of this module to its default values.\n *\n * This is a convenience method that clears any previously applied configuration\n * and restores the module to its initial state. This is equivalent to calling\n * `applyConfig(undefined)`.\n *\n * @remarks\n * After calling this method, the module will behave as if no configuration was ever applied.\n * Any custom settings, styling, or behavior modifications will be removed and replaced\n * with default values.\n *\n * @example\n * ```typescript\n * // Apply some configuration\n * myModule.applyConfig({ visible: true, opacity: 0.5 });\n *\n * // Later, reset to defaults\n * myModule.resetConfig();\n * ```\n *\n * @see {@link applyConfig} to apply a new configuration\n * @see {@link getConfig} to retrieve the current configuration before resetting\n */\n resetConfig(): void {\n this.applyConfig(undefined);\n }\n\n private async restoreDataAndConfig() {\n // defensively declaring the module as not ready to prevent race conditions:\n this.moduleReady = false;\n if (this.sourceType === 'geojson') {\n // Defer GeoJSON layer restoration by one animation frame to work around a MapLibre\n // glitch where symbol layers can't be added right after a styledata event.\n requestAnimationFrame(() => {\n this.restoreDataAndConfigImpl();\n });\n } else {\n this.restoreDataAndConfigImpl();\n }\n }\n\n /**\n * implementation needed to restore the module state (data and config applied to the module).\n * to be used to restore module state after map style change\n * @protected\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n this.initSourcesWithLayers(this.config, true);\n this._applyConfig(this.config);\n }\n\n /**\n * Retrieves a copy of the current module configuration.\n *\n * This method returns a shallow copy of the configuration object that is currently\n * applied to the module. If no configuration has been applied, it returns `undefined`.\n *\n * @returns A shallow copy of the current configuration object, or `undefined` if no\n * configuration is currently applied. The returned object is a copy to prevent\n * unintended modifications to the internal state.\n *\n * @remarks\n * The returned configuration object is a shallow copy, which means that while the\n * top-level properties are copied, any nested objects or arrays are still referenced\n * from the original configuration. This is sufficient for most use cases but should\n * be kept in mind when dealing with complex configurations.\n *\n * @example\n * ```typescript\n * // Apply a configuration\n * myModule.applyConfig({ visible: true, opacity: 0.8 });\n *\n * // Later, retrieve the current configuration\n * const currentConfig = myModule.getConfig();\n * console.log(currentConfig); // { visible: true, opacity: 0.8 }\n *\n * // When no config is applied\n * myModule.resetConfig();\n * console.log(myModule.getConfig()); // undefined\n * ```\n *\n * @see {@link applyConfig} to modify the configuration\n * @see {@link resetConfig} to clear the configuration\n */\n getConfig() {\n return this.config && { ...this.config };\n }\n\n /**\n * Gets the source and layer identifiers for all sources managed by this module.\n *\n * This property provides access to the MapLibre source and layer IDs that were created\n * and are managed by this module. These IDs can be used to interact directly with\n * MapLibre's API or to identify which layers belong to this module.\n *\n * @returns A record mapping each source name to its corresponding source ID and layer IDs.\n * Each entry contains the MapLibre source identifier and an array of layer identifiers\n * associated with that source.\n *\n * @remarks\n * The returned IDs are useful when you need to:\n * - Directly manipulate layers using MapLibre's native API\n * - Identify which layers on the map belong to this module\n * - Set layer ordering or positioning relative to other layers\n * - Access source or layer properties through MapLibre methods\n *\n * @example\n * ```typescript\n * const ids = myModule.sourceAndLayerIDs;\n * console.log(ids);\n * // {\n * // mySource: {\n * // sourceID: 'my-source-id',\n * // layerIDs: ['layer-1', 'layer-2']\n * // }\n * // }\n *\n * // Use with MapLibre API\n * const map = myModule.mapLibreMap;\n * ids.mySource.layerIDs.forEach(layerId => {\n * map.setLayoutProperty(layerId, 'visibility', 'visible');\n * });\n * ```\n */\n get sourceAndLayerIDs(): Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs> {\n return this._sourceAndLayerIDs;\n }\n}\n","import type { ModuleEvents } from './ModuleEvents';\nimport type { EventType, UserEventHandler } from './types';\nimport { UserEvents } from './UserEvents';\n\n/**\n * Unified events interface that blends user interaction events and module lifecycle events\n * under a single `on` / `off` surface.\n *\n * User event types (`click`, `contextmenu`, `hover`, `long-hover`) are handled by the\n * underlying {@link UserEvents} instance and removed with {@link off}.\n *\n * Module lifecycle event types (`config-change`, `shown-features`) are handled by the\n * underlying {@link ModuleEvents} instance. Each `on` call returns an unsubscribe function.\n * Both user and module event types are also removable in bulk via {@link off}.\n *\n * @typeParam T - Feature type surfaced by user interaction handlers.\n * @typeParam CFG - Module configuration type passed to `config-change` handlers.\n * @typeParam TShown - Data type passed to `shown-features` handlers. Use `never` for\n * modules that have no `show` method.\n *\n * @example\n * ```typescript\n * // User interaction\n * const unsub = places.events.on('click', (feature, lngLat) => {\n * console.log('Clicked place:', feature.properties.name);\n * });\n *\n * // Module lifecycle\n * const unsubConfig = places.events.on('config-change', (config) => {\n * console.log('Config updated:', config);\n * });\n *\n * const unsubShown = places.events.on('shown-features', (features) => {\n * console.log('Shown:', features);\n * });\n *\n * // Cleanup\n * unsub();\n * unsubConfig();\n * unsubShown();\n * places.events.off('click'); // also valid for user events\n * ```\n *\n * @group Events\n */\nexport class CombinedEvents<T, CFG, TShown = never> {\n constructor(\n private readonly userEventsInstance: UserEvents<T>,\n private readonly moduleEventsInstance: ModuleEvents<CFG, TShown>,\n ) {}\n\n /**\n * Subscribe to a user interaction event (`click`, `contextmenu`, `hover`, `long-hover`).\n *\n * @param type - The user event type.\n * @param handler - Called with the feature, click/hover coordinates, all features at the\n * point, and the source configuration.\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('click', (feature, lngLat) => {\n * showPlaceDetails(feature.properties);\n * });\n * unsub(); // remove only this handler\n * ```\n */\n on(type: EventType, handler: UserEventHandler<T>): () => void;\n /**\n * Subscribe to config-change events.\n *\n * Fires after any configuration mutation — `applyConfig`, `setVisible`, or any\n * module-specific setter. The handler receives the module's full updated configuration.\n *\n * @param type - `'config-change'`\n * @param handler - Receives the module's config (or `undefined` when config is cleared).\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = trafficFlow.events.on('config-change', (config) => {\n * toggle.checked = config?.visible ?? false;\n * });\n * unsub();\n * ```\n */\n on(type: 'config-change', handler: (config: CFG | undefined) => void): () => void;\n /**\n * Subscribe to shown-features events.\n *\n * Fires immediately after the module's `show()` method (or equivalent) has rendered\n * new data on the map. Only available on modules that have a `show` method.\n *\n * @param type - `'shown-features'`\n * @param handler - Receives the data that was passed to `show()`.\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('shown-features', (features) => {\n * fitMapToResults(features);\n * });\n * unsub();\n * ```\n */\n on(type: 'shown-features', handler: (features: TShown) => void): () => void;\n on(\n type: EventType | 'config-change' | 'shown-features',\n handler: UserEventHandler<T> | ((config: CFG | undefined) => void) | ((features: TShown) => void),\n ): () => void {\n if (type === 'config-change') {\n return this.moduleEventsInstance.on('config-change', handler as (config: CFG | undefined) => void);\n }\n\n if (type === 'shown-features') {\n return this.moduleEventsInstance.on('shown-features', handler as (features: TShown) => void);\n }\n\n return this.userEventsInstance.on(type, handler as UserEventHandler<T>);\n }\n\n /**\n * Remove **all** handlers for the given event type at once.\n *\n * Accepts both user interaction types (`click`, `contextmenu`, `hover`, `long-hover`) and\n * module lifecycle types (`config-change`, `shown-features`).\n *\n * Prefer the unsubscribe function returned by {@link on} when you need to remove a single\n * handler. Use `off` for bulk teardown — e.g. when unmounting a component that registered\n * multiple handlers of the same type.\n *\n * @param type - The event type whose handlers should all be cleared.\n *\n * @example\n * ```typescript\n * places.events.off('click'); // clear all click handlers\n * places.events.off('config-change'); // clear all config-change handlers\n * ```\n */\n off(type: EventType | 'config-change' | 'shown-features'): void {\n if (type === 'config-change' || type === 'shown-features') {\n this.moduleEventsInstance.off(type);\n } else {\n this.userEventsInstance.off(type);\n }\n }\n}\n","import { isEmpty, remove } from 'lodash-es';\nimport type { LayerSpecification, MapGeoJSONFeature } from 'maplibre-gl';\nimport type { EventHandlerConfig, EventType, SourcesWithLayers, SourceWithLayers, UserEventHandler } from './types';\n\n// Handlers for a (source, event-type) are stored as an array: repeated `on()` calls append and\n// ALL of them fire; `on()` returns a per-handler unsubscribe (see `removeHandler`), while `remove`\n// clears every handler for that (source, type). Note a handler fires for any layer of its source —\n// per-layer-subset handlers within a single source are not distinguished.\ntype SourceEventTypeHandler = {\n sourceWithLayers: SourceWithLayers;\n layerIDs: string[];\n fn: UserEventHandler<any>;\n config?: EventHandlerConfig;\n};\n\ntype SourceEventHandlers = Partial<Record<EventType, SourceEventTypeHandler[]>>;\n\n// Source ID to event handlers for that source:\n// Example:\n// const handlers = {\n// vectorTiles: {\n// click: [\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [\"Buildings\", \"Roads - Major\"],\n// fn: UserEventsHandler...\n// },\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [\"Water\", \"Landuse - Parks],\n// fn: UserEventsHandler...\n// }\n// ],\n// hover: [\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [...],\n// fn: UserEventsHandler...\n// }\n// ]\n// },\ntype EventHandlers = Record<string, SourceEventHandlers>;\n\nconst matchesLayers = (layers: LayerSpecification[], layerIDs: string[]): boolean =>\n layerIDs.every((layerId, index) => layerId === layers[index].id);\n\n/**\n * @ignore\n */\nexport abstract class AbstractEventProxy {\n protected interactiveLayerIDs: string[] = [];\n protected handlers: EventHandlers = {};\n\n /**\n * Adds the given layers as interactive, so we'll listen to them for hover and click.\n * @param sourceWithLayers The sources and layers to listen to.\n */\n private ensureInteractiveLayerIDsAdded(sourceWithLayers: SourceWithLayers) {\n sourceWithLayers._layerSpecs.forEach((layerSpec) => {\n if (!this.interactiveLayerIDs.includes(layerSpec.id)) {\n this.interactiveLayerIDs.push(layerSpec.id);\n }\n });\n }\n\n /**\n * Register an event listener to the list.\n * @param sourceWithLayers The sources and layers to added.\n * @param handlerFn Function that will handle the event.\n * @param type Type of event to listen to.\n * @param config Optional configuration for the event handler.\n */\n addEventHandler<T = MapGeoJSONFeature>(\n sourceWithLayers: SourceWithLayers,\n handlerFn: UserEventHandler<T>,\n type: EventType,\n config: EventHandlerConfig | undefined,\n ) {\n this.ensureInteractiveLayerIDsAdded(sourceWithLayers);\n const sourceId = sourceWithLayers.source.id;\n\n if (!this.handlers[sourceId]) {\n this.handlers[sourceId] = { [type]: [] };\n }\n this.handlers[sourceId][type] ??= [];\n\n this.handlers[sourceId][type]?.push({\n sourceWithLayers,\n layerIDs: sourceWithLayers._layerSpecs.map((layer) => layer.id),\n fn: handlerFn,\n config,\n });\n }\n\n /**\n * Removes a single handler function for the given source and event type.\n * Other handlers registered for the same source and type are not affected.\n * Unlike {@link remove}, this does not remove the source from the interactive layer list —\n * other handlers may still depend on it.\n * @param sourceWithLayers The source whose handler should be removed.\n * @param type The event type.\n * @param handlerFn The exact function reference to remove.\n */\n removeHandler(sourceWithLayers: SourceWithLayers, type: EventType, handlerFn: UserEventHandler<any>) {\n const sourceEventTypeHandlers = this.handlers[sourceWithLayers.source.id]?.[type];\n if (sourceEventTypeHandlers) {\n remove(sourceEventTypeHandlers, (handler) => handler.fn === handlerFn);\n if (!sourceEventTypeHandlers.length) {\n delete this.handlers[sourceWithLayers.source.id]?.[type];\n if (isEmpty(this.handlers[sourceWithLayers.source.id])) {\n delete this.handlers[sourceWithLayers.source.id];\n }\n }\n }\n }\n\n /**\n * Removes the given sources and layers from the interactive list. When not present, nothing happens.\n * @param type The event type to be removed.\n * @param sourceWithLayers The sources and layers to remove, matched by source and layer IDs.\n */\n remove(sourceWithLayers: SourceWithLayers, type: EventType) {\n const sourceEventTypeHandlers = this.handlers[sourceWithLayers.source.id]?.[type];\n if (sourceEventTypeHandlers) {\n // @ts-ignore\n remove(sourceEventTypeHandlers, (handler) => matchesLayers(sourceWithLayers._layerSpecs, handler.layerIDs));\n // cleaning up empty arrays and objects if necessary:\n if (!sourceEventTypeHandlers.length) {\n delete this.handlers[sourceWithLayers.source.id]?.[type];\n if (isEmpty(this.handlers[sourceWithLayers.source.id])) {\n delete this.handlers[sourceWithLayers.source.id];\n }\n }\n }\n for (const layer of sourceWithLayers._layerSpecs) {\n // @ts-ignore\n remove(this.interactiveLayerIDs, (id) => layer.id === id);\n }\n }\n\n /**\n * Removes all interactive sources and layers.\n */\n removeAll() {\n this.interactiveLayerIDs = [];\n this.handlers = {};\n }\n\n /**\n * Returns whether this source is registered for events (has handlers attached).\n * @param sourceId The source id (should be linked to a SourceWithLayers instance).\n */\n hasSourceID(sourceId: string): boolean {\n return !!this.handlers[sourceId];\n }\n\n /**\n * Look up the {@link SourceWithLayers} registered for the given source ID, if any.\n *\n * The SDK assumes at most one `SourceWithLayers` per MapLibre source ID — each\n * module creates its own source — so any non-empty handler bucket gives us the\n * right reference. We iterate event-type buckets defensively to skip transient\n * empty arrays left mid-`removeHandler` before the bucket itself is deleted.\n *\n * NOTE: if two modules ever register against the same source ID with overlapping\n * layers, this returns the first one we encounter.\n */\n protected sourceWithLayersFor(sourceId: string | undefined): SourceWithLayers | undefined {\n if (!sourceId) return undefined;\n const sourceHandlers = this.handlers[sourceId];\n if (!sourceHandlers) return undefined;\n for (const typeHandlers of Object.values(sourceHandlers)) {\n if (typeHandlers?.length) return typeHandlers[0].sourceWithLayers;\n }\n return undefined;\n }\n\n /**\n * Updates the given sourcesWithLayers, if they have any handlers.\n * * (This is typically called to refresh any registered, stale sourceWithLayers references after a map style has changed).\n * @param sourcesWithLayers The new sources with layers to replace existing ones.\n */\n updateIfRegistered(sourcesWithLayers: SourcesWithLayers): void {\n for (const sourceWithLayers of Object.values(sourcesWithLayers)) {\n const sourceHandlers = this.handlers[sourceWithLayers.source.id];\n if (sourceHandlers) {\n for (const sourceEventTypeHandlers of Object.values(sourceHandlers)) {\n for (const handler of sourceEventTypeHandlers) {\n if (sourceWithLayers.equalSourceAndLayerIDs(handler.sourceWithLayers)) {\n // We keep the reference fresh:\n handler.sourceWithLayers = sourceWithLayers;\n }\n }\n }\n }\n }\n }\n\n protected findHandlers = (\n types: EventType[],\n sourceId: string | undefined,\n layerId: string | undefined,\n ): SourceEventTypeHandler[] =>\n (sourceId &&\n layerId &&\n types.flatMap((type) => {\n const sourceEventTypeHandlers = this.handlers[sourceId]?.[type];\n return sourceEventTypeHandlers?.length === 1\n ? // if there's only handler for that source and type, we just return it, no need to match layers:\n sourceEventTypeHandlers\n : this.handlers[sourceId]?.[type]?.filter((handler) => handler.layerIDs.includes(layerId)) || [];\n })) ||\n [];\n}\n","import type { Feature } from 'geojson';\nimport { isNil, omit } from 'lodash-es';\nimport type { MapGeoJSONFeature, Point2D } from 'maplibre-gl';\nimport { findFeatureByRefId, renderedRefId } from './featureId';\nimport { areBothDefinedAndEqual } from './mapUtils';\nimport { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { EventType, SourceWithLayers } from './types';\n\n/**\n * Keep only the hits from `source`; pass everything through when `source` is undefined.\n * Runs on raw `MapGeoJSONFeature`s, before substitution strips `.source`.\n * @ignore\n */\nexport const scopeToSource = (features: MapGeoJSONFeature[], source: string | undefined): MapGeoJSONFeature[] =>\n source ? features.filter((f) => f.source === source) : features;\n\n/**\n * Collapse duplicate hits keyed on `(source, {@link renderedRefId})`, keeping the first\n * (topmost) occurrence. Features with no recoverable id can't be keyed, so they're all kept.\n * @ignore\n */\nexport const dedupeRenderedFeatures = (features: MapGeoJSONFeature[]): MapGeoJSONFeature[] => {\n const seen = new Set<string>();\n const out: MapGeoJSONFeature[] = [];\n for (const f of features) {\n const refId = renderedRefId(f);\n if (refId === undefined) {\n out.push(f);\n continue;\n }\n const key = `${f.source} ${refId}`;\n if (!seen.has(key)) {\n seen.add(key);\n out.push(f);\n }\n }\n return out;\n};\n\nconst isHighPriority = (eventType: EventType): boolean => eventType === 'click' || eventType === 'contextmenu';\n\n/**\n * Puts or removes the given event state to the right feature in featuresToUpdate based on the\n * @param eventState The new event state to apply or to use as reference.\n * @param featureId The ID of the feature to update within featuresToUpdate.\n * @param featuresToUpdate The features list which will be updated.\n * @param mode If updateInProps, replaces the existing eventState. If removeFromProps, removes the existing eventState.\n * @return The index of the updated feature in the mutated featuresToUpdate array.\n * @ignore\n */\nexport const putEventState = (\n eventState: EventType,\n featureId: string | number | undefined,\n featuresToUpdate: Feature[], // \"featuresToUpdate\" will be mutated\n mode: 'updateInProps' | 'removeFromProps' = 'updateInProps',\n): number | undefined => {\n const { feature, index } = findFeatureByRefId(featuresToUpdate, featureId) || {};\n if (feature && (!isHighPriority(feature.properties?.eventState) || isHighPriority(eventState))) {\n const updatedFeature = {\n ...feature,\n properties:\n mode === 'updateInProps'\n ? { ...feature?.properties, eventState }\n : omit(feature?.properties, 'eventState'),\n };\n featuresToUpdate.splice(index as number, 1, updatedFeature);\n return index;\n }\n return undefined;\n};\n\nconst removeEventStateAndShow = (\n newEventType: EventType,\n rawFeature: MapGeoJSONFeature,\n sourceWithLayers: GeoJSONSourceWithLayers,\n): void => {\n const prevFeaturesToUpdate = [...sourceWithLayers.shownFeatures.features];\n // renderedRefId, not rawFeature.id: clustered sources give the rendered feature a synthetic\n // top-level id, so we must key the cache lookup on the real properties.id.\n const updatedIndex = putEventState(\n newEventType,\n renderedRefId(rawFeature),\n prevFeaturesToUpdate,\n 'removeFromProps',\n );\n if (!isNil(updatedIndex)) {\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: prevFeaturesToUpdate },\n { automaticVisibility: false },\n );\n }\n};\n\n/**\n * Mutates the `eventState` marker on the cached `shownFeatures` so MapLibre paint\n * expressions can react to hover/click. Mutates `prevSourceWithLayers` too when the\n * event moved to a different source. Does not produce a caller-facing feature —\n * the dispatch site resolves that from the post-mutation cache.\n * @ignore\n */\nexport const updateEventState = (\n eventState: EventType,\n eventFeature: MapGeoJSONFeature | undefined,\n prevEventFeature: MapGeoJSONFeature | undefined,\n sourceWithLayers: SourceWithLayers | undefined,\n prevSourceWithLayers: SourceWithLayers | undefined,\n): void => {\n if (eventFeature && sourceWithLayers instanceof GeoJSONSourceWithLayers) {\n const featuresToUpdate = [...sourceWithLayers.shownFeatures.features];\n putEventState(eventState, renderedRefId(eventFeature), featuresToUpdate);\n\n if (prevEventFeature && !areBothDefinedAndEqual(prevEventFeature, eventFeature)) {\n // (we have both current and prev features for this event type)\n if (prevSourceWithLayers === sourceWithLayers) {\n // we undo the event state from prev feature next to the new one (they will be shown below):\n putEventState(eventState, renderedRefId(prevEventFeature), featuresToUpdate, 'removeFromProps');\n } else if (prevSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n // the prev feature is in other source/layers, so we update and show them:\n removeEventStateAndShow(eventState, prevEventFeature, prevSourceWithLayers);\n }\n }\n\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: featuresToUpdate },\n { automaticVisibility: false },\n );\n return;\n }\n if (prevEventFeature && prevSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n removeEventStateAndShow(eventState, prevEventFeature, prevSourceWithLayers);\n }\n};\n\n/**\n * Detects whether there's been a hovering change with the given event and state params.\n * @param hoveringPoint The current hovering pixel coordinates.\n * @param hoveringFeature The current feature being hovered, if any.\n * @param prevHoveredPoint The pixel coordinates from the previous hovering event, if any.\n * @param prevHoveredFeature The previous feature being hovered, if any (could be the same as hoveringFeature).\n * @ignore\n */\nexport const detectHoverState = (\n hoveringPoint: Point2D,\n hoveringFeature: MapGeoJSONFeature | undefined,\n prevHoveredPoint: Point2D | undefined,\n prevHoveredFeature: MapGeoJSONFeature | undefined,\n): {\n /**\n * Whether a hover state change happened, such as no-hover -> hover or vice-versa.\n */\n hoverChanged?: boolean;\n /**\n * Whether the mouse is moving along the hovered feature (not stopped on it).\n */\n mouseInMotionOverHoveredFeature?: boolean;\n} => {\n if (hoveringFeature) {\n if (!prevHoveredFeature) {\n return { hoverChanged: true };\n }\n if (\n (hoveringFeature.id && hoveringFeature.id !== prevHoveredFeature.id) ||\n (hoveringFeature.properties.id && hoveringFeature.properties.id !== prevHoveredFeature.properties.id) ||\n hoveringFeature.source !== prevHoveredFeature.source ||\n // comparing by layer ID is needed when two id-less features from the same source but different layers are compared\n // this can happen when e.g. hovering over different layer groups for the base map\n hoveringFeature.layer.id !== prevHoveredFeature.layer.id\n ) {\n // hovering from one feature to another one (from the same or different layer/source):\n return { hoverChanged: true };\n }\n // (else we're hovering along the same feature)\n if (\n prevHoveredPoint &&\n (hoveringPoint.x - prevHoveredPoint.x != 0 || hoveringPoint.y - prevHoveredPoint.y != 0)\n ) {\n return { mouseInMotionOverHoveredFeature: true };\n }\n } else if (prevHoveredFeature) {\n return { hoverChanged: true };\n }\n return {};\n};\n","import type { Feature } from 'geojson';\nimport type { LngLat, Map, MapGeoJSONFeature, MapMouseEvent, Point2D, PointLike } from 'maplibre-gl';\nimport type { MapEventsConfig } from '../init';\nimport { AbstractEventProxy } from './AbstractEventProxy';\nimport { dedupeRenderedFeatures, detectHoverState, scopeToSource, updateEventState } from './eventUtils';\nimport { renderedRefId } from './featureId';\nimport { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { ClickEventType, EventHandlerConfig, SourceWithLayers } from './types';\n\n// Default values for events\nconst eventsProxyDefaultConfig: Required<MapEventsConfig> = {\n precisionMode: 'box',\n paddingBoxPx: 5,\n cursorOnHover: 'pointer',\n cursorOnMouseDown: 'grabbing',\n cursorOnMap: 'default',\n /**\n * Delayed hover control:\n * * The first hover we do after the map moves is longer\n */\n longHoverDelayAfterMapMoveMS: 800,\n /* Followup hovers with the same non-moving map are quicker (\"hovering around mode\") */\n longHoverDelayOnStillMapMS: 300,\n};\n\n/**\n * This is the place where we handle the user events on the map (mousemove/hover and click mostly).\n * To have full control on hovers and clicks when multiple overlapping layers are present, that logic must be centralized here.\n * @ignore\n */\nexport class EventsProxy extends AbstractEventProxy {\n private readonly map: Map;\n private readonly mapCanvas: HTMLCanvasElement;\n private enabled = true;\n private hoveringLngLat?: LngLat;\n private hoveringPoint?: Point2D;\n private hoveringFeature?: MapGeoJSONFeature;\n private hoveringFeatures?: MapGeoJSONFeature[];\n private hoveringSourceWithLayers?: SourceWithLayers;\n private longHoverTimeoutHandlerID?: number;\n // Control flag to indicate that the coming hover is the first one since the map is \"quiet\" again:\n private firstDelayedHoverSinceMapMove = true;\n private lastClickedFeature?: MapGeoJSONFeature;\n private lastClickedSourceWithLayers?: SourceWithLayers;\n private lastCursorStyle: string;\n // Configuration\n private readonly config: Required<MapEventsConfig>;\n\n constructor(map: Map, config: MapEventsConfig = {}) {\n super();\n this.map = map;\n this.mapCanvas = map.getCanvas();\n this.config = { ...eventsProxyDefaultConfig, ...config };\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n this.lastCursorStyle = this.config.cursorOnMap;\n this.listenToEvents();\n }\n\n private listenToEvents() {\n this.map.on('mousemove', (ev) => this.onMouseMove(ev));\n this.map.on('movestart', () => this.onMouseStart());\n this.map.on('mouseout', () => this.onMouseOut());\n this.map.on('mouseover', (ev) => this.onMouseMove(ev));\n this.map.on('mousedown', () => this.onMouseDown());\n this.map.on('mouseup', () => this.onMouseUp());\n this.map.on('click', (ev) => this.onMapClick('click', ev));\n this.map.on('contextmenu', (ev) => this.onMapClick('contextmenu', ev));\n }\n\n // Enable/Disable Events\n public enable(enabled: boolean) {\n this.enabled = enabled;\n if (!enabled) {\n this.clearLongHoverTimeout();\n }\n }\n\n private toPaddedBounds(point: Point2D): [[number, number], [number, number]] {\n const padding = this.config.paddingBoxPx;\n return [\n // sw:\n [point.x - padding, point.y + padding],\n // ne:\n [point.x + padding, point.y - padding],\n ];\n }\n\n private isEnabled() {\n return this.enabled && !this.map.isMoving();\n }\n\n /**\n * Build the consumer-facing `allEventFeatures`: scope to the firing source, dedupe, then\n * substitute cached typed originals. Scope and dedupe run on the raw features, since\n * `.source` and the rendered id survive only before substitution; the top hit stays first.\n *\n * Scoping leaves only `firingSource`'s hits, so its {@link SourceWithLayers} is resolved once\n * here rather than per feature. For GeoJSON-backed sources we substitute the cached typed\n * original (un-stringified properties, real Dates, nested objects); for everything else — vector\n * tiles, unregistered sources — the MapLibre-rendered feature passes through (`MapGeoJSONFeature\n * extends Feature`).\n */\n private toCallerFeatures(rendered: MapGeoJSONFeature[], firingSource: string | undefined): Feature[] {\n const scoped = dedupeRenderedFeatures(scopeToSource(rendered, firingSource));\n const sourceWithLayers = this.sourceWithLayersFor(firingSource);\n return sourceWithLayers instanceof GeoJSONSourceWithLayers\n ? scoped.map((feature) => sourceWithLayers.findById(renderedRefId(feature))?.feature ?? feature)\n : scoped;\n }\n\n private getRenderedFeatures(point: Point2D): MapGeoJSONFeature[] {\n if (!this.interactiveLayerIDs.length) {\n return [];\n }\n const options = { layers: this.interactiveLayerIDs, validate: false };\n const precision = this.config.precisionMode;\n // first optional attempt right in the given coordinates:\n const renderedFeatures =\n precision === 'point-then-box' || precision === 'point'\n ? this.map.queryRenderedFeatures(point as PointLike, options)\n : [];\n return renderedFeatures.length || precision === 'point'\n ? renderedFeatures\n : // second attempt using 'box' = padded bounds (trying to hit something slightly further from the pointer location)\n this.map.queryRenderedFeatures(this.toPaddedBounds(point), options);\n }\n\n private clearLongHoverTimeout() {\n window.clearTimeout(this.longHoverTimeoutHandlerID);\n }\n\n private restartLongHoverTimeout() {\n this.clearLongHoverTimeout();\n this.longHoverTimeoutHandlerID = window.setTimeout(\n () => this.handleLongHoverTimeout(),\n this.firstDelayedHoverSinceMapMove\n ? this.config.longHoverDelayAfterMapMoveMS\n : this.config.longHoverDelayOnStillMapMS,\n );\n }\n\n private handleLongHoverTimeout() {\n // We avoid firing long hovers when the feature is in clicked state:\n this.firstDelayedHoverSinceMapMove = false;\n\n if (this.hoveringSourceWithLayers) {\n updateEventState('long-hover', this.hoveringFeature, undefined, this.hoveringSourceWithLayers, undefined);\n\n const longHoverHandlers = this.findHandlers(\n ['long-hover'],\n this.hoveringFeature?.source,\n this.hoveringFeature?.layer.id,\n );\n\n // Only build `allEventFeatures` when a handler will read it (see onMouseMove).\n if (longHoverHandlers.length) {\n const callerFeatures = this.toCallerFeatures(this.hoveringFeatures ?? [], this.hoveringFeature?.source);\n for (const handler of longHoverHandlers) {\n handler.fn(\n callerFeatures[0],\n this.hoveringLngLat as LngLat,\n callerFeatures,\n handler.sourceWithLayers,\n );\n }\n }\n }\n }\n\n private onMouseStart() {\n this.firstDelayedHoverSinceMapMove = true;\n this.clearLongHoverTimeout();\n }\n\n private onMouseOut() {\n // Preventing accidental de-hover event if we actually leave the map canvas.\n // Since this could potentially be about jumping into a map popup, so we leave that up to the caller.\n this.clearLongHoverTimeout();\n }\n\n private onMouseDown() {\n this.lastCursorStyle = this.mapCanvas.style.cursor;\n this.mapCanvas.style.cursor = this.config.cursorOnMouseDown;\n }\n\n private onMouseUp() {\n this.mapCanvas.style.cursor = this.lastCursorStyle;\n }\n\n private onMouseMove(ev: MapMouseEvent) {\n if (!this.isEnabled()) {\n // We ensure no unwanted hover handling while disabled or the map moves\n return;\n }\n\n this.hoveringFeatures = this.getRenderedFeatures(ev.point);\n const [hoveredTopFeature] = this.hoveringFeatures;\n\n // Check if the layer has any handlers registered.\n // Since hover is the \"lowest\" event type, having a handler for any event type justifies supporting hover state.\n // However, we'll only fire the hover events if there are handlers for hover specifically.\n if (hoveredTopFeature && !this.hasSourceID(hoveredTopFeature.source)) {\n return;\n }\n\n // hoverChangeDetected: whether a change happened, such as no-hover -> hover or vice versa:\n // mouseInMotionOverHoveredFeature: whether the mouse is moving along the hovered feature (not stopped on it):\n const { hoverChanged, mouseInMotionOverHoveredFeature } = detectHoverState(\n ev.point,\n hoveredTopFeature,\n this.hoveringPoint,\n this.hoveringFeature,\n );\n\n if (hoverChanged || mouseInMotionOverHoveredFeature) {\n this.hoveringLngLat = ev.lngLat;\n this.hoveringPoint = ev.point;\n const prevHoveredFeature = this.hoveringFeature;\n this.hoveringFeature = hoveredTopFeature;\n const prevHoveredSourceWithLayers = this.hoveringSourceWithLayers;\n\n // Hovering basic event states are still processed if any other handlers are registered for that source/layers.\n // We do so because basic hovering states indicate a feature is interactive.\n // (e.g. if there's a click handler, we'll still apply basic hover states, even if we don't fire hover events)\n const firstHandler = this.findHandlers(\n ['hover', 'hover-move', 'long-hover', 'click', 'contextmenu'],\n hoveredTopFeature?.source,\n hoveredTopFeature?.layer.id,\n )?.[0];\n\n this.hoveringSourceWithLayers = this.sourceWithLayersFor(hoveredTopFeature?.source);\n\n // Resolved lazily so the `hover` branch picks up the marker-added spread that\n // `updateEventState` writes back to `shownFeatures` (matches the click path's\n // dispatch order).\n let callerFeatures: Feature[] | undefined;\n\n if (hoverChanged) {\n this.updateHoverCursor(firstHandler?.config);\n\n updateEventState(\n 'hover',\n this.hoveringFeature,\n prevHoveredFeature,\n this.hoveringSourceWithLayers,\n prevHoveredSourceWithLayers,\n );\n\n const hoverHandlers = this.findHandlers(\n ['hover'],\n hoveredTopFeature?.source,\n hoveredTopFeature?.layer.id,\n );\n\n // Only build `allEventFeatures` (scope + dedupe + substitute) when a handler\n // will read it — hover state above is tracked for the cursor/eventState even\n // without hover handlers, so a click-only module pays nothing here.\n if (hoverHandlers.length) {\n callerFeatures = this.toCallerFeatures(this.hoveringFeatures, hoveredTopFeature?.source);\n for (const handler of hoverHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n\n if (mouseInMotionOverHoveredFeature) {\n const hoverMoveHandlers = this.findHandlers(\n ['hover-move'],\n this.hoveringFeature?.source,\n this.hoveringFeature?.layer.id,\n );\n\n if (hoverMoveHandlers.length) {\n callerFeatures ??= this.toCallerFeatures(this.hoveringFeatures, this.hoveringFeature?.source);\n for (const handler of hoverMoveHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n\n this.restartLongHoverTimeout();\n }\n }\n\n private updateHoverCursor(config: EventHandlerConfig | undefined) {\n if (this.hoveringFeature) {\n this.mapCanvas.style.cursor = config?.cursorOnHover ?? this.config.cursorOnHover;\n } else {\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n }\n }\n\n private onMapClick(clickType: ClickEventType, ev: MapMouseEvent) {\n if (!this.isEnabled()) {\n // We avoid any accidental click handling while disabled or the map moves\n return;\n }\n\n const clickedFeatures = this.getRenderedFeatures(ev.point);\n\n const prevClickedFeature = this.lastClickedFeature;\n this.lastClickedFeature = clickedFeatures[0];\n const prevClickedSourceWithLayers = this.lastClickedSourceWithLayers;\n const clickHandlers = this.findHandlers(\n [clickType],\n this.lastClickedFeature?.source,\n this.lastClickedFeature?.layer.id,\n );\n\n // Resolve from the firing click handler, NOT sourceWithLayersFor(source): the high-priority\n // `click` eventState must only be written when this module actually handles clicks. Otherwise\n // clicking a feature of a hover-only module would stick a `click` marker that later hovers\n // (lower priority) can't clear, freezing its highlight.\n this.lastClickedSourceWithLayers = clickHandlers[0]?.sourceWithLayers;\n\n updateEventState(\n clickType,\n this.lastClickedFeature,\n prevClickedFeature,\n this.lastClickedSourceWithLayers,\n prevClickedSourceWithLayers,\n );\n\n // Only build `allEventFeatures` when a handler will read it. onMapClick fires on every\n // map click (no source pre-filter), so a click on empty map or a feature whose module\n // has no click handler does no scope/dedupe/substitution work.\n if (clickHandlers.length) {\n const callerFeatures = this.toCallerFeatures(clickedFeatures, this.lastClickedFeature?.source);\n for (const handler of clickHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n}\n","/**\n * Key layer IDs from the vector map style for positioning custom layers.\n *\n * @remarks\n * This constant provides reference layer IDs from TomTom's vector map styles that are used\n * as anchors when positioning custom layers in the map's layer stack. By referencing these\n * layer IDs, SDK modules and custom implementations can control where their layers appear\n * in the rendering order (visual stacking).\n *\n * **Understanding Layer Order:**\n *\n * \"Lowest\" refers to position in the layer stack, not physical elevation. Layers are rendered\n * from bottom to top - layers lower in the stack are drawn first and appear underneath layers\n * higher in the stack. When you add a layer \"before\" a reference layer, it's inserted lower\n * in the stack and will be drawn underneath that reference layer.\n *\n * **How SDK Modules Use These IDs:**\n *\n * **RoutingModule** - Positions route visualizations below labels for readability:\n * ```typescript\n * // Routes appear below labels but above the base map\n * const routingModule = await RoutingModule.get(map);\n * // Uses mapStyleLayerIDs.lowestLabel by default\n * ```\n *\n * **GeometriesModule** - Configurable layer positioning for polygon areas:\n * ```typescript\n * // Default: below labels\n * const geometriesModule = await GeometriesModule.get(map);\n *\n * // Custom: below buildings to show at ground level\n * const geometriesModule = await GeometriesModule.get(map, {\n * beforeLayerConfig: 'lowestBuilding'\n * });\n *\n * // On top of everything\n * const geometriesModule = await GeometriesModule.get(map, {\n * beforeLayerConfig: 'top'\n * });\n * ```\n *\n * **Custom Layer Implementation:**\n * ```typescript\n * // Add a highlight layer below labels\n * map.addLayer({\n * id: 'search-results',\n * type: 'fill',\n * source: 'results-source',\n * paint: { 'fill-color': '#ffcc00', 'fill-opacity': 0.4 }\n * }, mapStyleLayerIDs.lowestLabel);\n * ```\n *\n * **Style Persistence:**\n *\n * These layer IDs remain consistent across TomTom map style changes (e.g., switching from\n * 'standardLight' to 'standardDark'). The SDK automatically repositions module layers using\n * the corresponding reference layers in the new style, maintaining the intended visual hierarchy.\n *\n * **Important Notes:**\n * - `lowestBuilding` is not available in the Satellite style\n * - If a reference layer doesn't exist, custom layers will be added on top of the style\n * - Both RoutingModule and GeometriesModule default to `lowestLabel` for optimal visibility\n *\n * @see {@link GeometryBeforeLayerConfig} for GeometriesModule layer positioning options\n * @see {@link RouteLayersConfig} for RoutingModule layer configuration\n *\n * @group Map Style\n */\nexport const mapStyleLayerIDs = {\n /**\n * Country name label layer.\n *\n * @remarks\n * Lower in the stack than most labels. Use to position elements below country\n * labels but above other map features.\n */\n country: 'Places - Country name',\n /**\n * The lowest layer for place labels.\n *\n * @remarks\n * Lower in the stack than city labels. Use to position content below all place\n * name labels while keeping it visible above geographic features.\n */\n lowestPlaceLabel: 'Places - Village / Hamlet',\n /**\n * Points of Interest layer.\n *\n * @remarks\n * Use to position custom content in the layer stack relative to POI icons and labels.\n */\n poi: 'POI',\n /**\n * The lowest labels layer in the stack.\n *\n * @remarks\n * **Most commonly used reference layer.** Positioning layers before this ensures\n * content appears below all text labels, maintaining map readability while keeping\n * visualizations prominent.\n *\n * **Default for SDK modules:**\n * - RoutingModule uses this for route lines and waypoints\n * - GeometriesModule uses this as the default for polygon fills and borders\n *\n * This provides optimal balance between visibility and not obscuring map labels.\n */\n lowestLabel: 'Borders - Treaty label',\n /**\n * The lowest road line layer in the stack.\n *\n * @remarks\n * Represents tunnel railway outlines. Use to position content below the road network,\n * useful for showing base layers or features that should appear beneath transportation\n * infrastructure.\n */\n lowestRoadLine: 'Tunnel - Railway outline',\n /**\n * The lowest building layer in the stack.\n *\n * @remarks\n * Use to position content below 3D building extrusions while keeping it above\n * flat map features like roads and terrain.\n *\n * **Note:** Not available in the Satellite style.\n */\n lowestBuilding: 'Buildings - Underground',\n} as const;\n","/**\n * Source identifier for POI (Point of Interest) vector tiles.\n *\n * @remarks\n * Used to reference the POI layer in the map style, which contains\n * business locations, landmarks, and other points of interest.\n *\n * @group POIs\n */\nexport const POI_SOURCE_ID = 'vectorTiles';\n\n/**\n * Source identifier for hillshade terrain visualization.\n *\n * @remarks\n * References the raster source that provides terrain shading to visualize\n * elevation and topography on the map.\n *\n * @group Hillshade\n */\nexport const HILLSHADE_SOURCE_ID = 'hillshade';\n\n/**\n * Source identifier for base map vector tiles.\n *\n * @remarks\n * References the primary vector tile source containing roads, buildings,\n * land use, water bodies, and other fundamental map features.\n *\n * @group Base Map\n */\nexport const BASE_MAP_SOURCE_ID = 'vectorTiles';\n\n/**\n * Source identifier for traffic incidents vector tiles.\n *\n * @remarks\n * References the vector tile source containing real-time traffic incident data\n * such as accidents, road closures, and construction.\n *\n * @group Traffic Incidents\n */\nexport const TRAFFIC_INCIDENTS_SOURCE_ID = 'vectorTilesIncidents';\n\n/**\n * Source identifier for traffic flow vector tiles.\n *\n * @remarks\n * References the vector tile source containing real-time traffic flow data\n * showing current traffic speeds and congestion levels.\n *\n * @group Traffic Flow\n */\nexport const TRAFFIC_FLOW_SOURCE_ID = 'vectorTilesFlow';\n","/**\n * Lifecycle event module for map modules.\n *\n * Provides a unified API for subscribing to module-level events that are not tied\n * to user interaction with map features. Two event types are supported:\n *\n * - `config-change`: emitted whenever the module's configuration is updated\n * (via `applyConfig`, `resetConfig`, or any module-specific setter that mutates config).\n * - `shown-features`: emitted by modules that have a `show` method, immediately after\n * features are displayed on the map.\n *\n * Each `on()` call returns an unsubscribe function so individual handlers can be\n * removed without clearing the entire listener list.\n *\n * @typeParam TConfig - The module's configuration type.\n * @typeParam TShownFeatures - The type of data passed to `show()`. Use `never` for\n * modules that have no `show` method.\n *\n * @example\n * ```typescript\n * const unsubscribeConfig = trafficFlow.events.on('config-change', (config) => {\n * console.log('New config:', config);\n * });\n *\n * const unsubscribeShown = places.events.on('shown-features', (features) => {\n * console.log('Shown:', features);\n * });\n *\n * // Later:\n * unsubscribeConfig();\n * unsubscribeShown();\n * ```\n *\n * @group Events\n */\nexport class ModuleEvents<TConfig, TShownFeatures = never> {\n constructor(\n private readonly configChangeHandlers: ((config: TConfig | undefined) => void)[],\n private readonly shownFeaturesHandlers: ((features: TShownFeatures) => void)[],\n ) {}\n\n /**\n * Subscribe to config-change events.\n *\n * The handler is called with the module's full current configuration immediately after\n * any mutation — whether through `applyConfig`, a module-specific setter, or `resetConfig`.\n *\n * @param type - `'config-change'`\n * @param handler - Receives the module's config (or `undefined` when config is cleared).\n * @returns An unsubscribe function. Call it to remove **only this handler** without\n * affecting other registered handlers for the same event type.\n *\n * @example\n * ```typescript\n * const unsub = trafficFlow.events.on('config-change', (config) => {\n * toggle.checked = config?.visible ?? false;\n * });\n *\n * // Later:\n * unsub();\n * ```\n */\n on(type: 'config-change', handler: (config: TConfig | undefined) => void): () => void;\n /**\n * Subscribe to shown-features events.\n *\n * The handler is called immediately after a module's `show()` method (or equivalent)\n * has updated the map with new data. Only available on modules that have a `show` method.\n *\n * @param type - `'shown-features'`\n * @param handler - Receives the data that was passed to `show()`.\n * @returns An unsubscribe function. Call it to remove **only this handler** without\n * affecting other registered handlers for the same event type.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('shown-features', (features) => {\n * fitMapToResults(features);\n * });\n *\n * // Later:\n * unsub();\n * ```\n */\n on(type: 'shown-features', handler: (features: TShownFeatures) => void): () => void;\n on(\n type: 'config-change' | 'shown-features',\n handler: ((config: TConfig | undefined) => void) | ((features: TShownFeatures) => void),\n ): () => void {\n if (type === 'config-change') {\n const configHandler = handler as (config: TConfig | undefined) => void;\n this.configChangeHandlers.push(configHandler);\n return () => {\n const index = this.configChangeHandlers.indexOf(configHandler);\n if (index !== -1) {\n this.configChangeHandlers.splice(index, 1);\n }\n };\n }\n\n const shownHandler = handler as (features: TShownFeatures) => void;\n this.shownFeaturesHandlers.push(shownHandler);\n return () => {\n const index = this.shownFeaturesHandlers.indexOf(shownHandler);\n if (index !== -1) {\n this.shownFeaturesHandlers.splice(index, 1);\n }\n };\n }\n\n /**\n * Remove **all** handlers for the given event type at once.\n *\n * Prefer the unsubscribe function returned by {@link on} when you need to remove a\n * single handler. Use `off` for bulk teardown — e.g. when unmounting a component that\n * registered multiple handlers.\n *\n * @param type - The event type whose handlers should all be cleared.\n *\n * @example\n * ```typescript\n * // Clear all config-change listeners in one call\n * trafficFlow.events.off('config-change');\n * ```\n */\n off(type: 'config-change' | 'shown-features'): void {\n if (type === 'config-change') {\n this.configChangeHandlers.length = 0;\n } else {\n this.shownFeaturesHandlers.length = 0;\n }\n }\n}\n","import type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { EventsProxy } from './EventsProxy';\nimport type { EventHandlerConfig, EventType, SourceWithLayers, UserEventHandler } from './types';\n\n/**\n * Event handling interface for map features.\n *\n * Provides a simple API for attaching and removing event handlers for user interactions\n * with map features such as clicks, hovers, and context menus. Each map module (POIs, Routing,\n * Places, etc.) exposes an `events` property that returns a `UserEvents` instance.\n *\n * @typeParam T - The feature type returned in event handlers (extends MapGeoJSONFeature)\n *\n * @remarks\n * **Supported Event Types:**\n * - `click`: User clicks/taps on a feature\n * - `contextmenu`: User right-clicks on a feature (or long-press on mobile)\n * - `hover`: Mouse enters a feature\n * - `long-hover`: Mouse hovers over feature for configured duration (300-800ms)\n *\n * **Event Handler Signature:**\n * ```typescript\n * (feature: T, lngLat: LngLat, features: T[]) => void\n * ```\n *\n * **Parameters:**\n * - `feature`: The primary feature under the cursor\n * - `lngLat`: Geographic coordinates of the event\n * - `features`: All features at this location (when multiple overlap)\n *\n * **Key Features:**\n * - Automatic cursor management (pointer on hover)\n * - Smart event handling for overlapping features\n * - Configurable hover delays\n * - Memory-safe cleanup when removing handlers\n *\n * @example\n * ```typescript\n * // POI click handler\n * const pois = map.pois();\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('Clicked POI:', feature.properties.name);\n * console.log('Location:', lngLat);\n * showInfoWindow(feature.properties);\n * });\n *\n * // Route waypoint hover\n * const routing = map.routing();\n * routing.events.waypoints.on('hover', (waypoint) => {\n * showTooltip(`Waypoint ${waypoint.properties.index}`);\n * });\n *\n * // Long-hover for detailed info\n * pois.events.on('long-hover', (feature) => {\n * loadAndShowDetailedInfo(feature.properties.id);\n * });\n *\n * // Context menu (right-click)\n * routing.events.mainLines.on('contextmenu', (route, lngLat) => {\n * showContextMenu(lngLat, [\n * { label: 'Add waypoint here', action: () => addWaypoint(lngLat) },\n * { label: 'View route details', action: () => showDetails(route) }\n * ]);\n * });\n *\n * // Remove all click handlers\n * pois.events.off('click');\n * ```\n *\n * @example\n * ```typescript\n * // Complete interaction example\n * const places = map.places();\n *\n * // Show name on hover\n * places.events.on('hover', (place) => {\n * tooltip.show(place.properties.name);\n * });\n *\n * // Show details on click\n * places.events.on('click', (place, lngLat) => {\n * sidebar.show({\n * title: place.properties.name,\n * address: place.properties.address.freeformAddress,\n * coordinates: lngLat\n * });\n * });\n *\n * // Cleanup on component unmount\n * onUnmount(() => {\n * places.events.off('hover');\n * places.events.off('click');\n * });\n * ```\n *\n * @group Events\n */\nexport class UserEvents<T = MapGeoJSONFeature> {\n private readonly sources: SourceWithLayers[];\n\n constructor(\n private readonly eventProxy: EventsProxy,\n /**\n * The source(s) whose features this module handles events for.\n * Pass an array to register handlers across multiple sources representing similar data in different display modes (e.g. hexgrid + square\n * in {@link TrafficAreaAnalyticsModule}). A single value is also accepted for\n * backwards compatibility.\n */\n sourcesWithLayers: SourceWithLayers | SourceWithLayers[],\n private readonly config: EventHandlerConfig | undefined,\n /**\n * Optional function that transforms a raw {@link MapGeoJSONFeature} into the\n * feature type `T` exposed by the module. When provided, every feature passed (fired)\n * to event handlers is first run through this function, so callers receive the\n * module's own feature shape instead of the underlying MapLibre feature.\n *\n * When omitted, `T` defaults to `MapGeoJSONFeature` and features are forwarded\n * as-is.\n */\n private readonly mapping?: (feature: MapGeoJSONFeature) => T,\n ) {\n this.sources = Array.isArray(sourcesWithLayers) ? sourcesWithLayers : [sourcesWithLayers];\n }\n\n /**\n * Register an event handler for user interactions with map features.\n *\n * Attaches a callback function that will be invoked when users interact with\n * features in this module (e.g., clicking on a POI, hovering over a route).\n *\n * @param type The type of event to listen for (click, contextmenu, hover, long-hover)\n * @param handler Callback function invoked when the event occurs\n *\n * @returns An unsubscribe function that removes the handler registered by this call.\n *\n * @remarks\n * **Handler Parameters:**\n * - `feature`: The primary feature that triggered the event\n * - `lngLat`: Geographic coordinates [longitude, latitude] of the event\n * - `features`: The features from this module at the event location, de-duplicated (for\n * overlapping features within the module). To inspect features across all modules, query\n * `map.mapLibreMap.queryRenderedFeatures(...)` directly — see {@link UserEventHandler}.\n *\n * **Behavior:**\n * - Multiple handlers per event type are supported — each `on()` adds a handler (it does\n * **not** replace existing ones) and all of them fire. To remove one, call the unsubscribe\n * this returns; to clear them all, use `off(type)`.\n * - Handlers are preserved across map style changes\n * - Cursor automatically changes to pointer on hover\n * - Events respect module visibility (hidden features don't trigger events)\n *\n * **Performance:**\n * - Hover events use spatial indexing for fast lookup\n * - Long-hover has configurable delay to prevent accidental triggers\n * - Event handlers should be lightweight to maintain smooth interaction\n *\n * @example\n * ```typescript\n * // Basic click handler\n * module.events.on('click', (feature, lngLat, features) => {\n * console.log('Clicked feature:', feature.properties);\n * console.log('Location:', lngLat);\n * console.log('All features here:', features.length);\n * });\n *\n * // Hover with tooltip\n * module.events.on('hover', (feature) => {\n * const name = feature.properties.name || 'Unnamed';\n * tooltip.show(name);\n * });\n *\n * // Long-hover for detailed preview\n * module.events.on('long-hover', (feature) => {\n * // Only triggered after hovering for 300-800ms\n * loadPreview(feature.properties.id);\n * });\n *\n * // Right-click context menu\n * module.events.on('contextmenu', (feature, lngLat) => {\n * event.preventDefault(); // Prevent browser context menu\n * showCustomMenu(lngLat, feature);\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Route-specific handlers\n * const routing = map.routing();\n *\n * // Handle route line clicks\n * routing.events.mainLines.on('click', (route) => {\n * highlightRoute(route.properties.routeID);\n * showRouteSummary(route.properties.summary);\n * });\n *\n * // Handle waypoint interactions\n * routing.events.waypoints.on('hover', (waypoint) => {\n * const index = waypoint.properties.index;\n * showTooltip(`Stop ${index + 1}`);\n * });\n * ```\n */\n on(type: EventType, handler: UserEventHandler<T>): () => void {\n const entries: Array<{ source: SourceWithLayers; registeredHandler: UserEventHandler<any> }> = [];\n\n if (this.mapping) {\n const mapping = this.mapping;\n for (const source of this.sources) {\n const wrappedHandler: UserEventHandler<any> = (feature, ...rest) => handler(mapping(feature), ...rest);\n this.eventProxy.addEventHandler(source, wrappedHandler, type, this.config);\n entries.push({ source, registeredHandler: wrappedHandler });\n }\n } else {\n for (const source of this.sources) {\n this.eventProxy.addEventHandler(source, handler, type, this.config);\n entries.push({ source, registeredHandler: handler });\n }\n }\n\n return () => {\n for (const { source, registeredHandler } of entries) {\n this.eventProxy.removeHandler(source, type, registeredHandler);\n }\n };\n }\n\n /**\n * Remove all event handlers for a specific event type.\n *\n * Unregisters the callback function for the specified event type, stopping\n * further event notifications. This is important for cleanup to prevent\n * memory leaks and unwanted behavior.\n *\n * @param type The type of event to stop listening for\n *\n * @remarks\n * **Cleanup Behavior:**\n * - Removes **all** handlers for the specified event type — to remove a single handler, call\n * the unsubscribe function returned by {@link on}\n * - Other event types remain active\n * - Resets cursor behavior for this module\n * - Safe to call multiple times (no error if no handler exists)\n * - Does not affect other modules' event handlers\n *\n * **When to Use:**\n * - Component unmounting/cleanup\n * - Switching between interaction modes\n * - Temporarily disabling interactions\n * - Replacing an existing handler (call `off()` then `on()`)\n *\n * **Best Practices:**\n * - Always clean up event handlers when component unmounts\n * - Remove handlers before removing features from the map\n * - Use framework lifecycle hooks for automatic cleanup\n *\n * @example\n * ```typescript\n * // Remove click handlers\n * module.events.off('click');\n *\n * // Remove all handlers\n * module.events.off('click');\n * module.events.off('hover');\n * module.events.off('long-hover');\n * module.events.off('contextmenu');\n * ```\n *\n * @example\n * ```typescript\n * // React component cleanup\n * useEffect(() => {\n * const pois = map.pois();\n *\n * pois.events.on('click', handlePoiClick);\n * pois.events.on('hover', handlePoiHover);\n *\n * return () => {\n * // Cleanup on unmount\n * pois.events.off('click');\n * pois.events.off('hover');\n * };\n * }, [map]);\n * ```\n *\n * @example\n * ```typescript\n * // Replace handler\n * module.events.off('click'); // Remove old handler\n * module.events.on('click', newHandler); // Add new handler\n *\n * // Disable interactions temporarily\n * const savedHandler = currentHandler;\n * module.events.off('click'); // Disable\n * // ... later ...\n * module.events.on('click', savedHandler); // Re-enable\n * ```\n */\n off(type: EventType) {\n for (const source of this.sources) {\n this.eventProxy.remove(source, type);\n }\n }\n}\n","/**\n * Layer IDs for POIs on the map.\n * @ignore\n */\nexport const poiLayerIDs = ['POI', 'POI - Micro'];\n","import type { POICategory } from '@tomtom-org/maps-sdk/core';\n\n// mappings based on https://docs.tomtom.com/map-display-api/documentation/tomtom-orbis-maps/vector/content#poi\n\n/**\n * POI category mappings that have a direct equivalent in Map Display POI categories.\n * @ignore\n */\nexport const mapDisplayPoiCategoryMappings: Partial<Record<POICategory, string>> = {\n NATIVE_RESERVATION: 'aboriginal_land',\n AIRPORT: 'airport',\n AMUSEMENT_PARK: 'amusement_park',\n ATM: 'atm',\n BANK: 'bank',\n BAR: 'bar',\n BEACH: 'beach_resort',\n ACCESS_GATEWAY: 'border_control',\n FRONTIER_CROSSING: 'border_control',\n BUS_STOP: 'bus_station',\n CAFE: 'cafe',\n CAFE_PUB: 'cafe',\n CAMPING_GROUND: 'camping_ground',\n RENT_A_CAR_FACILITY: 'car_rental',\n CASINO: 'casino_and_gambling',\n CEMETERY: 'cemetery',\n ELECTRIC_VEHICLE_STATION: 'charging_location',\n CINEMA: 'cinema',\n MOVIE_THEATER: 'cinema',\n HEALTH_CARE_SERVICE: 'clinic',\n CLOTHING_SHOP: 'clothing_shop',\n COFFEE_SHOP: 'coffee_shop',\n COLLEGE_OR_UNIVERSITY: 'college_or_university',\n COLLEGE_UNIVERSITY: 'college_or_university',\n AGRICULTURE: 'commercial_area',\n BUSINESS_PARK: 'commercial_area',\n COMMERCIAL_BUILDING: 'commercial_area',\n COMPANY: 'commercial_area',\n RESIDENTIAL_ACCOMMODATION: 'commercial_area',\n EXHIBITION_CONVENTION_CENTER: 'conference_center',\n CLUB_ASSOCIATION: 'consumer_service',\n PUBLIC_AMENITY: 'consumer_service',\n WELFARE_ORGANIZATION: 'consumer_service',\n COURTHOUSE: 'courthouse',\n COMMUNITY_CENTER: 'cultural_center',\n CULTURAL_CENTER: 'cultural_center',\n DENTIST: 'dentist',\n DEPARTMENT_STORE: 'department_store',\n DOCTOR: 'doctor',\n EMERGENCY_MEDICAL_SERVICE: 'emergency_ward_entrance',\n EMERGENCY_ROOM: 'emergency_ward_entrance',\n FAST_FOOD: 'fast_food',\n FERRY_TERMINAL: 'ferry_terminal',\n FIRE_STATION_BRIGADE: 'fire_station',\n FITNESS_CLUB_CENTER: 'fitness_center',\n WINERY: 'food_and_drinks_shop',\n CURRENCY_EXCHANGE: 'foreign_exchange',\n EXCHANGE: 'foreign_exchange',\n GAS_STATION: 'fuel_station',\n GOLD_EXCHANGE: 'gold_exchange',\n GOLF_COURSE: 'golf_course',\n EMBASSY: 'government_office',\n GOVERNMENT_OFFICE: 'government_office',\n HELIPAD_HELICOPTER_LANDING: 'heliport_or_helipad',\n HISTORIC_SITE: 'historic_site',\n HOLIDAY_RENTAL: 'holiday_home',\n GENERAL_HOSPITAL_AND_POLYCLINIC: 'hospital',\n HOSPITAL: 'hospital',\n HOTEL_MOTEL: 'hotel_or_motel',\n ICE_SKATING_RINK: 'ice_rink',\n INDUSTRIAL_BUILDING: 'industrial_area',\n PORT_WAREHOUSE_FACILITY: 'industrial_area',\n PRIMARY_RESOURCE_UTILITY: 'industrial_area',\n GEOGRAPHIC_FEATURE: 'landmark',\n SHOPPING_CENTER: 'mall',\n MANUFACTURING_FACILITY: 'manufacturing_facility',\n MARINA: 'marina',\n MARKET: 'marketplace',\n MEDIA_FACILITY: 'media_facility',\n MOUNTAIN_PASS: 'mountain_pass',\n MUSEUM: 'museum',\n NON_GOVERNMENTAL_ORGANIZATION: 'ngo_office',\n CONCERT_HALL: 'nightlife',\n ENTERTAINMENT: 'nightlife',\n NIGHTLIFE: 'nightlife',\n PARK_RECREATION_AREA: 'park_and_recreation_area',\n TRAIL_SYSTEM: 'park_and_recreation_area',\n LEISURE_CENTER: 'park_and_recreation_facility',\n WATER_SPORT: 'park_and_recreation_facility',\n OPEN_PARKING_AREA: 'parking_facility',\n PARKING_GARAGE: 'parking_facility',\n PHARMACY: 'pharmacy',\n POLICE_STATION: 'police_station',\n POST_OFFICE: 'post_office',\n PRISON_CORRECTIONAL_FACILITY: 'prison',\n PROTECTED_AREA: 'protected_land',\n PUB: 'pub',\n LIBRARY: 'public_library',\n PUBLIC_TRANSPORT_STOP: 'public_transport_stop',\n RAILWAY_STATION: 'railway_station',\n CHURCH: 'religious_site',\n GURUDWARA: 'religious_site',\n MOSQUE: 'religious_site',\n PAGODA: 'religious_site',\n PLACE_OF_WORSHIP: 'religious_site',\n SYNAGOG: 'religious_site',\n TEMPLE: 'religious_site',\n RENT_A_CAR_PARKING: 'rental_car_parking',\n RESEARCH_FACILITY: 'research_facility',\n REST_AREA: 'rest_and_service_area',\n RESTAURANT: 'restaurant',\n RESTAURANT_AREA: 'restaurant',\n SCHOOL: 'school_or_kindergarten',\n SHOP: 'shop_or_store',\n ADVENTURE_SPORTS_FACILITY: 'sport_facility',\n SPORTS_CENTER: 'sport_facility',\n TENNIS_COURT: 'sport_facility',\n STADIUM: 'stadium',\n SUPERMARKETS_HYPERMARKETS: 'supermarket',\n SWIMMING_POOL: 'swimming_pool',\n TAXI_STAND: 'taxi_stand',\n OPERA_HOUSE: 'theater',\n THEATER: 'theater',\n TOLL_GATE: 'toll_booth',\n TOURIST_ATTRACTION: 'tourist_attraction',\n TOURIST_INFORMATION_OFFICE: 'tourist_information',\n TRUCK_STOP: 'truck_stop',\n AUTOMOTIVE_DEALER: 'vehicle_dealer',\n MOTORING_ORGANIZATION_OFFICE: 'vehicle_inspection',\n TRAFFIC_CONTROL_DEPARTMENT: 'vehicle_inspection',\n TRANSPORT_AUTHORITY_VEHICLE_REGISTRATION: 'vehicle_inspection',\n REPAIR_FACILITY: 'vehicle_repair',\n CAR_WASH: 'vehicle_wash',\n VETERINARIAN: 'veterinary',\n HILL: 'viewpoint',\n MOUNTAIN_PEAK: 'viewpoint',\n SCENIC_PANORAMIC_VIEW: 'viewpoint',\n WEIGH_STATION: 'weighbridge',\n AQUATIC_ZOO: 'zoo_or_aquarium',\n ARBORETA_BOTANICAL_GARDENS: 'zoo_or_aquarium',\n ZOOS_ARBORETA_BOTANICAL_GARDEN: 'zoo_or_aquarium',\n} as const;\n\n/**\n * Complete POI category mappings including all POI categories.\n * Extends the original mappings with additional categories mapped to semantically appropriate values.\n * @ignore\n */\nconst completeMapDisplayPoiCategoryMappings: Record<POICategory, string> = {\n ...(mapDisplayPoiCategoryMappings as Record<POICategory, string>),\n // Subtypes and additional categories\n AIRFIELD: 'airport',\n AIRLINE_ACCESS: 'airport',\n PRIVATE_AIRPORT: 'airport',\n PUBLIC_AIRPORT: 'airport',\n ANIMAL_SHELTER: 'animal_shelter',\n DIVERSIFIED_FINANCIALS: 'bank',\n COCKTAIL_BAR: 'bar',\n BEACH_CLUB: 'beach_resort',\n SECURED_ENTRANCE: 'border_control',\n INTERNET_CAFE: 'cafe',\n CAFETERIA: 'cafeteria',\n CARAVAN_SITE: 'camping_ground',\n RECREATIONAL_CAMPING_GROUND: 'camping_ground',\n REST_CAMP: 'camping_ground',\n MAUSOLEUM_GRAVE: 'cemetery',\n CHILD_CARE_FACILITY: 'childcare',\n PRE_SCHOOL: 'childcare',\n DRIVE_IN_MOVIES: 'cinema',\n CHILDRENS_CLOTHES: 'clothing_shop',\n MENS_CLOTHING: 'clothing_shop',\n SPECIALTY_CLOTHING_SHOP: 'clothing_shop',\n WOMENS_CLOTHING: 'clothing_shop',\n JUNIOR_COLLEGE_COMMUNITY_COLLEGE: 'college_or_university',\n COMEDY_CLUB: 'comedy_club',\n ADVERTISING_COMPANY: 'commercial_area',\n AGRICULTURAL_TECHNOLOGY: 'commercial_area',\n AIRLINE_COMPANY: 'commercial_area',\n APARTMENT_RENTAL: 'commercial_area',\n AUTOMOBILE_COMPANY: 'commercial_area',\n BUILDING: 'commercial_area',\n BUSINESS_SERVICES: 'commercial_area',\n BUS_CHARTER_COMPANY: 'commercial_area',\n CABLE_TELEPHONE_COMPANY: 'commercial_area',\n CLEANING_SERVICES: 'commercial_area',\n COMPUTER_DATA_SERVICES: 'commercial_area',\n CONSTRUCTION_COMPANY: 'commercial_area',\n DELIVERY_SERVICE: 'commercial_area',\n ELECTRONICS_COMPANY: 'commercial_area',\n EQUIPMENT_RENTAL: 'commercial_area',\n FARM: 'commercial_area',\n FLATS_APARTMENT_COMPLEX: 'commercial_area',\n HORTICULTURE: 'commercial_area',\n IMPORT_EXPORT_AND_DISTRIBUTION: 'commercial_area',\n INSURANCE_COMPANY: 'commercial_area',\n INVESTMENT_ADVISOR: 'commercial_area',\n LEGAL_SERVICES: 'commercial_area',\n MINING_COMPANY: 'commercial_area',\n MOVING_STORAGE_COMPANY: 'commercial_area',\n OIL_NATURAL_GAS: 'commercial_area',\n PHARMACEUTICAL_COMPANY: 'commercial_area',\n PRIMARY_PRODUCER: 'commercial_area',\n PUBLISHING_TECHNOLOGIES: 'commercial_area',\n REAL_ESTATE_AGENT: 'commercial_area',\n REAL_ESTATE_COMPANY: 'commercial_area',\n RETIREMENT_COMMUNITY: 'commercial_area',\n SERVICE_COMPANY: 'commercial_area',\n SOFTWARE_COMPANY: 'commercial_area',\n TAX_SERVICES: 'commercial_area',\n TELECOMMUNICATIONS: 'commercial_area',\n TRANSPORT_COMPANY: 'commercial_area',\n TRAVEL_AGENT: 'commercial_area',\n WEDDING_SERVICES: 'commercial_area',\n PUBLIC_CALL_BOX: 'consumer_service',\n PUBLIC_TOILET: 'consumer_service',\n GENERAL_PRACTITIONER: 'doctor',\n SPECIALIST: 'doctor',\n AMBULANCE_UNIT: 'emergency_ward_entrance',\n ROAD_RESCUE: 'emergency_ward_entrance',\n MICROBREWERY: 'food_and_drinks_shop',\n BATTLEFIELD: 'historic_site',\n MEMORIAL: 'historic_site',\n BUNGALOW_RENTAL: 'holiday_home',\n CHALET_RENTAL: 'holiday_home',\n COTTAGE_RENTAL: 'holiday_home',\n VILLA_RENTAL: 'holiday_home',\n HORSE_RIDING_CENTER: 'horse_riding',\n BLOOD_BANK: 'hospital',\n HOSPITAL_FOR_WOMEN_AND_CHILDREN: 'hospital',\n HOSPITAL_OF_CHINESE_MEDICINE: 'hospital',\n SPECIAL_HOSPITAL: 'hospital',\n B_B_GUEST_HOUSE: 'hotel_or_motel',\n CABINS_LODGES: 'hotel_or_motel',\n HOSTEL: 'hotel_or_motel',\n HOTEL: 'hotel_or_motel',\n MOTEL: 'hotel_or_motel',\n RESORT: 'hotel_or_motel',\n ICE_CREAM_PARLOR: 'ice_cream_shop',\n ICE_HOCKEY_ARENA: 'ice_rink',\n CHEMICAL_COMPANY: 'industrial_area',\n MECHANICAL_ENGINEERING: 'industrial_area',\n QUARRY: 'industrial_area',\n YOGURT_JUICE_BAR: 'juice_bar',\n ARCH: 'landmark',\n BAY: 'landmark',\n BRIDGE: 'landmark',\n CAPE: 'landmark',\n COVE: 'landmark',\n DAM: 'landmark',\n DUNE: 'landmark',\n ISLAND: 'landmark',\n LOCALE: 'landmark',\n MARSH: 'landmark',\n MONUMENT: 'landmark',\n OASIS: 'landmark',\n OBSERVATORY: 'landmark',\n PARKWAY: 'landmark',\n PLAIN_FLAT: 'landmark',\n PLATEAU: 'landmark',\n RAPIDS: 'landmark',\n REEF: 'landmark',\n RESERVOIR: 'landmark',\n RIVER_CROSSING: 'landmark',\n ROCKS: 'landmark',\n STATUE: 'landmark',\n TOWER: 'landmark',\n TUNNEL: 'landmark',\n VALLEY: 'landmark',\n WATER_HOLE: 'landmark',\n WELL: 'landmark',\n AUTOMOBILE_MANUFACTURING: 'manufacturing_facility',\n MANUFACTURING_COMPANY: 'manufacturing_facility',\n FARMERS_MARKET: 'marketplace',\n FOOD_MARKET: 'marketplace',\n INFORMAL_MARKET: 'marketplace',\n PUBLIC_MARKET: 'marketplace',\n MILITARY_AIRPORT: 'military_airfield',\n FUNERAL_SERVICE_MORTUARIES: 'mourning_facility',\n AMUSEMENT_PLACE: 'nightlife',\n BETTING_STATION: 'nightlife',\n CABARET_THEATER: 'nightlife',\n DISCO_CLUB: 'nightlife',\n FAIRGROUND: 'nightlife',\n JAZZ_CLUB: 'nightlife',\n KARAOKE_CLUB: 'nightlife',\n MUSIC_CENTER: 'nightlife',\n ADVENTURE_VEHICLE_TRAIL: 'park_and_recreation_area',\n FISHING_HUNTING_AREA: 'park_and_recreation_area',\n FOREST_AREA: 'park_and_recreation_area',\n HIKING_TRAIL: 'park_and_recreation_area',\n HISTORICAL_PARK: 'park_and_recreation_area',\n HORSE_RIDING_TRAIL: 'park_and_recreation_area',\n MOUNTAIN_BIKE_TRAIL: 'park_and_recreation_area',\n PARK: 'park_and_recreation_area',\n PICNIC_AREA: 'park_and_recreation_area',\n RECREATION_AREA: 'park_and_recreation_area',\n ROCK_CLIMBING_TRAIL: 'park_and_recreation_area',\n WILDERNESS_AREA: 'park_and_recreation_area',\n OPEN_CAR_PARKING_AREA: 'parking_facility',\n BEAUTY_SALON: 'personal_care_service',\n HAIRDRESSER: 'personal_care_service',\n NAIL_SALON: 'personal_care_service',\n PERSONAL_CARE_FACILITY: 'personal_care_service',\n PERSONAL_SERVICE: 'personal_care_service',\n SAUNA_SOLARIUM_MASSAGE: 'personal_care_service',\n DRUG_STORE: 'pharmacy',\n MARIJUANA_DISPENSARY: 'pharmacy',\n MEDICINAL_MARIJUANA_DISPENSARY: 'pharmacy',\n RECREATIONAL_MARIJUANA_DISPENSARY: 'pharmacy',\n PLANETARIUM: 'planetarium',\n COURIER_DROP_BOX: 'post_depot',\n BUS_LINES: 'public_transport_stop',\n PASSENGER_TRANSPORT_TICKET_OFFICE: 'public_transport_stop',\n STREETCAR_STOP: 'public_transport_stop',\n SUBWAY_STATION: 'public_transport_stop',\n INTERNATIONAL_RAILROAD_STATION: 'railway_station',\n NATIONAL_RAILROAD_STATION: 'railway_station',\n STATION_ACCESS: 'railway_station',\n URBAN_STATION: 'railway_station',\n AFGHAN_RESTAURANT: 'restaurant',\n AFRICAN_RESTAURANT: 'restaurant',\n AMERICAN_RESTAURANT: 'restaurant',\n ARABIAN_RESTAURANT: 'restaurant',\n ARMENIAN_RESTAURANT: 'restaurant',\n ASIAN_RESTAURANT: 'restaurant',\n AUSTRALIAN_RESTAURANT: 'restaurant',\n AUSTRIAN_RESTAURANT: 'restaurant',\n BANQUET_ROOMS: 'restaurant',\n BARBECUE_RESTAURANT: 'restaurant',\n BASQUE_RESTAURANT: 'restaurant',\n BELGIAN_RESTAURANT: 'restaurant',\n BISTRO: 'restaurant',\n BOLIVIAN_RESTAURANT: 'restaurant',\n BRAZILIAN_RESTAURANT: 'restaurant',\n BRITISH_RESTAURANT: 'restaurant',\n BUFFET_RESTAURANT: 'restaurant',\n BULGARIAN_RESTAURANT: 'restaurant',\n BURMESE_RESTAURANT: 'restaurant',\n CALIFORNIAN_RESTAURANT: 'restaurant',\n CAMBODIAN_RESTAURANT: 'restaurant',\n CANADIAN_RESTAURANT: 'restaurant',\n CARIBBEAN_RESTAURANT: 'restaurant',\n CATERING_SERVICES: 'restaurant',\n CHICKEN_RESTAURANT: 'restaurant',\n CHINESE_RESTAURANT: 'restaurant',\n COLOMBIAN_RESTAURANT: 'restaurant',\n CORSICAN_RESTAURANT: 'restaurant',\n CREOLE_RESTAURANT: 'restaurant',\n CREPERIE: 'restaurant',\n CUBAN_RESTAURANT: 'restaurant',\n CZECH_RESTAURANT: 'restaurant',\n DANISH_RESTAURANT: 'restaurant',\n DINNER_THEATER: 'restaurant',\n DONGBEI_RESTAURANT: 'restaurant',\n DOUGHNUT_RESTAURANT: 'restaurant',\n DUTCH_RESTAURANT: 'restaurant',\n EGYPTIAN_RESTAURANT: 'restaurant',\n ENGLISH_RESTAURANT: 'restaurant',\n ETHIOPIAN_RESTAURANT: 'restaurant',\n FINNISH_RESTAURANT: 'restaurant',\n FONDUE_RESTAURANT: 'restaurant',\n FRENCH_RESTAURANT: 'restaurant',\n FUSION_RESTAURANT: 'restaurant',\n GERMAN_RESTAURANT: 'restaurant',\n GREEK_RESTAURANT: 'restaurant',\n GRILL_RESTAURANT: 'restaurant',\n HAMBURGER_RESTAURANT: 'restaurant',\n HAWAIIAN_RESTAURANT: 'restaurant',\n HOT_POT_RESTAURANT: 'restaurant',\n HUNAN_RESTAURANT: 'restaurant',\n HUNGARIAN_RESTAURANT: 'restaurant',\n INDIAN_RESTAURANT: 'restaurant',\n INDONESIAN_RESTAURANT: 'restaurant',\n INTERNATIONAL_RESTAURANT: 'restaurant',\n IRANIAN_RESTAURANT: 'restaurant',\n IRISH_RESTAURANT: 'restaurant',\n ISRAELI_RESTAURANT: 'restaurant',\n ITALIAN_RESTAURANT: 'restaurant',\n JAMAICAN_RESTAURANT: 'restaurant',\n JAPANESE_RESTAURANT: 'restaurant',\n JEWISH_RESTAURANT: 'restaurant',\n KOREAN_RESTAURANT: 'restaurant',\n KOSHER_RESTAURANT: 'restaurant',\n LATIN_AMERICAN_RESTAURANT: 'restaurant',\n LEBANESE_RESTAURANT: 'restaurant',\n MEDITERRANEAN_RESTAURANT: 'restaurant',\n MEXICAN_RESTAURANT: 'restaurant',\n MIDDLE_EASTERN_RESTAURANT: 'restaurant',\n MONGOLIAN_RESTAURANT: 'restaurant',\n MOROCCAN_RESTAURANT: 'restaurant',\n NEPALESE_RESTAURANT: 'restaurant',\n NORWEGIAN_RESTAURANT: 'restaurant',\n ORGANIC_FOOD_RESTAURANT: 'restaurant',\n ORIENTAL_RESTAURANT: 'restaurant',\n PAKISTANI_RESTAURANT: 'restaurant',\n PERUVIAN_RESTAURANT: 'restaurant',\n PHILIPPINE_RESTAURANT: 'restaurant',\n PIZZERIA: 'restaurant',\n POLISH_RESTAURANT: 'restaurant',\n POLYNESIAN_RESTAURANT: 'restaurant',\n PORTUGUESE_RESTAURANT: 'restaurant',\n PUB_FOOD: 'restaurant',\n RUSSIAN_RESTAURANT: 'restaurant',\n SALAD_BAR: 'restaurant',\n SANDWICH_RESTAURANT: 'restaurant',\n SCANDINAVIAN_RESTAURANT: 'restaurant',\n SEAFOOD: 'restaurant',\n SHANDONG_RESTAURANT: 'restaurant',\n SICHUAN_RESTAURANT: 'restaurant',\n SNACKS_RESTAURANT: 'restaurant',\n SOUL_FOOD: 'restaurant',\n SOUP_RESTAURANT: 'restaurant',\n SPANISH_RESTAURANT: 'restaurant',\n STEAK_HOUSE: 'restaurant',\n SUSHI_RESTAURANT: 'restaurant',\n SWEDISH_RESTAURANT: 'restaurant',\n SWISS_RESTAURANT: 'restaurant',\n SYRIAN_RESTAURANT: 'restaurant',\n TAIWANESE_RESTAURANT: 'restaurant',\n TAKEOUT_FOOD: 'restaurant',\n TAPAS_RESTAURANT: 'restaurant',\n TEPPANYAKI_RESTAURANT: 'restaurant',\n THAI_RESTAURANT: 'restaurant',\n TIBETAN_RESTAURANT: 'restaurant',\n TUNISIAN_RESTAURANT: 'restaurant',\n TURKISH_RESTAURANT: 'restaurant',\n VEGETARIAN_RESTAURANT: 'restaurant',\n VENEZUELAN_RESTAURANT: 'restaurant',\n VIETNAMESE_RESTAURANT: 'restaurant',\n WESTERN_RESTAURANT: 'restaurant',\n ART_SCHOOL: 'school_or_kindergarten',\n DANCE_STUDIO_SCHOOL: 'school_or_kindergarten',\n DRIVING_SCHOOL: 'school_or_kindergarten',\n HIGH_SCHOOL: 'school_or_kindergarten',\n LANGUAGE_SCHOOL: 'school_or_kindergarten',\n MIDDLE_SCHOOL: 'school_or_kindergarten',\n PRIMARY_SCHOOL: 'school_or_kindergarten',\n SENIOR_HIGH_SCHOOL: 'school_or_kindergarten',\n VOCATIONAL_SCHOOL: 'school_or_kindergarten',\n AGRICULTURAL_SUPPLIES: 'shop_or_store',\n ANTIQUE_ART_SHOP: 'shop_or_store',\n BAGS_LEATHERWEAR: 'shop_or_store',\n BAKERY: 'shop_or_store',\n BEAUTY_SUPPLIES: 'shop_or_store',\n BOATING_EQUIPMENT_ACCESSORIES: 'shop_or_store',\n BOOK_SHOP: 'shop_or_store',\n BUTCHER: 'shop_or_store',\n CAMERAS_PHOTOGRAPHY: 'shop_or_store',\n CARPET_FLOOR_COVERINGS: 'shop_or_store',\n CHRISTMAS_HOLIDAY_SHOP: 'shop_or_store',\n COMPUTER_COMPUTER_SUPPLIES: 'shop_or_store',\n CONSTRUCTION_MATERIAL_EQUIPMENT: 'shop_or_store',\n CONSUMER_ELECTRONICS: 'shop_or_store',\n CONVENIENCE_STORE: 'shop_or_store',\n CURTAINS_TEXTILES: 'shop_or_store',\n C_DS_DVD_VIDEOS: 'shop_or_store',\n DELICATESSEN: 'shop_or_store',\n DO_IT_YOURSELF_CENTERS: 'shop_or_store',\n DRIVE_THROUGH_BOTTLE_SHOP: 'shop_or_store',\n DRY_CLEANER: 'shop_or_store',\n ELECTRICAL_APPLIANCES_SHOP: 'shop_or_store',\n FACTORY_OUTLET: 'shop_or_store',\n FISHMONGER: 'shop_or_store',\n FLORISTS: 'shop_or_store',\n FOOTWEAR_SHOE_REPAIRS: 'shop_or_store',\n FURNITURE_HOME_FURNISHINGS: 'shop_or_store',\n GARDEN_CENTERS_SERVICES: 'shop_or_store',\n GIFTS_CARDS_NOVELTIES_SOUVENIRS: 'shop_or_store',\n GLASSWARE_CERAMIC_SHOP: 'shop_or_store',\n GLASS_WINDOWS_STORE: 'shop_or_store',\n GREENGROCER: 'shop_or_store',\n GROCERY_STORE: 'shop_or_store',\n HARDWARE_STORE: 'shop_or_store',\n HOBBY_SHOP: 'shop_or_store',\n HOUSE_GARDEN_FURNITURE_FITTINGS: 'shop_or_store',\n JEWELRY_CLOCKS_WATCHES: 'shop_or_store',\n KITCHENS_BATHROOMS: 'shop_or_store',\n LAUNDRY: 'shop_or_store',\n LIGHTING_SHOPS: 'shop_or_store',\n LOCAL_SPECIALITIES_SHOP: 'shop_or_store',\n LOTTERY_SHOP: 'shop_or_store',\n MEDICAL_SUPPLIES_EQUIPMENT: 'shop_or_store',\n MOBILE_PHONE_SHOP: 'shop_or_store',\n MUSIC_INSTRUMENTS_STORE: 'shop_or_store',\n NEWSAGENTS_TOBACCONISTS: 'shop_or_store',\n OFFICE_EQUIPMENT: 'shop_or_store',\n OPTICIAN: 'shop_or_store',\n OTHER_FOOD_SHOPS: 'shop_or_store',\n PAINTING_DECORATING: 'shop_or_store',\n PAWN_SHOP: 'shop_or_store',\n PET_SUPPLIES: 'shop_or_store',\n PHOTOCOPY_SHOP: 'shop_or_store',\n PHOTO_LAB_DEVELOPMENT: 'shop_or_store',\n RECYCLING_SHOP: 'shop_or_store',\n RETAIL_OUTLET: 'shop_or_store',\n SECURITY_PRODUCTS: 'shop_or_store',\n SHOPPING_SERVICE: 'shop_or_store',\n SPECIALTY_FOODS: 'shop_or_store',\n SPORTS_EQUIPMENT_CLOTHING: 'shop_or_store',\n TAILOR_SHOP: 'shop_or_store',\n TOYS_GAMES_SHOP: 'shop_or_store',\n VARIETY_STORE: 'shop_or_store',\n VIDEO_RENTAL_SHOP: 'shop_or_store',\n WHOLESALE_CLUB: 'shop_or_store',\n WINE_SPIRITS: 'shop_or_store',\n ATHLETICS_TRACK: 'sport_facility',\n BASEBALL_PARK: 'sport_facility',\n BASKETBALL_ARENA: 'sport_facility',\n BOWLING_CENTER: 'sport_facility',\n CRICKET_GROUND: 'sport_facility',\n HOCKEY_CLUB: 'sport_facility',\n HORSE_RACING_TRACK: 'sport_facility',\n RACE_TRACK: 'sport_facility',\n RUGBY_GROUND: 'sport_facility',\n SNOOKER_POOL_BILLIARD: 'sport_facility',\n THEMATIC_SPORT_CENTER: 'sport_facility',\n FOOTBALL_STADIUM: 'stadium',\n MOTOR_RACING_STADIUM: 'stadium',\n MULTI_PURPOSE_STADIUM: 'stadium',\n NETBALL_STADIUM: 'stadium',\n SOCCER_STADIUM: 'stadium',\n STOCK_EXCHANGE: 'stock_exchange',\n TAXI_LIMOUSINE_SHUTTLE_SERVICE: 'taxi_stand',\n TEA_HOUSE: 'tea_house',\n AMPHITHEATER: 'theater',\n CAVE: 'tourist_attraction',\n MINERAL_HOT_SPRINGS: 'tourist_attraction',\n NATURAL_TOURIST_ATTRACTION: 'tourist_attraction',\n BOAT_LAUNCHING_RAMP: 'transport_access',\n HARBOR: 'transport_access',\n YACHT_BASIN: 'transport_access',\n ATV_DEALER: 'vehicle_dealer',\n BOAT_DEALER: 'vehicle_dealer',\n BUS_DEALER: 'vehicle_dealer',\n CAR_DEALER: 'vehicle_dealer',\n MOTORCYCLE_DEALER: 'vehicle_dealer',\n RECREATIONAL_VEHICLE_DEALER: 'vehicle_dealer',\n TRUCK_DEALER: 'vehicle_dealer',\n VAN_DEALER: 'vehicle_dealer',\n ROAD_TRAFFIC_CONTROL_CENTER: 'vehicle_inspection',\n BODYSHOP: 'vehicle_repair',\n CAR_GLASS_REPLACEMENT_SHOP: 'vehicle_repair',\n CAR_REPAIR_AND_SERVICE: 'vehicle_repair',\n HOME_APPLIANCE_REPAIR: 'vehicle_repair',\n MOTORCYCLE_REPAIR: 'vehicle_repair',\n TIRE_SERVICE: 'vehicle_repair',\n TRUCK_REPAIR_AND_SERVICE: 'vehicle_repair',\n ANIMAL_SERVICES: 'veterinary',\n RIDGE: 'viewpoint',\n OTHER_WINTER_SPORT: 'winter_sports',\n SKI_RESORT: 'winter_sports',\n WILDLIFE_PARK: 'zoo_or_aquarium',\n ZOO: 'zoo_or_aquarium',\n} as const;\n\n/**\n * Map style POI category type.\n *\n * @remarks\n * Represents all available POI (Point of Interest) categories used in the map style.\n * These categories correspond to Search API classification codes and are used for\n * filtering, styling, and displaying POI icons on the map.\n *\n * Each category maps to a specific icon sprite and can be used with custom icon configurations.\n *\n * @example\n * ```ts\n * const category: MapStylePOICategory = 'RESTAURANT';\n * ```\n *\n * @see {@link POICategoryGroup} - For grouped collections of related categories\n *\n * @group POIs\n */\nexport type MapStylePOICategory = keyof typeof mapDisplayPoiCategoryMappings;\n\n/**\n * @ignore\n */\nexport const toBaseMapPOICategory = (category: POICategory): string => completeMapDisplayPoiCategoryMappings[category];\n\n/**\n * The deduplicated set of base-map POI categories — the right-hand side of\n * {@link completeMapDisplayPoiCategoryMappings}. Each value names the\n * `poi-<value>` sprite the base-map style ships, so this set is what callers\n * (e.g., the `pin-clustered` cluster pin's `match` expression) statically\n * enumerate when they need MapLibre to pre-load every possible sprite into\n * the atlas.\n *\n * @ignore\n */\nexport const baseMapPOICategoryValues: readonly string[] = Array.from(\n new Set(Object.values(completeMapDisplayPoiCategoryMappings)),\n);\n\n/**\n * Maps the base map POI category (see {@link toBaseMapPOICategory}) to the\n * group used by the base map style's `POI`/`POI - Micro` layers for color and\n * micro-icon selection. Groups mirror the match arms in the style's\n * `text-color` expression (e.g., `eat_and_drink`, `lodging`, `driving`).\n * @ignore\n */\nconst baseMapCategoryToGroup: Record<string, string> = {\n // eat_and_drink\n bar: 'eat_and_drink',\n cafe: 'eat_and_drink',\n cafeteria: 'eat_and_drink',\n coffee_shop: 'eat_and_drink',\n fast_food: 'eat_and_drink',\n ice_cream_shop: 'eat_and_drink',\n juice_bar: 'eat_and_drink',\n pub: 'eat_and_drink',\n restaurant: 'eat_and_drink',\n tea_house: 'eat_and_drink',\n // shopping\n clothing_shop: 'shopping',\n department_store: 'shopping',\n food_and_drinks_shop: 'shopping',\n mall: 'shopping',\n marketplace: 'shopping',\n shop_or_store: 'shopping',\n supermarket: 'shopping',\n // finance\n atm: 'finance',\n bank: 'finance',\n foreign_exchange: 'finance',\n gold_exchange: 'finance',\n stock_exchange: 'finance',\n // lodging\n camping_ground: 'lodging',\n holiday_home: 'lodging',\n hotel_or_motel: 'lodging',\n // transport\n airport: 'transport',\n bus_station: 'transport',\n ferry_terminal: 'transport',\n heliport_or_helipad: 'transport',\n marina: 'transport',\n public_transport_stop: 'transport',\n railway_station: 'transport',\n taxi_stand: 'transport',\n transport_access: 'transport',\n // driving\n border_control: 'driving',\n car_rental: 'driving',\n charging_location: 'driving',\n fuel_station: 'driving',\n rest_and_service_area: 'driving',\n toll_booth: 'driving',\n truck_stop: 'driving',\n vehicle_dealer: 'driving',\n vehicle_inspection: 'driving',\n vehicle_repair: 'driving',\n vehicle_wash: 'driving',\n weighbridge: 'driving',\n // parking\n parking_facility: 'parking',\n rental_car_parking: 'parking',\n // healthcare\n clinic: 'healthcare',\n dentist: 'healthcare',\n doctor: 'healthcare',\n emergency_ward_entrance: 'healthcare',\n hospital: 'healthcare',\n pharmacy: 'healthcare',\n veterinary: 'healthcare',\n // education\n childcare: 'education',\n college_or_university: 'education',\n research_facility: 'education',\n school_or_kindergarten: 'education',\n // cultural\n cultural_center: 'cultural',\n historic_site: 'cultural',\n museum: 'cultural',\n planetarium: 'cultural',\n theater: 'cultural',\n // leisure\n amusement_park: 'leisure',\n beach_resort: 'leisure',\n casino_and_gambling: 'leisure',\n cinema: 'leisure',\n comedy_club: 'leisure',\n nightlife: 'leisure',\n park_and_recreation_facility: 'leisure',\n tourist_attraction: 'leisure',\n tourist_information: 'leisure',\n zoo_or_aquarium: 'leisure',\n // sport\n fitness_center: 'sport',\n golf_course: 'sport',\n horse_riding: 'sport',\n ice_rink: 'sport',\n sport_facility: 'sport',\n stadium: 'sport',\n swimming_pool: 'sport',\n winter_sports: 'sport',\n // outdoor\n aboriginal_land: 'outdoor',\n landmark: 'outdoor',\n mountain_pass: 'outdoor',\n park_and_recreation_area: 'outdoor',\n viewpoint: 'outdoor',\n // protected\n protected_land: 'protected',\n // public\n cemetery: 'public',\n courthouse: 'public',\n fire_station: 'public',\n government_office: 'public',\n police_station: 'public',\n post_depot: 'public',\n post_office: 'public',\n prison: 'public',\n public_library: 'public',\n // military\n military_airfield: 'military',\n // religion\n religious_site: 'religion',\n // business\n animal_shelter: 'business',\n commercial_area: 'business',\n conference_center: 'business',\n consumer_service: 'business',\n industrial_area: 'business',\n manufacturing_facility: 'business',\n media_facility: 'business',\n mourning_facility: 'business',\n ngo_office: 'business',\n personal_care_service: 'business',\n};\n\n/**\n * @ignore\n */\nexport const toBaseMapPOIGroup = (category: POICategory): string | undefined => {\n const baseMapCategory = toBaseMapPOICategory(category);\n return baseMapCategory ? baseMapCategoryToGroup[baseMapCategory] : undefined;\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\n\n/**\n * Bold font face used in the TomTom map style.\n *\n * @remarks\n * This is the primary bold font used for prominent labels and headings in the map.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_BOLD_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_BOLD_FONT],\n * 'text-field': ['get', 'name']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_BOLD_FONT = 'Noto-Bold';\n\n/**\n * Regular font face used in the TomTom map style.\n *\n * @remarks\n * This is the standard font used for most text labels on the map.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_REGULAR_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_REGULAR_FONT],\n * 'text-field': ['get', 'description']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_REGULAR_FONT = 'Noto-Regular';\n\n/**\n * Medium weight font face used in the TomTom map style.\n *\n * @remarks\n * This font provides a middle ground between regular and bold weights.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_MEDIUM_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_MEDIUM_FONT],\n * 'text-field': ['get', 'title']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_MEDIUM_FONT = 'Noto-Medium';\n\n/**\n * Italic font face used in the TomTom map style.\n *\n * @remarks\n * This is the italic variant used for emphasized or secondary text labels.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_ITALIC_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_ITALIC_FONT],\n * 'text-field': ['get', 'subtitle']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_ITALIC_FONT = 'NotoSans-MediumItalic';\n\n/**\n * Default text size expression used in the TomTom map style.\n *\n * @remarks\n * This MapLibre expression defines zoom-dependent text sizing that scales from 12px at zoom 10\n * to 14px at zoom 16. Use this for consistent text sizing across zoom levels.\n *\n * @example\n * ```typescript\n * import { DEFAULT_TEXT_SIZE } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-size': DEFAULT_TEXT_SIZE,\n * 'text-field': ['get', 'name']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const DEFAULT_TEXT_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 10, 14, 18, 16];\n\n/**\n * Array of all available font faces in the TomTom map style.\n *\n * @remarks\n * Contains all font variants available in the map style. Useful for font selection\n * or validation when creating custom layers.\n *\n * @example\n * ```typescript\n * import { mapFonts } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Check if a font is available\n * if (mapFonts.includes('Noto-Bold')) {\n * // Use the font\n * }\n *\n * // Use as fallback list\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [...mapFonts]\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const mapFonts = [MAP_REGULAR_FONT, MAP_ITALIC_FONT, MAP_BOLD_FONT, MAP_MEDIUM_FONT] as const;\n\n/**\n * Type representing valid font faces available in the TomTom map style.\n *\n * @remarks\n * Use this type to ensure type-safe font selection when working with text layers.\n * Restricts values to only the fonts available in the map style.\n *\n * @example\n * ```typescript\n * import type { MapFont } from '@tomtom-international/maps-sdk-js/map';\n *\n * function createTextLayer(font: MapFont) {\n * return {\n * type: 'symbol',\n * layout: {\n * 'text-font': [font],\n * 'text-field': ['get', 'name']\n * }\n * };\n * }\n *\n * // Type-safe: only accepts valid map fonts\n * const layer = createTextLayer('Noto-Bold');\n * ```\n *\n * @group Map Style\n */\nexport type MapFont = (typeof mapFonts)[number];\n\n/**\n * Default pin icon scale at max zoom level (zoom 22).\n * This is the icon-size scale factor - 1.0 would be the original icon size.\n * @ignore\n */\nexport const DEFAULT_MAX_PIN_SCALE = 0.8;\n\n/**\n * @ignore\n */\nexport const PIN_ICON_SIZE: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 8,\n 0.6,\n 22,\n DEFAULT_MAX_PIN_SCALE,\n];\n\n/**\n * @ignore\n */\nexport const SELECTED_PIN_ICON_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 8, 0.8, 22, 1];\n","import { SymbolLayerSpecification } from 'maplibre-gl';\nimport { LayerSpecTemplate } from '../types';\nimport { DEFAULT_TEXT_SIZE, MAP_BOLD_FONT, PIN_ICON_SIZE } from './commonLayerProps';\n\n/**\n * @ignore\n */\nexport const TITLE = 'title';\n\n/**\n * @ignore\n */\nexport const ICON_ID = 'iconID';\n\n/**\n * Y-offset multiplier for text positioned below icons (top anchor).\n * In ems, relative to icon scale.\n * @ignore\n */\nexport const DEFAULT_TEXT_OFFSET_Y = 0.7;\n\n/**\n * X-offset multiplier for text positioned beside icons (left/right anchors).\n * In ems, relative to icon scale.\n * @ignore\n */\nexport const DEFAULT_TEXT_OFFSET_X = 1.4;\n\n/**\n * @ignore\n */\nexport const DEFAULT_PLACE_ICON_ID = 'default_place';\n\n// Fallback sprite for `base-map` theme places that don't match any category. The\n// style ships `poi-<group>-micro` sprites (e.g. `poi-parking-micro`) for the\n// `POI - Micro` layer — reusing one of those keeps uncategorized places visually\n// consistent with the native base-map POI labels (small dot) instead of showing\n// the large `default_place` pin.\n/**\n * @ignore\n */\nexport const DEFAULT_BASE_MAP_PLACE_ICON_ID = 'poi-parking-micro';\n\n/**\n * @ignore\n */\nexport const pinIconBaseLayout: SymbolLayerSpecification['layout'] = {\n 'icon-image': ['get', ICON_ID],\n 'icon-anchor': 'bottom',\n 'icon-size': PIN_ICON_SIZE,\n 'icon-allow-overlap': true,\n 'icon-padding': 0,\n};\n\n/**\n * @ignore\n */\nexport const pinIconBasePaint: SymbolLayerSpecification['paint'] = {\n 'icon-translate': [0, 5],\n 'icon-translate-anchor': 'viewport',\n};\n\n/**\n * @ignore\n */\nexport const pinTextBaseLayout: SymbolLayerSpecification['layout'] = {\n 'text-optional': true,\n 'text-font': [MAP_BOLD_FONT],\n 'text-field': ['get', TITLE],\n 'text-justify': 'auto',\n 'text-variable-anchor': ['top', 'left', 'right'],\n // NOTE: make sure to text against pins and waypoints, in a way that there's enough distance from the pin so the text doesn't disappear\n 'text-variable-anchor-offset': [\n 'top',\n [0, DEFAULT_TEXT_OFFSET_Y],\n 'left',\n [DEFAULT_TEXT_OFFSET_X, -DEFAULT_TEXT_OFFSET_X],\n 'right',\n [-DEFAULT_TEXT_OFFSET_X, -DEFAULT_TEXT_OFFSET_X],\n ],\n 'text-size': DEFAULT_TEXT_SIZE,\n 'text-padding': 5,\n};\n\n/**\n * Note: `text-color` and `text-halo-color` are intentionally omitted so\n * {@link buildPaintConfig} can inject theme-adaptive defaults on layers that\n * don't supply their own. Layers that bring a category/group-driven expression\n * (e.g., the base-map POI layer) keep their own colors untouched.\n * @ignore\n */\nexport const pinTextBasePaint: SymbolLayerSpecification['paint'] = {\n 'text-halo-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 10, 1.5],\n 'text-translate-anchor': 'viewport',\n};\n\n/**\n * Pin places, base layer with mostly the icon portion.\n * @ignore\n */\nexport const pinLayerBaseSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: { ...pinIconBaseLayout, ...pinTextBaseLayout },\n paint: { ...pinIconBasePaint, ...pinTextBasePaint },\n};\n","/**\n * Returns a text with a number suffixed after a hyphen.\n * @ignore\n */\nexport const suffixNumber = (text: string, numberToSuffix: number): string => `${text}-${numberToSuffix}`;\n","import type { CircleLayerSpecification, ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport { baseMapPOICategoryValues } from '../../pois/util/poiCategoryMapping';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { DEFAULT_BASE_MAP_PLACE_ICON_ID } from '../../shared/layers/symbolLayers';\n\n// Filter that picks cluster features out of a GeoJSON source with `cluster: true`.\n// MapLibre adds `point_count` to every cluster feature; using `has` keeps the\n// expression decoupled from any particular cluster property name.\nconst isClusterFilter: ExpressionSpecification = ['has', 'point_count'];\n\n// Filter for individual (non-cluster) features in a clustered source. Used by the\n// caller to derive the \"main pin\" filter when clustering is enabled.\nexport const isNotClusterFilter: ExpressionSpecification = ['!', isClusterFilter];\n\n/**\n * Cluster property key for the deduplicated comma-separated list of base-map\n * sprite IDs (each leaf's `baseMapIconID`). Single-category clusters carry a\n * single id here — the cluster pin layer's `match` expression picks the\n * matching `poi-<category>` sprite. The presence of a comma in this property\n * is also what {@link isMixedCategoryClusterFilter} keys off to route mixed\n * clusters to the centred badge + count pair instead.\n *\n * @ignore\n */\nexport const CLUSTER_PROPERTY_BASE_MAP_ICON_IDS = 'clusterBaseMapIconIDs';\n\n/**\n * Source-feature property key carrying each place's base-map sprite id,\n * populated by `preparePlacesForDisplay` when the `pin-clustered` theme is\n * active. The cluster pin (single-category case) reads it via the\n * {@link CLUSTER_PROPERTY_BASE_MAP_ICON_IDS} aggregator.\n *\n * @ignore\n */\nexport const FEATURE_PROPERTY_BASE_MAP_ICON_ID = 'baseMapIconID';\n\n// Cluster has only one unique base-map sprite among its leaves — i.e. every\n// place in the cluster shares a category. Used to gate single-category-only\n// renderings (cluster icon, top-right count badge) so mixed clusters fall\n// through to the centred badge + count pair.\nconst isSingleCategoryClusterFilter: ExpressionSpecification = [\n 'all',\n isClusterFilter,\n ['==', ['index-of', ',', ['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]], -1],\n];\n\n// Cluster contains 2+ unique base-map sprites — flagged by a comma in the\n// dedup-concat aggregator. Used to gate the centred badge + count circle.\nconst isMixedCategoryClusterFilter: ExpressionSpecification = [\n 'all',\n isClusterFilter,\n ['>=', ['index-of', ',', ['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]], 0],\n];\n\n/**\n * Symbol layer drawing the lone POI category's base-map sprite at the centre\n * of single-category clusters. Mixed clusters skip this layer entirely and\n * surface their count via {@link buildClusterMixedBadgeCircleLayerSpec} +\n * {@link buildClusterMixedCountTextLayerSpec} instead.\n *\n * @ignore\n */\nexport const buildClusterPinLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isSingleCategoryClusterFilter,\n layout: {\n // Single-category clusters carry exactly one entry in\n // `clusterBaseMapIconIDs`. We map it to a literal `image()` arm via\n // {@link buildKnownBaseMapSpriteImageExpr} so MapLibre statically\n // pre-loads every possible base-map sprite into the atlas — without\n // that enumeration, dynamic image references render as blanks.\n 'icon-image': buildKnownBaseMapSpriteImageExpr(['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]),\n // Constant 1.0 — the cluster icon stays the same on-screen size at\n // every zoom level so users can rely on a stable hit target while\n // panning/zooming. The pin theme's per-place sprites still scale via\n // `PIN_ICON_SIZE`; only the cluster pin is fixed.\n 'icon-size': 1,\n 'icon-allow-overlap': true,\n 'icon-ignore-placement': true,\n },\n});\n\n// Translate (in viewport pixels) that places the count badge at the\n// top-right edge of the single-category cluster icon. The cluster icon is\n// anchored 'center' at viewport (0, 0), so [12, -14] tucks the badge above\n// and to the right of it without obscuring the icon.\nconst SINGLE_BADGE_TRANSLATE: [number, number] = [12, -14];\n\n// Radius of the small top-right badge for single-category clusters.\nconst SINGLE_BADGE_RADIUS = 11;\n\n// Radius of the centred badge that occupies a mixed cluster's pin slot. Larger\n// than the single-category corner badge because there's no icon behind it —\n// the circle itself is the visual.\nconst MIXED_BADGE_RADIUS = 15;\n\n/**\n * Black circle drawn at the top-right corner of single-category clusters.\n * Background for {@link buildClusterCountTextLayerSpec}; mixed clusters use\n * {@link buildClusterMixedBadgeCircleLayerSpec} instead.\n *\n * @ignore\n */\nexport const buildClusterBadgeCircleLayerSpec = (): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isSingleCategoryClusterFilter,\n paint: {\n 'circle-radius': SINGLE_BADGE_RADIUS,\n 'circle-color': '#000000',\n 'circle-stroke-color': '#FFFFFF',\n 'circle-stroke-width': 1.5,\n 'circle-translate': SINGLE_BADGE_TRANSLATE,\n 'circle-translate-anchor': 'viewport',\n },\n});\n\n/**\n * Symbol layer rendering the cluster count on top of the top-right badge\n * (single-category clusters). Mixed clusters use\n * {@link buildClusterMixedCountTextLayerSpec}.\n *\n * @ignore\n */\nexport const buildClusterCountTextLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isSingleCategoryClusterFilter,\n layout: {\n 'text-field': ['get', 'point_count_abbreviated'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': 12,\n 'text-allow-overlap': true,\n 'text-ignore-placement': true,\n },\n paint: {\n 'text-color': '#FFFFFF',\n 'text-translate': SINGLE_BADGE_TRANSLATE,\n 'text-translate-anchor': 'viewport',\n },\n});\n\n/**\n * Centred count circle for mixed-category clusters — replaces the cluster\n * icon entirely. {@link buildClusterMixedCountTextLayerSpec} renders the\n * count on top.\n *\n * @ignore\n */\nexport const buildClusterMixedBadgeCircleLayerSpec = (): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isMixedCategoryClusterFilter,\n paint: {\n 'circle-radius': MIXED_BADGE_RADIUS,\n 'circle-color': '#000000',\n 'circle-stroke-color': '#FFFFFF',\n 'circle-stroke-width': 1.5,\n },\n});\n\n/**\n * Cluster count rendered on top of the centred badge for mixed-category\n * clusters. Mirrors {@link buildClusterCountTextLayerSpec} but with no\n * translate, so it lands inside the centred badge from\n * {@link buildClusterMixedBadgeCircleLayerSpec}.\n *\n * @ignore\n */\nexport const buildClusterMixedCountTextLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isMixedCategoryClusterFilter,\n layout: {\n 'text-field': ['get', 'point_count_abbreviated'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': 12,\n 'text-allow-overlap': true,\n 'text-ignore-placement': true,\n },\n paint: {\n 'text-color': '#FFFFFF',\n },\n});\n\n/**\n * `clusterProperties` aggregator that exposes a deduplicated, comma-separated\n * list of source-feature property values on every generated cluster.\n *\n * @remarks\n * Uses MapLibre's advanced `[reduceExpression, mapExpression]` form: the map\n * expression extracts the value from each clustered feature; the reduce\n * expression appends it to the running accumulator only when not already\n * present (membership test via `index-of` over `,acc,` ↔ `,current,`).\n *\n * @ignore\n */\nconst buildDeduplicatedConcatAggregator = (sourceProperty: string, outputProperty: string) => [\n [\n 'case',\n ['==', ['get', outputProperty], null],\n ['accumulated'],\n ['==', ['accumulated'], null],\n ['get', outputProperty],\n ['>=', ['index-of', ['concat', ',', ['get', outputProperty], ','], ['concat', ',', ['accumulated'], ',']], 0],\n ['accumulated'],\n ['concat', ['accumulated'], ',', ['get', outputProperty]],\n ],\n ['get', sourceProperty],\n];\n\n/**\n * `clusterProperties` map for clustered places sources. Aggregates a\n * deduplicated, comma-separated list of base-map sprite IDs\n * (`baseMapIconID` per leaf) onto every cluster feature, feeding the\n * cluster pin's single-vs-mixed `match` expression and the\n * single/mixed cluster filters.\n *\n * @ignore\n */\nexport const clusteredPlacesAggregateProperties = {\n [CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]: buildDeduplicatedConcatAggregator(\n FEATURE_PROPERTY_BASE_MAP_ICON_ID,\n CLUSTER_PROPERTY_BASE_MAP_ICON_IDS,\n ),\n};\n\n// Wrap a dynamic sprite-name expression in a `match` whose arms statically\n// enumerate every known `poi-<category>` sprite. Each arm returns\n// `image(literal)`, which MapLibre walks at style-parse time to enumerate\n// sprite usages and pre-load them into the atlas. Without this enumeration\n// the dynamic value resolves to a missing image at render time and nothing\n// shows.\n//\n// The default arm falls back to `DEFAULT_BASE_MAP_PLACE_ICON_ID` (the same\n// fallback the `base-map` theme uses for unmapped places); callers should\n// gate the layer with a filter so the default never fires for valid input.\nconst buildKnownBaseMapSpriteImageExpr = (dynamicNameExpr: ExpressionSpecification): ExpressionSpecification => {\n const arms: (string | ExpressionSpecification)[] = ['match', dynamicNameExpr];\n for (const category of baseMapPOICategoryValues) {\n const spriteId = `poi-${category}`;\n arms.push(spriteId, ['image', spriteId]);\n }\n arms.push(['image', DEFAULT_BASE_MAP_PLACE_ICON_ID]);\n return arms as ExpressionSpecification;\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { PlaceConnectionsConfig } from '../types/placesModuleConfig';\n\n/**\n * @ignore\n */\nexport type ConnectionLayerSpecs = {\n line: LayerSpecTemplate<LineLayerSpecification>;\n label: LayerSpecTemplate<SymbolLayerSpecification>;\n};\n\n// Base dashed line for connections. Width/colour mirror the places `selected`\n// highlight colour so the connection reads as visually tied to the place markers.\nconst baseLineSpec: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-color': '#3f9cd9',\n 'line-width': 1.5,\n 'line-dasharray': [2, 2],\n 'line-opacity': 0.9,\n },\n};\n\n// Label placed along the line (`line-center` keeps one label per connection, centred).\n// A small negative `text-offset` nudges the text perpendicular to the line so it reads\n// \"a bit on the side\" rather than sitting on top of the dashes.\nconst baseLabelSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line-center',\n 'text-field': ['get', 'label'],\n 'text-size': 12,\n 'text-offset': [0, -1],\n 'text-allow-overlap': false,\n 'text-ignore-placement': false,\n 'text-keep-upright': true,\n },\n paint: {\n 'text-color': '#1a5f8a',\n 'text-halo-color': '#ffffff',\n 'text-halo-width': 1.5,\n },\n};\n\n/**\n * Builds layer specifications for rendering place-to-place connections.\n * @ignore\n */\nexport const buildConnectionLayerSpecs = (config: PlaceConnectionsConfig | undefined): ConnectionLayerSpecs => {\n const customLine = config?.layers?.line;\n const customLabel = config?.layers?.label;\n\n return {\n line: {\n ...baseLineSpec,\n ...customLine,\n layout: { ...baseLineSpec.layout, ...customLine?.layout },\n paint: { ...baseLineSpec.paint, ...customLine?.paint },\n },\n label: {\n ...baseLabelSpec,\n ...customLabel,\n layout: { ...baseLabelSpec.layout, ...customLabel?.layout },\n paint: { ...baseLabelSpec.paint, ...customLabel?.paint },\n },\n };\n};\n","import { suffixNumber } from '../../shared/layers/utils';\nimport { parseSvg } from '../../shared/resources';\nimport type { PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\n\n/**\n * Map of icon IDs to their text offset scale factors.\n * @ignore\n */\nexport type IconScalesMap = Map<string, { heightScale: number; widthScale: number }>;\n\n/**\n * Default pin dimensions (from base pin.svg)\n */\nconst DEFAULT_PIN_HEIGHT_PX = 140;\nconst DEFAULT_PIN_WIDTH_PX = 120;\n\n/**\n * Default base-map POI icon dimensions (from base-theme POI icons)\n */\nconst DEFAULT_MAP_POI_HEIGHT_PX = 54;\nconst DEFAULT_MAP_POI_WIDTH_PX = 54;\n\n/**\n * Extracts dimensions from an SVG string or HTMLImageElement.\n * For SVGs, parses the viewBox or width/height attributes.\n * For images, uses naturalWidth/naturalHeight.\n * @ignore\n */\nexport const extractImageDimensions = (image: string | HTMLImageElement): { width: number; height: number } | null => {\n try {\n if (typeof image === 'string') {\n if (image.includes('<svg')) {\n // Parse SVG to extract dimensions\n const svgElement = parseSvg(image);\n\n // Try viewBox first (format: \"minX minY width height\")\n const viewBox = svgElement.getAttribute('viewBox');\n if (viewBox) {\n const parts = viewBox.split(/\\s+/);\n if (parts.length === 4) {\n return {\n width: Number.parseFloat(parts[2]),\n height: Number.parseFloat(parts[3]),\n };\n }\n }\n\n // Fallback to width/height attributes\n const width = svgElement.getAttribute('width');\n const height = svgElement.getAttribute('height');\n if (width && height) {\n return {\n width: Number.parseFloat(width),\n height: Number.parseFloat(height),\n };\n }\n }\n // For URL strings, we can't extract dimensions synchronously\n return null;\n } else {\n // HTMLImageElement - check if loaded\n if (image.complete && image.naturalWidth > 0) {\n return {\n width: image.naturalWidth,\n height: image.naturalHeight,\n };\n }\n return null;\n }\n } catch (error) {\n console.warn('Failed to extract image dimensions:', error);\n return null;\n }\n};\n\n/**\n * Calculate the scale factors for a custom icon based on its dimensions relative to standard icon sizes.\n *\n * Calculates both height and width scales independently:\n * - Height scale is used for vertical text offset\n * - Width scale is used for horizontal text offset\n *\n *\n * @param image The image to extract dimensions from\n * @param theme The places theme ('base-map'/'circle-icon' for centered icons, 'pin' for bottom-anchored pins)\n * @returns Object with heightScale and widthScale, or undefined if icon is standard-sized (within tolerance)\n * @ignore\n */\nexport const calculateIconScale = (\n image: string | HTMLImageElement | undefined,\n theme?: PlacesTheme,\n): { heightScale: number; widthScale: number } | undefined => {\n if (!image) {\n return undefined;\n }\n\n const dimensions = extractImageDimensions(image);\n if (!dimensions) {\n return undefined;\n }\n\n const isCenteredTheme = theme === 'base-map' || theme === 'circle-icon';\n\n // Centered themes (base-map / circle-icon) scale relative to POI icon dimensions.\n // Pin theme scales relative to default pin dimensions.\n const refHeight = isCenteredTheme ? DEFAULT_MAP_POI_HEIGHT_PX : DEFAULT_PIN_HEIGHT_PX;\n const refWidth = isCenteredTheme ? DEFAULT_MAP_POI_WIDTH_PX : DEFAULT_PIN_WIDTH_PX;\n\n return { heightScale: dimensions.height / refHeight, widthScale: dimensions.width / refWidth };\n};\n\n/**\n * Builds a map of icon IDs to their text offset scales for custom icons.\n * @ignore\n */\nexport const buildCustomIconScalesMap = (\n config: PlacesModuleConfig | undefined,\n instanceIndex: number,\n): IconScalesMap => {\n const iconTextOffsetScales = new Map<string, { heightScale: number; widthScale: number }>();\n const customIcons = config?.icon?.categoryIcons ?? [];\n\n for (const icon of customIcons) {\n if (icon.image) {\n const scales = calculateIconScale(icon.image, config?.theme);\n if (scales !== undefined) {\n // Base icon ID with instance suffix\n const suffixedIconId = suffixNumber(icon.id, instanceIndex);\n iconTextOffsetScales.set(suffixedIconId, scales);\n\n // If this icon has an availability level, also add the availability-suffixed version\n // (e.g., \"ELECTRIC_VEHICLE_STATION-available-0\")\n if (icon.availabilityLevel) {\n const availabilitySuffixedId = suffixNumber(`${icon.id}-${icon.availabilityLevel}`, instanceIndex);\n iconTextOffsetScales.set(availabilitySuffixedId, scales);\n }\n }\n }\n }\n\n return iconTextOffsetScales;\n};\n","import type { ChargingPark, ChargingParkWithAvailability, Place, POICategory } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport type { AvailabilityLevel } from '../../shared';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport type { EVAvailabilityConfig, PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\n\n/**\n * Type guard to check if a charging park has availability data.\n * @ignore\n */\nexport const hasChargingAvailability = (\n chargingPark: ChargingPark | ChargingParkWithAvailability | undefined,\n): chargingPark is ChargingParkWithAvailability =>\n Boolean(chargingPark && 'availability' in chargingPark && chargingPark.availability);\n\n/**\n * Check if a place is an EV charging station with availability data.\n * @ignore\n */\nexport const isEVStationWithAvailability = (place: Place): boolean => {\n const category = place.properties.poi?.categories?.[0];\n return category === 'ELECTRIC_VEHICLE_STATION' && hasChargingAvailability(place.properties.chargingPark);\n};\n\n/**\n * Calculate charging point availability information.\n * @ignore\n */\nexport const getChargingPointAvailability = (\n place: Place,\n): { availableCount: number; totalCount: number; ratio: number } | undefined => {\n const chargingPark = place.properties.chargingPark;\n if (hasChargingAvailability(chargingPark)) {\n const availability = chargingPark.availability.chargingPointAvailability;\n const available = availability.statusCounts.Available ?? 0;\n return {\n availableCount: available,\n totalCount: availability.count,\n ratio: available / availability.count,\n };\n }\n return undefined;\n};\n\n/**\n * Default threshold for EV availability - available vs occupied.\n * @ignore\n */\nexport const DEFAULT_EV_AVAILABILITY_THRESHOLD = 0;\n\n/**\n * Default formatter for EV availability text.\n * @ignore\n */\nexport const defaultFormatAvailabilityText = (available: number, total: number): string => `${available}/${total}`;\n\n/**\n * Build availability text for a place with EV availability data.\n * @ignore\n */\nexport const buildAvailabilityText = (place: Place, config?: EVAvailabilityConfig): string => {\n const availability = getChargingPointAvailability(place);\n if (!availability) {\n return '';\n }\n\n const formatFn = config?.formatText ?? defaultFormatAvailabilityText;\n return formatFn(availability.availableCount, availability.totalCount);\n};\n\n/**\n * Build availability ratio for a place with EV availability data.\n * @ignore\n */\nexport const getAvailabilityRatio = (place: Place): number => {\n const availability = getChargingPointAvailability(place);\n return availability?.ratio ?? 0;\n};\n\n/**\n * Get the color expression for EV availability based on ratio.\n * @ignore\n */\nexport const getAvailabilityColorExpression = (config?: EVAvailabilityConfig): ExpressionSpecification => {\n const threshold = config?.threshold ?? DEFAULT_EV_AVAILABILITY_THRESHOLD;\n\n return ['case', ['>=', ['get', 'evAvailabilityRatio'], threshold], 'green', 'red'] as ExpressionSpecification;\n};\n\n/**\n * Handles icon selection for EV stations with availability data.\n * Returns icon ID if availability-specific icon should be used, or undefined to fall through to regular selection.\n * @ignore\n */\nexport const getEVAvailabilityIconID = (\n place: Place,\n poiCategory: POICategory,\n instanceIndex: number,\n config: PlacesModuleConfig,\n iconTheme: PlacesTheme,\n): string | undefined => {\n if (!config.evAvailability?.enabled || !isEVStationWithAvailability(place)) {\n return undefined;\n }\n\n const ratio = getAvailabilityRatio(place);\n const threshold = config.evAvailability.threshold ?? 0.3;\n const requiredLevel: AvailabilityLevel = ratio >= threshold ? 'available' : 'occupied';\n const hasCustomIcons = config.icon?.categoryIcons && config.icon.categoryIcons.length > 0;\n\n const customIconWithAvailability = config.icon?.categoryIcons?.find(\n (customIcon) => customIcon.id === poiCategory && customIcon.availabilityLevel === requiredLevel,\n );\n\n // If a custom icon with the required availability level exists, use it\n if (customIconWithAvailability) {\n return suffixNumber(`${customIconWithAvailability.id}-${requiredLevel}`, instanceIndex);\n }\n\n // For pin theme: use CDN availability sprites when no custom icons are defined\n if (!hasCustomIcons && iconTheme === 'pin') {\n return `7309-${requiredLevel}`;\n }\n\n // Otherwise, fall through to regular icon selection\n return undefined;\n};\n","import type {\n DataDrivenPropertyValueSpecification,\n ExpressionSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport { DEFAULT_MAX_PIN_SCALE } from '../../shared/layers/commonLayerProps';\nimport { DEFAULT_TEXT_OFFSET_X, DEFAULT_TEXT_OFFSET_Y, ICON_ID } from '../../shared/layers/symbolLayers';\nimport type { PlacesTheme } from '../types/placesModuleConfig';\n\ntype VariableAnchorOffset = NonNullable<SymbolLayerSpecification['layout']>['text-variable-anchor-offset'];\n\n/**\n * Extracts the maximum icon scale factor from an icon-size expression.\n * For interpolate expressions, returns the last value (max zoom scale).\n * @ignore\n */\nconst extractMaxIconScale = (expression: DataDrivenPropertyValueSpecification<number> | undefined): number => {\n if (!expression) {\n return DEFAULT_MAX_PIN_SCALE;\n }\n\n // Literal: 0.8\n if (typeof expression === 'number') {\n return expression;\n }\n\n // Interpolate expression: ['interpolate', ['linear'], ['zoom'], 8, 0.6, 22, **0.8**]\n if (Array.isArray(expression)) {\n const lastValue = expression.at(-1);\n if (typeof lastValue === 'number') {\n return lastValue;\n }\n }\n\n // Legacy stops object: { stops: [[10, 0.7], [18, **1**]] }\n if (typeof expression === 'object' && 'stops' in expression) {\n const stops = (expression as { stops: [number, number][] }).stops;\n if (Array.isArray(stops) && stops.length > 0) {\n const lastStop = stops.at(-1);\n if (Array.isArray(lastStop) && typeof lastStop[1] === 'number') {\n return lastStop[1];\n }\n }\n }\n\n return DEFAULT_MAX_PIN_SCALE;\n};\n\n/**\n * Layout properties for text offset - can be spread directly into layer layout.\n */\ntype TextOffsetLayout = {\n 'text-offset'?: [number, number];\n 'text-variable-anchor-offset'?: VariableAnchorOffset;\n};\n\n/**\n * Rounds to 4 decimal places — prevents float noise (e.g. 0.7*2*1.5 = 2.0999…)\n * from leaking into style JSON when custom icon scales produce non-exact products.\n * @ignore\n */\nconst round4 = (n: number): number => Math.round(n * 10000) / 10000;\n\n/**\n * Builds anchor offsets for variable-anchor-offset property.\n * @ignore\n */\nconst buildAnchorOffsets = (\n topOffset: number,\n sideOffset: number,\n pinVerticalAdjustment: number,\n customTextOffset?: number,\n): { top: [number, number]; left: [number, number]; right: [number, number] } => {\n const hasCustomOffset = customTextOffset !== undefined;\n // For pin theme with custom offset, override only the primary direction for each anchor\n // top anchor → custom offset applies to vertical, left/right → custom offset applies to horizontal\n return {\n top: hasCustomOffset ? [0, customTextOffset] : [0, round4(topOffset)],\n left: hasCustomOffset\n ? [customTextOffset, round4(pinVerticalAdjustment)]\n : [round4(sideOffset), round4(pinVerticalAdjustment)],\n right: hasCustomOffset\n ? [-customTextOffset, round4(pinVerticalAdjustment)]\n : [-round4(sideOffset), round4(pinVerticalAdjustment)],\n };\n};\n\n/**\n * Calculates text offset for place labels\n *\n * @param iconSizeExpression The icon-size property from the layer specification\n * @param iconTextOffsetScales Map of icon IDs to their scale factors (heightScale for vertical, widthScale for horizontal)\n * @param theme The places theme ('base-map'/'circle-icon' for centered icons, 'pin' for bottom-anchored pins)\n * @param customTextOffset Custom text offset multiplier (overrides default TEXT_OFFSET constants)\n * @returns Configuration object with the MapLibre property type and value to apply\n * @ignore\n */\nexport const getTextOffset = (\n iconSizeExpression: DataDrivenPropertyValueSpecification<number> | undefined,\n iconTextOffsetScales: Map<string, { heightScale: number; widthScale: number }>,\n theme?: PlacesTheme,\n customTextOffset?: number,\n): TextOffsetLayout => {\n const maxIconScale = extractMaxIconScale(iconSizeExpression);\n const iconScaleMultiplier = maxIconScale / DEFAULT_MAX_PIN_SCALE;\n // 'base-map' and 'circle-icon' both use centered icons; 'pin' is bottom-anchored.\n const isCenteredTheme = theme === 'base-map' || theme === 'circle-icon';\n const hasCustomOffset = customTextOffset !== undefined;\n\n // For centered themes with a custom offset, use simple text-offset\n if (isCenteredTheme && hasCustomOffset) {\n return {\n 'text-offset': [customTextOffset, customTextOffset],\n };\n }\n\n // Calculate fallback offsets (used when no custom icons or as default case)\n const fallbackTopOffset = DEFAULT_TEXT_OFFSET_Y * iconScaleMultiplier;\n const fallbackSideOffset = DEFAULT_TEXT_OFFSET_X * iconScaleMultiplier;\n const fallbackVerticalAdjustment = isCenteredTheme ? 0 : -fallbackSideOffset;\n const fallbackOffsets = buildAnchorOffsets(\n fallbackTopOffset,\n fallbackSideOffset,\n fallbackVerticalAdjustment,\n customTextOffset,\n );\n const fallbackAnchorOffset = [\n 'top',\n fallbackOffsets.top,\n 'left',\n fallbackOffsets.left,\n 'right',\n fallbackOffsets.right,\n ];\n\n // No custom icons - return literal value directly (no case expression needed)\n if (iconTextOffsetScales.size === 0) {\n return {\n 'text-variable-anchor-offset': fallbackAnchorOffset as VariableAnchorOffset,\n };\n }\n\n // Build case expression for custom icons\n const offsetCaseExpression: (string | number | ExpressionSpecification)[] = ['case'];\n\n for (const [iconId, scales] of iconTextOffsetScales.entries()) {\n // Centered themes (base-map / circle-icon) use larger vertical offsets to match native map styling\n const baseTopOffset = isCenteredTheme ? DEFAULT_TEXT_OFFSET_Y * 2 : DEFAULT_TEXT_OFFSET_Y;\n const topOffset = baseTopOffset * scales.heightScale;\n const sideOffset = DEFAULT_TEXT_OFFSET_X * scales.widthScale;\n\n // Centered themes (centered icon anchor) → no vertical adjustment for side anchors\n // Pin theme uses pins (bottom-anchored) → shift labels upward to align with visual center\n const pinVerticalAdjustment = isCenteredTheme ? 0 : -sideOffset;\n\n const offsets = buildAnchorOffsets(topOffset, sideOffset, pinVerticalAdjustment, customTextOffset);\n offsetCaseExpression.push(\n ['==', ['get', ICON_ID], iconId],\n ['literal', ['top', offsets.top, 'left', offsets.left, 'right', offsets.right]],\n );\n }\n\n // Add fallback for icons not in the custom scales map\n offsetCaseExpression.push(['literal', fallbackAnchorOffset]);\n\n return {\n 'text-variable-anchor-offset': offsetCaseExpression as VariableAnchorOffset,\n };\n};\n","import { LightDark } from '../../shared/types/style';\n/**\n * Calculates theme-adaptive text colors based on light/dark mode.\n * @param lightDark Whether the current theme is light or dark\n * @returns Object with text and halo colors appropriate for the theme\n * @ignore\n *\n * TODO: Should these colors adapt to the POI context colors,\n * or should they remain as fixed defaults?\n */\nexport const getThemeAdaptiveTextColors = (\n lightDark: LightDark,\n): {\n textColor: string;\n haloColor: string;\n} => {\n return {\n textColor: lightDark === 'dark' ? '#FFFFFF' : '#333333',\n haloColor: lightDark === 'dark' ? '#333333' : '#FFFFFF',\n };\n};\n","import type { DataDrivenPropertyValueSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { TITLE } from '../../shared/layers/symbolLayers';\nimport type { PlaceLayerName, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport type { IconScalesMap } from './customIconScales';\nimport { getAvailabilityColorExpression } from './evAvailabilityHelpers';\nimport { getTextOffset } from './textOffsetCalculator';\nimport { getThemeAdaptiveTextColors } from './themeAdaptation';\n\n/**\n * Builds the text field expression for place labels\n * Supports EV availability text when enabled.\n * @ignore\n */\n\nexport const buildTextFieldExpression = (\n config: PlacesModuleConfig | undefined,\n evAvailabilityEnabled: boolean,\n): DataDrivenPropertyValueSpecification<string> => {\n if (!evAvailabilityEnabled) {\n return ['get', TITLE];\n }\n\n return [\n 'case',\n ['has', 'evAvailabilityText'],\n // If has EV availability, show two-line format with colored availability\n [\n 'format',\n ['get', TITLE],\n {},\n '\\n',\n {},\n ['get', 'evAvailabilityText'],\n {\n 'font-scale': 1.1,\n 'text-color': getAvailabilityColorExpression(config?.evAvailability),\n },\n ],\n // Otherwise, show normal title\n ['get', TITLE],\n ];\n};\n\n/**\n * Builds the layout configuration\n * @ignore\n */\nexport const buildLayoutConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: PlaceLayerName,\n textField: DataDrivenPropertyValueSpecification<string> | undefined,\n iconTextOffsetScales?: IconScalesMap,\n): SymbolLayerSpecification['layout'] => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const hasCustomIcons = iconTextOffsetScales && iconTextOffsetScales.size > 0;\n\n // Start with base layout\n const baseLayout = { ...layerSpec.layout };\n\n // Remove and recalculate offset properties when custom icons, custom offset, or the circle-icon\n // theme is present. `circle-icon` inherits pin-style offsets from pinLayerBaseSpec which must\n // be replaced with centered offsets.\n const needsOffsetRecalculation =\n hasCustomIcons || textConfig?.offset !== undefined || config?.theme === 'circle-icon';\n\n if (needsOffsetRecalculation) {\n delete baseLayout['text-offset'];\n delete baseLayout['text-variable-anchor-offset'];\n delete baseLayout['text-radial-offset'];\n }\n\n const layout = {\n ...baseLayout,\n ...customLayer?.layout,\n ...(textConfig?.size && { 'text-size': textConfig.size }),\n ...(textConfig?.font && { 'text-font': textConfig.font }),\n ...(textField !== undefined && { 'text-field': textField }),\n };\n\n // Apply offset configuration\n if (needsOffsetRecalculation) {\n // Dynamic offset calculation handles custom icons, custom offset, and circle-icon theme centering\n const iconSize = layout['icon-size'];\n const scales = iconTextOffsetScales ?? new Map();\n return { ...layout, ...getTextOffset(iconSize, scales, config?.theme, textConfig?.offset) };\n }\n\n return layout;\n};\n\n/**\n * Builds the paint configuration with theme-adaptive colors.\n * @ignore\n */\nexport const buildPaintConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: PlaceLayerName,\n lightDark: LightDark,\n): SymbolLayerSpecification['paint'] => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const { textColor: baseTextColor, haloColor: baseHaloColor } = getThemeAdaptiveTextColors(lightDark);\n const basePaint = layerSpec.paint ?? {};\n // Theme-adaptive defaults only fill in when the base layer doesn't already\n // define a color — that way layers carrying their own `text-color` (e.g., the\n // base-map style's category/group-driven expression, or `SELECTED_COLOR` on\n // the selected layer) are preserved. User `text.*` config still wins.\n return {\n ...basePaint,\n ...(basePaint['text-color'] === undefined && { 'text-color': baseTextColor }),\n ...(basePaint['text-halo-color'] === undefined && { 'text-halo-color': baseHaloColor }),\n ...(textConfig?.color && { 'text-color': textConfig.color }),\n ...(textConfig?.haloColor && { 'text-halo-color': textConfig.haloColor }),\n ...(textConfig?.haloWidth && { 'text-halo-width': textConfig.haloWidth }),\n ...customLayer?.paint,\n };\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\n\n/**\n * @ignore\n */\nexport const isClickEventState: ExpressionSpecification = [\n 'in',\n ['get', 'eventState'],\n ['literal', ['click', 'contextmenu']],\n];\n","import { omit } from 'lodash-es';\nimport type { DataDrivenPropertyValueSpecification, Map as MapLibreMap, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { isClickEventState } from '../../shared/layers/eventState';\nimport { ICON_ID, pinLayerBaseSpec, TITLE } from '../../shared/layers/symbolLayers';\n\n/**\n * Replaces placeholders in text size spec with the actual title property.\n * @ignore\n */\nexport const getTextSizeSpec = (\n textSize?: DataDrivenPropertyValueSpecification<number>,\n): DataDrivenPropertyValueSpecification<number> => {\n return JSON.parse(JSON.stringify(textSize)?.replaceAll('name', TITLE));\n};\n\nconst findStyleLayer = (map: MapLibreMap, layerID: string): SymbolLayerSpecification =>\n (map.getStyle().layers.find((layer) => layer.id === layerID) as SymbolLayerSpecification) ?? {};\n\n// Shared foundation for every POI-like layer variant: filter, symbol type, and\n// paint/layout props copied verbatim from the base map style layer so the style's\n// original expressions (e.g., text-color keyed on `category`/`group`) keep working\n// against our GeoJSON features. We strip inherited `visibility` because some styles\n// hide `POI - Micro` by default — our places layer owns visibility via\n// `PlacesModule.applyPlacesVisibility` and must not carry the style's hidden state.\nconst buildBaseMapPOILayerFoundation = (\n poiLayer: SymbolLayerSpecification,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n filter: ['!', isClickEventState],\n type: 'symbol',\n paint: poiLayer.paint,\n layout: poiLayer.layout ? omit(poiLayer.layout, 'visibility') : undefined,\n});\n\n/**\n * Builds a POI-like layer spec that mirrors the base map style's 'POI' layer,\n * overriding the icon image (bound to our `iconID` feature prop) and text field\n * so custom category icons and titles take effect. Paint and remaining layout\n * props — notably `text-color`, which reads `['get','category']`/`['get','group']`\n * — are inherited verbatim from the style.\n * @ignore\n */\nexport const buildBaseMapPOILayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const foundation = buildBaseMapPOILayerFoundation(findStyleLayer(map, 'POI'));\n const textSize = foundation.layout?.['text-size'];\n return {\n ...foundation,\n layout: {\n ...foundation.layout,\n 'icon-image': ['get', ICON_ID],\n 'text-field': ['get', TITLE],\n ...(textSize && { 'text-size': getTextSizeSpec(textSize) }),\n },\n };\n};\n\n// Builds a POI-like layer spec that mirrors the base map style's 'POI - Micro' layer.\n// Inherits the style's `['get','group']`-driven `icon-image` verbatim — custom\n// `PlaceIconConfig.categoryIcons` icons only apply on the `main` layer. Three\n// style-carried properties would otherwise suppress rendering:\n// - `minzoom: 15.5` and `icon-opacity` zoom-interp starting at 0 below 15.5 — our places\n// should show at all zooms, so we drop the zoom gate and force opacity to 1;\n// - collision culling against the `main` POI layer above — bypassed via\n// `icon-allow-overlap` + `icon-ignore-placement`.\nexport const buildPOIMicroLikeLayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const foundation = buildBaseMapPOILayerFoundation(findStyleLayer(map, 'POI - Micro'));\n return {\n ...foundation,\n minzoom: 0,\n layout: {\n ...foundation.layout,\n 'icon-allow-overlap': true,\n 'icon-ignore-placement': true,\n },\n paint: {\n ...foundation.paint,\n 'icon-opacity': 1,\n },\n };\n};\n\n/**\n * Builds a base layer spec for the 'circle-icon' theme.\n * @ignore\n */\nexport const buildCircleBaseLayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const poiLayout = findStyleLayer(map, 'POI').layout ?? {};\n return {\n ...pinLayerBaseSpec,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'icon-anchor': 'center',\n ...(poiLayout['icon-size'] !== undefined && { 'icon-size': poiLayout['icon-size'] }),\n ...(poiLayout['icon-padding'] !== undefined && { 'icon-padding': poiLayout['icon-padding'] }),\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n 'icon-translate': [0, 0],\n },\n };\n};\n","import type {\n CircleLayerSpecification,\n ExpressionSpecification,\n Map as MapLibreMap,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport { LayerSpecTemplate, LightDark, mapStyleLayerIDs } from '../../shared';\nimport { SELECTED_PIN_ICON_SIZE } from '../../shared/layers/commonLayerProps';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport type { PlaceLayerName, PlaceLayersConfig, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport { buildCustomIconScalesMap, type IconScalesMap } from '../utils/customIconScales';\nimport { buildLayoutConfig, buildPaintConfig, buildTextFieldExpression } from '../utils/layerConfiguration';\nimport {\n buildBaseMapPOILayerSpec,\n buildCircleBaseLayerSpec,\n buildPOIMicroLikeLayerSpec,\n} from '../utils/layerSpecBuilders';\nimport {\n buildClusterBadgeCircleLayerSpec,\n buildClusterCountTextLayerSpec,\n buildClusterMixedBadgeCircleLayerSpec,\n buildClusterMixedCountTextLayerSpec,\n buildClusterPinLayerSpec,\n isNotClusterFilter,\n} from './clusterLayers';\n\n/**\n * @ignore\n */\nexport const hasEventState: ExpressionSpecification = ['has', 'eventState'];\n\n// A minimal placeholder spec used to keep a layer registered but hidden. This mirrors\n// the pattern in `TrafficAreaAnalyticsModule` (toggling `layout.visibility`), so theme\n// switches just flip visibility via `setLayoutProperty` without adding/removing layers.\nconst HIDDEN_LAYER_SPEC: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: { visibility: 'none' },\n};\n\n// Hidden circle-layer placeholder for the cluster badge slot. Like `HIDDEN_LAYER_SPEC`\n// but typed as `circle` because the cluster badge is the only non-symbol layer the\n// places module registers, and `addLayers`/`updateLayersAndSource` reject layer-type\n// changes for an existing id.\nconst HIDDEN_CIRCLE_LAYER_SPEC: LayerSpecTemplate<CircleLayerSpecification> = {\n type: 'circle',\n layout: { visibility: 'none' },\n};\n\n/**\n * @ignore\n */\nexport const SELECTED_COLOR = '#3f9cd9';\n\n/**\n * @ignore\n */\nexport const pinLayerSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...pinLayerBaseSpec,\n filter: ['!', hasEventState],\n};\n\n/**\n * We use an extra layer for highlighted text since it's not easy to enforce z-ordering with icons and text\n * while text has different collision rules.\n * @ignore\n */\nexport const selectedPinLayerSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...pinLayerBaseSpec,\n filter: hasEventState,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'icon-size': SELECTED_PIN_ICON_SIZE,\n 'text-allow-overlap': true,\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n 'text-color': SELECTED_COLOR,\n },\n};\n\n// Per-theme builders: each returns the layer spec for one (theme, role) pair.\n// `buildPlacesLayerSpecs` dispatches to these based on `config.theme`; roles a theme\n// doesn't render fall back to `HIDDEN_LAYER_SPEC` so layers stay registered but\n// invisible (theme switches toggle `visibility` rather than adding/removing layers).\n\n// Derives a `selected` variant from a POI-like base spec: match only on event state,\n// allow text overlap so selected labels stay visible, and paint text in SELECTED_COLOR.\nconst toSelectedPOILikeSpec = (\n base: LayerSpecTemplate<SymbolLayerSpecification>,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...base,\n filter: hasEventState,\n layout: {\n ...base.layout,\n 'text-allow-overlap': true,\n },\n paint: {\n ...base.paint,\n 'text-color': SELECTED_COLOR,\n },\n});\n\nconst buildBaseMapSelectedLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> =>\n toSelectedPOILikeSpec(buildBaseMapPOILayerSpec(mapLibreMap));\n\nconst buildCircleIconMainLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...buildCircleBaseLayerSpec(mapLibreMap),\n filter: ['!', hasEventState],\n});\n\nconst buildCircleIconSelectedLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> =>\n toSelectedPOILikeSpec(buildCircleBaseLayerSpec(mapLibreMap));\n\n// Subset of layer slots that go through the symbol-layer `withConfig` pipeline.\n// Cluster slots are excluded because `clusterBadge` / `clusterMixedBadge` are\n// circle layers and the cluster pin / count / mixed-count layers don't\n// participate in EV availability or custom text expressions.\ntype SymbolPlaceLayerName = Exclude<\n PlaceLayerName,\n 'cluster' | 'clusterBadge' | 'clusterCount' | 'clusterMixedBadge' | 'clusterMixedCount'\n>;\n\n// Compute a text-field override only when user config or EV availability demands it.\n// Otherwise the layerSpec's own text-field passes through untouched — which lets the\n// base-map `micro` layer keep the style's own expression verbatim and avoids redundantly\n// re-setting the default on layers whose base spec already binds `text-field`.\nconst withConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: SymbolPlaceLayerName,\n lightDark: LightDark,\n iconTextOffsetScales?: IconScalesMap,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const evAvailabilityEnabled = config?.evAvailability?.enabled === true;\n\n const textField =\n textConfig?.title && typeof textConfig?.title !== 'function'\n ? textConfig.title\n : evAvailabilityEnabled\n ? buildTextFieldExpression(config, evAvailabilityEnabled)\n : undefined;\n\n return {\n ...layerSpec,\n ...customLayer,\n layout: buildLayoutConfig(layerSpec, config, layerName, textField, iconTextOffsetScales),\n paint: buildPaintConfig(layerSpec, config, layerName, lightDark),\n };\n};\n\n/**\n * Builds layer specifications for places display.\n * @ignore\n */\nexport const buildPlacesLayerSpecs = (\n config: PlacesModuleConfig | undefined,\n mapLibreMap: MapLibreMap,\n styleLightDarkTheme: LightDark,\n instanceIndex: number,\n): PlaceLayersConfig => {\n const iconTextOffsetScales = buildCustomIconScalesMap(config, instanceIndex);\n\n let main: LayerSpecTemplate<SymbolLayerSpecification>;\n let selected: LayerSpecTemplate<SymbolLayerSpecification>;\n // `micro` is always registered so theme switches only update its filter/paint/layout\n // (fast path via `updateLayersAndSource` + `changeLayerProps`) rather than adding or\n // removing a layer. For themes that don't render on `micro` it's kept present but\n // hidden via `layout.visibility = 'none'`.\n let micro: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n\n // Cluster slots: same \"always-registered, toggled via visibility\" pattern as `micro`.\n // Only the `pin-clustered` theme populates them with real specs.\n let cluster: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n let clusterBadge: LayerSpecTemplate<CircleLayerSpecification> = HIDDEN_CIRCLE_LAYER_SPEC;\n let clusterCount: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n let clusterMixedBadge: LayerSpecTemplate<CircleLayerSpecification> = HIDDEN_CIRCLE_LAYER_SPEC;\n let clusterMixedCount: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n\n if (config?.theme === 'base-map') {\n main = { ...buildBaseMapPOILayerSpec(mapLibreMap), minzoom: 10 };\n micro = buildPOIMicroLikeLayerSpec(mapLibreMap);\n selected = buildBaseMapSelectedLayerSpec(mapLibreMap);\n } else if (config?.theme === 'circle-icon') {\n main = buildCircleIconMainLayerSpec(mapLibreMap);\n selected = buildCircleIconSelectedLayerSpec(mapLibreMap);\n } else if (config?.theme === 'pin-clustered') {\n // The pin layers stay the same as the regular `pin` theme but are restricted\n // to non-cluster (singleton) features so a clustered point is not double-drawn\n // as both a cluster pin and an individual pin. The dedicated cluster pin layer\n // renders single-category clusters with that category's base-map sprite;\n // mixed clusters skip the icon and surface a centred count circle via\n // `clusterMixedBadge` + `clusterMixedCount`.\n //\n // The `micro` slot is also populated — same builder the `base-map` theme uses\n // — restricted to non-cluster features so it only renders the un-clustered\n // singletons under the base-map style's POI - Micro layer expressions, blending\n // them with the native base-map POIs at low zoom.\n main = { ...pinLayerSpec, filter: ['all', isNotClusterFilter, ['!', hasEventState]] };\n selected = { ...selectedPinLayerSpec, filter: ['all', isNotClusterFilter, hasEventState] };\n const microBase = buildPOIMicroLikeLayerSpec(mapLibreMap);\n micro = {\n ...microBase,\n filter: microBase.filter\n ? (['all', isNotClusterFilter, microBase.filter] as ExpressionSpecification)\n : isNotClusterFilter,\n };\n cluster = buildClusterPinLayerSpec();\n clusterBadge = buildClusterBadgeCircleLayerSpec();\n clusterCount = buildClusterCountTextLayerSpec();\n clusterMixedBadge = buildClusterMixedBadgeCircleLayerSpec();\n clusterMixedCount = buildClusterMixedCountTextLayerSpec();\n } else {\n // pin (default)\n main = pinLayerSpec;\n selected = selectedPinLayerSpec;\n }\n\n // Layer stacking (bottom → top): micro < main < selected, expressed via\n // `beforeID` so `addLayers` resolves ordering the same way RoutingModule does.\n // PlacesModule rewrites these internal names into instance-scoped layer IDs.\n // Cluster slots stack on top: cluster pin < clusterBadge < clusterCount <\n // clusterMixedBadge < clusterMixedCount, anchored above `selected` so the\n // count text is always legible. The mixed-cluster pair is last so the\n // centred count circle paints on top of the (filtered-out) single-cluster\n // slots without depending on draw-order luck.\n\n return {\n micro: {\n ...withConfig(micro, config, 'micro', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: mapStyleLayerIDs.lowestPlaceLabel,\n },\n main: {\n ...withConfig(main, config, 'main', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: 'selected',\n },\n selected: withConfig(selected, config, 'selected', styleLightDarkTheme, iconTextOffsetScales),\n cluster: {\n ...cluster,\n ...config?.layers?.cluster,\n beforeID: 'clusterBadge',\n },\n clusterBadge: {\n ...clusterBadge,\n ...config?.layers?.clusterBadge,\n beforeID: 'clusterCount',\n },\n clusterCount: {\n ...clusterCount,\n ...config?.layers?.clusterCount,\n beforeID: 'clusterMixedBadge',\n },\n clusterMixedBadge: {\n ...clusterMixedBadge,\n ...config?.layers?.clusterMixedBadge,\n beforeID: 'clusterMixedCount',\n },\n clusterMixedCount: {\n ...clusterMixedCount,\n ...config?.layers?.clusterMixedCount,\n },\n ...config?.layers?.additional,\n };\n};\n","import { SVGIconStyleOptions } from '../../shared';\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { pinSvg } from '../../shared/resources';\n\n/**\n * Default pin for selected images without a specific category on it.\n * @ignore\n */\nexport const defaultPin = (options?: SVGIconStyleOptions): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return svgToImg(pinSvg(options));\n};\n","import { generateId, Place, Places } from '@tomtom-org/maps-sdk/core';\nimport type { Feature, FeatureCollection, LineString } from 'geojson';\nimport type { PlaceConnectionDisplay, PlacesModuleConfig } from '../types/placesModuleConfig';\n\n/**\n * Properties carried by each connection feature once it is on the map.\n * @ignore\n */\nexport type ConnectionFeatureProps = {\n id: string;\n fromId?: string;\n toId?: string;\n label: string;\n};\n\n/**\n * @ignore\n */\nexport type ConnectionFeatureCollection = FeatureCollection<LineString, ConnectionFeatureProps>;\n\n// Looks up an endpoint: a full Place is used directly, a string is resolved against\n// the module's currently shown places. Unknown ids return `undefined` and the whole\n// connection is dropped in `prepareConnectionsForDisplay`.\nconst resolveEndpoint = (endpoint: Place | string, shownPlaces: Places): Place | undefined => {\n if (typeof endpoint !== 'string') return endpoint;\n return shownPlaces.features.find((p) => p.id === endpoint);\n};\n\n/**\n * Turns a list of {@link PlaceConnectionDisplay} into a GeoJSON FeatureCollection of\n * LineStrings, resolving id references against the module's currently shown places and\n * applying the optional label function.\n * @ignore\n */\nexport const prepareConnectionsForDisplay = (\n connections: PlaceConnectionDisplay[],\n shownPlaces: Places,\n config: PlacesModuleConfig = {},\n): ConnectionFeatureCollection => {\n const labelFn = config.connections?.label;\n const features: Feature<LineString, ConnectionFeatureProps>[] = [];\n\n for (const connection of connections) {\n const from = resolveEndpoint(connection.from, shownPlaces);\n const to = resolveEndpoint(connection.to, shownPlaces);\n if (!from || !to) continue;\n\n const id = connection.id ?? generateId();\n const label = labelFn ? labelFn(connection) : '';\n\n features.push({\n type: 'Feature',\n id,\n geometry: {\n type: 'LineString',\n coordinates: [from.geometry.coordinates, to.geometry.coordinates],\n },\n properties: {\n id,\n fromId: typeof connection.from === 'string' ? connection.from : from.id,\n toId: typeof connection.to === 'string' ? connection.to : to.id,\n label,\n },\n });\n }\n\n return { type: 'FeatureCollection', features };\n};\n","// Supported sub-categories for map display pins\n// See: https://github.com/tomtom-internal/mdt-backend-mapbox-gl-js-styles/blob/orbis-preview/src/orbis/sprites/poi_light/config.json\n// For the rest we'll use the main categories (which are the first 4 digits of a category)\nconst supportedPinSubcategories: Set<number> = new Set([\n 7339002, 8099002, 7369004, 7321003, 7376006, 9362003, 9362004, 9160004, 9376007, 7315015, 9376006, 9376002, 7315078,\n 7315149, 9376005, 7372003, 9352045, 9352032, 9361048, 9902003, 9378005, 7383004, 9910004, 7320002, 9362016, 7320003,\n 9362025, 9942002, 9942003, 7380005, 9663003, 9361021, 9379009, 9379004, 7315147, 9376003, 9160002, 9160003, 9352008,\n 9361006, 7389004, 9910006,\n]);\n\n/**\n * @ignore\n */\nexport const toPinImageID = (categoryID: number | undefined): string | undefined => {\n if (!categoryID) {\n return undefined;\n }\n\n // Check if the category ID is in our supported subcategories:\n if (supportedPinSubcategories.has(categoryID)) {\n return categoryID.toString();\n }\n\n // If not, fall back to the main category (first 4 digits of the category ID):\n return categoryID.toString().substring(0, 4);\n};\n","import { generateId, Place, Places, POICategory, poiCategoriesToID } from '@tomtom-org/maps-sdk/core';\nimport { toBaseMapPOICategory, toBaseMapPOIGroup } from '../../pois/util/poiCategoryMapping';\nimport { DEFAULT_BASE_MAP_PLACE_ICON_ID, DEFAULT_PLACE_ICON_ID } from '../../shared/layers/symbolLayers';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { FEATURE_PROPERTY_BASE_MAP_ICON_ID } from '../layers/clusterLayers';\nimport type { DisplayPlaceProps } from '../types/placeDisplayProps';\nimport type { PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\nimport {\n buildAvailabilityText,\n getAvailabilityRatio,\n getEVAvailabilityIconID,\n isEVStationWithAvailability,\n} from './evAvailabilityHelpers';\nimport { toPinImageID } from './toPinImageID';\n\n/**\n * Builds the title of the place to display it on the map.\n * @param place The place to display.\n * @ignore\n */\nexport const buildPlaceTitle = (place: Place): string =>\n place.properties.poi?.name ?? place.properties.address.freeformAddress;\n\n/**\n * Resolves the image ID to use for the given POI category and icon theme.\n */\nconst toImageID = (poiCategory: POICategory, iconTheme: PlacesTheme, defaultPlaceIconID: string): string => {\n // `pin-clustered` uses the same per-category sprites as `pin` for individual\n // (un-clustered) features — only the cluster pin layer renders the default\n // sprite, regardless of the leaves' categories.\n if (iconTheme === 'pin' || iconTheme === 'pin-clustered') {\n const imageID = toPinImageID(poiCategoriesToID[poiCategory]);\n return imageID ?? defaultPlaceIconID;\n } else {\n const imageID = toBaseMapPOICategory(poiCategory);\n return imageID ? `poi-${imageID}` : defaultPlaceIconID;\n }\n};\n\n// When the caller hasn't registered a custom default image for this module\n// instance, the `base-map` theme should fall back to a built-in micro sprite\n// (small dot next to native POI labels) rather than the large `default_place`\n// pin sprite used by the `pin` theme.\nconst resolveDefaultPlaceIconID = (config: PlacesModuleConfig, instanceIndex: number): string => {\n const hasCustomDefault = !!config.icon?.default;\n if (!hasCustomDefault && (config.theme ?? 'pin') === 'base-map') {\n return DEFAULT_BASE_MAP_PLACE_ICON_ID;\n }\n return suffixNumber(DEFAULT_PLACE_ICON_ID, instanceIndex);\n};\n\n/**\n * Gets the map style sprite image ID to display on the map for the give place.\n * @ignore\n */\nexport const getIconIDForPlace = (place: Place, instanceIndex: number, config: PlacesModuleConfig = {}): string => {\n const iconTheme = config.theme ?? 'pin';\n const defaultPlaceIconID = resolveDefaultPlaceIconID(config, instanceIndex);\n\n const imageMapping = config.icon?.mapping;\n // First, try custom mapping if provided:\n if (imageMapping) {\n if (imageMapping.to === 'imageID') {\n // Direct image ID mapping\n return imageMapping.fn(place);\n } else {\n // POI category mapping - resolve category to icon ID\n return toImageID(imageMapping.fn(place), iconTheme, defaultPlaceIconID);\n }\n }\n\n // Next, try to match any custom icon:\n const poiCategory = place.properties.poi?.categories?.[0] as POICategory;\n\n // Check for EV availability-specific icon selection\n const evAvailabilityIconID = getEVAvailabilityIconID(place, poiCategory, instanceIndex, config, iconTheme);\n if (evAvailabilityIconID) {\n return evAvailabilityIconID;\n }\n\n // Regular custom icon matching (no availability)\n const matchingCustomIcon = config.icon?.categoryIcons?.find((customIcon) => customIcon.id === poiCategory);\n if (matchingCustomIcon) {\n return suffixNumber(matchingCustomIcon.id, instanceIndex);\n }\n\n // Else: if no custom icon matched, we map to the map style icons or default:\n return toImageID(poiCategory, iconTheme, defaultPlaceIconID);\n};\n\n// When a user provides `icon.mapping.to === 'poiCategory'`, that function becomes the\n// authoritative source of the place's category — the original `poi.categories[0]` may be\n// missing or unrelated (e.g., user data that isn't a search-API Place). For base-map\n// themes the style's `icon-image` / `text-color` expressions read `category` / `group`,\n// so without honoring the mapping here those expressions resolve to `poi-` and icons\n// fail to load.\nconst getEffectivePOICategory = (place: Place, config?: PlacesModuleConfig): POICategory | undefined => {\n const mapping = config?.icon?.mapping;\n if (mapping?.to === 'poiCategory') {\n return mapping.fn(place);\n }\n return place.properties.poi?.categories?.[0];\n};\n\n/**\n * Maps a Place category to the poi layer one, so the latter's style can apply it.\n * @ignore\n */\nexport const getPOILayerCategoryForPlace = (place: Place, config?: PlacesModuleConfig): string | undefined => {\n const category = getEffectivePOICategory(place, config);\n // if it's one of the different categories between search and poi layer, use poi layer category\n return category && toBaseMapPOICategory(category);\n};\n\n/**\n * Maps a Place to the base map POI group used by the style's `text-color` and\n * `POI - Micro` icon expressions (e.g., `eat_and_drink`, `lodging`, `driving`).\n * @ignore\n */\nexport const getPOIGroupForPlace = (place: Place, config?: PlacesModuleConfig): string | undefined => {\n const category = getEffectivePOICategory(place, config);\n return category && toBaseMapPOIGroup(category);\n};\n\n/**\n * Transforms the input of a \"show\" call to FeatureCollection \"Places\".\n * @ignore\n */\nexport const toPlaces = (places: Place | Place[] | Places): Places => {\n if (Array.isArray(places)) {\n return { type: 'FeatureCollection', features: places };\n }\n return places.type === 'Feature' ? { type: 'FeatureCollection', features: [places] } : places;\n};\n\n/**\n * Merges EV availability props into extraFeatureProps if enabled.\n * This makes EV stations use the same mechanism as any other custom properties.\n * @ignore\n */\nconst mergeEVAvailabilityProps = (\n extraFeatureProps: PlacesModuleConfig['extraFeatureProps'],\n evAvailabilityConfig: PlacesModuleConfig['evAvailability'],\n places: Places,\n): PlacesModuleConfig['extraFeatureProps'] => {\n if (evAvailabilityConfig?.enabled !== true) {\n return extraFeatureProps;\n }\n\n // Check if any EV stations exist but lack availability data\n let hasEVStations = false;\n let hasEVStationsWithAvailability = false;\n\n for (const place of places.features) {\n const isEVStation = place.properties.poi?.categories?.[0] === 'ELECTRIC_VEHICLE_STATION';\n if (isEVStation) {\n hasEVStations = true;\n if (isEVStationWithAvailability(place)) {\n hasEVStationsWithAvailability = true;\n break;\n }\n }\n }\n\n if (hasEVStations && !hasEVStationsWithAvailability) {\n console.warn(\n 'PlacesModule: evAvailability is enabled but no availability data found. ' +\n 'Did you call getPlacesWithEVAvailability()?',\n );\n }\n\n return {\n ...extraFeatureProps,\n evAvailabilityText: (place: Place) =>\n isEVStationWithAvailability(place) ? buildAvailabilityText(place, evAvailabilityConfig) : '',\n evAvailabilityRatio: (place: Place) => (isEVStationWithAvailability(place) ? getAvailabilityRatio(place) : 0),\n };\n};\n\n// Resolves the per-leaf base-map sprite id consumed by the `pin-clustered`\n// theme's cluster pin (single-category case). It's the same\n// `poi-<category>` sprite the `base-map` theme renders for individual\n// places, which lets the cluster icon match the surrounding base-map style\n// when only one category is present. Falls back to the parking-micro\n// sprite when the place has no recognisable category — same fallback the\n// `base-map` theme uses for unmatched places.\nconst getBaseMapIconIDForPlace = (place: Place, config: PlacesModuleConfig | undefined): string => {\n const category = getPOILayerCategoryForPlace(place, config);\n return category ? `poi-${category}` : DEFAULT_BASE_MAP_PLACE_ICON_ID;\n};\n\n/**\n * prepare places features to be displayed on map by adding needed properties for title, icon and style\n * @ignore\n */\nexport const preparePlacesForDisplay = (\n placesInput: Place | Place[] | Places,\n instanceIndex: number,\n config: PlacesModuleConfig = {},\n): Places<DisplayPlaceProps> => {\n const places = toPlaces(placesInput);\n\n // Only merge EV availability props when explicitly enabled\n const mergedExtraFeatureProps =\n config.evAvailability?.enabled === true\n ? mergeEVAvailabilityProps(config.extraFeatureProps, config.evAvailability, places)\n : config.extraFeatureProps;\n\n return {\n ...places,\n features: places.features.map((place) => {\n const title =\n typeof config?.text?.title === 'function' ? config?.text?.title(place) : buildPlaceTitle(place);\n\n const extraFeatureProps = mergedExtraFeatureProps\n ? Object.fromEntries(\n Object.entries(mergedExtraFeatureProps).map(([prop, value]) => [\n prop,\n typeof value === 'function' ? value(place) : value,\n ]),\n )\n : {};\n\n const id = place.id ?? generateId();\n\n return {\n ...place,\n id,\n geometry: { ...place.geometry, bbox: place.bbox },\n properties: {\n ...place.properties,\n id, // we need id in properties due to promoteId feature\n title,\n iconID: getIconIDForPlace(place, instanceIndex, config),\n // The base-map style's POI / POI - Micro layer expressions key off\n // `category` and `group` for icon-image and text-color. Both the\n // `base-map` theme and the `pin-clustered` theme's micro layer\n // (registered for un-clustered singletons) reuse those expressions,\n // so the leaf needs `category`/`group` populated under either theme.\n ...((config?.theme === 'base-map' || config?.theme === 'pin-clustered') && {\n category: getPOILayerCategoryForPlace(place, config),\n group: getPOIGroupForPlace(place, config),\n }),\n // For the `pin-clustered` theme we promote the per-place\n // base-map sprite ID to a top-level feature property so the\n // source's `clusterProperties` aggregator can read it via\n // `['get', ...]` from each leaf feature. `baseMapIconID`\n // resolves to a `poi-<category>` sprite and feeds the cluster pin's\n // single-vs-mixed `match` expression.\n ...(config?.theme === 'pin-clustered' && {\n [FEATURE_PROPERTY_BASE_MAP_ICON_ID]: getBaseMapIconIDForPlace(place, config),\n }),\n ...extraFeatureProps,\n },\n };\n }),\n };\n};\n","import type { Place, Places } from '@tomtom-org/maps-sdk/core';\nimport type { LayerSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type {\n CleanEventStateOptions,\n CleanEventStatesOptions,\n GeoJSONSourceClusterOptions,\n PutEventStateOptions,\n ToBeAddedLayerSpecWithoutSource,\n} from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { DEFAULT_PLACE_ICON_ID } from '../shared/layers/symbolLayers';\nimport { suffixNumber } from '../shared/layers/utils';\nimport { addLayers, addOrUpdateImage, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { clusteredPlacesAggregateProperties } from './layers/clusterLayers';\nimport { buildConnectionLayerSpecs } from './layers/connectionsLayers';\nimport { buildPlacesLayerSpecs } from './layers/placesLayers';\nimport { defaultPin } from './resources';\nimport type { DisplayPlaceProps } from './types/placeDisplayProps';\nimport {\n PlaceConnectionDisplay,\n PlaceIconConfig,\n PlaceLayerName,\n PlacesModuleConfig,\n PlacesTheme,\n PlaceTextConfig,\n} from './types/placesModuleConfig';\nimport type { ConnectionFeatureCollection } from './utils/prepareConnectionsForDisplay';\nimport { prepareConnectionsForDisplay } from './utils/prepareConnectionsForDisplay';\nimport { preparePlacesForDisplay } from './utils/preparePlacesForDisplay';\n\ntype PlacesSourcesAndLayers = {\n /**\n * Places source id with corresponding layers ids. Carries every layer the\n * module owns under non-clustered themes; under `pin-clustered` it carries\n * everything except `micro` and is configured with `cluster: true` so the\n * pin / cluster pin / cluster-badge / cluster-count / mixed-cluster\n * badge + count layers all see aggregated cluster features.\n */\n places: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>;\n /**\n * Companion source — only created under `pin-clustered`. Feeds the `micro`\n * layer with the same place features but **without** clustering, so the\n * tiny base-map-style POI dots render on every place individually\n * regardless of how the larger pins above them get aggregated.\n */\n placesUnclustered?: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>;\n /**\n * Connections source id with corresponding layers ids (line + label).\n */\n connections: GeoJSONSourceWithLayers<ConnectionFeatureCollection>;\n};\n\ntype ConnectionLayerSpecMap = {\n line: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>;\n label: ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>;\n};\n\n/**\n * Map module for displaying and managing place markers.\n *\n * The PlacesModule provides functionality to display location markers (pins) on the map\n * for points of interest, search results, or custom locations. It supports various marker\n * styles, custom icons, text labels, and interactive events.\n *\n * @remarks\n * **Features:**\n * - Multiple marker styles (pin, circle-icon, base-map POI-like)\n * - Custom icons per POI category\n * - Text labels with styling options\n * - Data-driven styling via MapLibre expressions\n * - Interactive events (click, hover, etc.)\n * - Support for custom feature properties\n * - EV charging station availability display (opt-in)\n *\n * **Marker Styles:**\n * - `pin`: Traditional teardrop-shaped map pins\n * - `circle-icon`: Simple circular markers (previously named `circle`)\n * - `base-map`: Mimics the map's built-in POI layer styling (combines `main`, `selected`, and `micro` layers). For micro-only rendering, hide the `main` layer via `layers.main.layout.visibility = 'none'`.\n *\n * **EV Charging Station Availability:**\n * When displaying EV charging stations with availability data from\n * {@link getPlacesWithEVAvailability}, the module can:\n * - Show available/total charging points (e.g., \"3/10\")\n * - Color-code availability (green = good, orange = limited, red = none/low)\n * - Display as formatted text within the station's label\n *\n * This feature is disabled by default. To enable it, set `evAvailability.enabled` to `true`\n * in the configuration.\n *\n * **Common Use Cases:**\n * - Search result visualization\n * - Custom location markers\n * - Store locators\n * - EV charging station maps with real-time availability\n * - Delivery/pickup points\n * - Saved locations display\n *\n * @example\n * ```typescript\n * // Create places module with pin markers\n * const placesModule = await PlacesModule.get(map, {\n * icon: {\n * categoryIcons: []\n * },\n * text: {\n * field: (place) => place.properties.poi?.name || 'Unknown'\n * },\n * theme: 'pin'\n * });\n *\n * // Display places from search\n * await placesModule.show(searchResults);\n *\n * // EV Charging Stations - Opt-in to availability display\n * const evStations = await PlacesModule.get(map, {\n * evAvailability: { enabled: true }\n * });\n * const results = await search({ poiCategories: ['ELECTRIC_VEHICLE_STATION'] });\n * evStations.show(await getPlacesWithEVAvailability(results)); // Shows availability\n *\n * // Granular control: Enable for searched stations only, background stations without\n * const bgStations = await PlacesModule.get(map); // EV availability disabled\n * const searched = await PlacesModule.get(map, {\n * evAvailability: { enabled: true }\n * });\n *\n * // Handle clicks\n * placesModule.events.on('click', (feature) => {\n * console.log('Clicked:', feature.properties);\n * });\n *\n * placesModule.events.on('hover', (feature) => {\n * showTooltip(feature.properties.poi?.name);\n * });\n * ```\n *\n * @see [Places Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/places)\n *\n * @group Places\n */\n// Default radius (in pixels) used by MapLibre when clustering pins for the\n// `pin-clustered` theme. Tuned to the default pin sprite footprint so that\n// adjacent pins are visually merged but cities/blocks aren't collapsed into\n// a single cluster.\nconst DEFAULT_CLUSTER_RADIUS_PX = 60;\n\nexport class PlacesModule extends AbstractMapModule<PlacesSourcesAndLayers, PlacesModuleConfig> {\n // Cluster layers (`clusterBadge` in particular) aren't symbol layers, so the\n // record value is widened to the full layer-spec union.\n private layerSpecs!: Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>;\n private connectionLayerSpecs!: ConnectionLayerSpecMap;\n // Tracks the cluster mode reflected in the currently-mounted source. Used to\n // detect cluster mode toggles in `updateLayersAndData` so we can rebuild the\n // source (MapLibre fixes `cluster` at source-creation time).\n private currentClusterMode = false;\n private sourceID!: string;\n // Companion source id used by the `pin-clustered` theme to back the\n // un-clustered `micro` layer. Allocated alongside `sourceID` on first init\n // so it stays stable across rebuilds.\n private unclusteredSourceID!: string;\n private connectionSourceID!: string;\n private layerIDPrefix!: string;\n private defaultPlaceIconID!: string;\n // Assigned in `_initSourcesWithLayers` (which runs before `_applyConfig` during the\n // base-class constructor). A class-field initializer would be too late: it runs\n // after `super()`, by which point `_applyConfig` has already read this field.\n private shownConnections!: PlaceConnectionDisplay[];\n private readonly shownFeaturesHandlers: ((features: Place | Place[] | Places) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n */\n static async get(tomtomMap: TomTomMap, config?: PlacesModuleConfig): Promise<PlacesModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new PlacesModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: PlacesModuleConfig) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: PlacesModuleConfig, restore?: boolean): PlacesSourcesAndLayers {\n // `instanceIndex` is auto-assigned by AbstractMapModule on construction; nothing\n // to bump on restore (we keep the same IDs across style changes).\n if (!restore) {\n this.sourceID = `places-${this.instanceIndex}`;\n this.unclusteredSourceID = `places-${this.instanceIndex}-unclustered`;\n this.connectionSourceID = `places-connections-${this.instanceIndex}`;\n this.layerIDPrefix = this.sourceID;\n this.defaultPlaceIconID = suffixNumber(DEFAULT_PLACE_ICON_ID, this.instanceIndex);\n this.shownConnections = [];\n }\n\n // Update each layer id with the instance-specific prefix\n this.layerSpecs = this.buildLayerSpecs(config);\n\n const clusterOptions = this.buildClusterOptions(config);\n this.currentClusterMode = clusterOptions?.cluster === true;\n\n // Under `pin-clustered` we split the layer specs across two sources:\n // - clustered (`places`): every layer that needs aggregated cluster\n // features (main pin, selected, cluster pin, cluster badge,\n // cluster count, mixed-cluster badge, mixed-cluster count)\n // - unclustered (`placesUnclustered`): just `micro`, so the small\n // base-map-style POI dots render per place even when the bigger\n // pins above them collapse into clusters\n // For all other themes there's only one source; the second one stays\n // `undefined` to keep the surface area minimal.\n const splitSpecs = this.partitionSpecsForSources(this.layerSpecs);\n\n // Defer the built-in layer add (`false`) — our specs use `beforeID` stacking\n // (`micro` → `main` → `selected` → `cluster` → `clusterBadge` →\n // `clusterCount` → `clusterMixedBadge` → `clusterMixedCount`)\n // which `ensureLayersAddedToMap` can't resolve topologically. `addLayers` walks\n // the dependency order instead; running it at init keeps the layers on the map\n // before `updateLayersAndSource` touches them.\n const places = new GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>(\n this.mapLibreMap,\n this.sourceID,\n splitSpecs.clusteredSpecs,\n false,\n clusterOptions,\n );\n\n let placesUnclustered: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>> | undefined;\n if (splitSpecs.unclusteredSpecs.length > 0) {\n placesUnclustered = new GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>(\n this.mapLibreMap,\n this.unclusteredSourceID,\n splitSpecs.unclusteredSpecs,\n false,\n );\n }\n\n // Both source's layers must be on the map before `addLayers` resolves\n // any cross-spec `beforeID` dependencies. The combined list is what\n // existing layers (`cluster`, the row, etc.) anchor against, so feed\n // them in together.\n addLayers([...places._layerSpecs, ...(placesUnclustered?._layerSpecs ?? [])], this.mapLibreMap);\n\n // Connections are added after places so that their `beforeID` anchor (the\n // places `main` layer) already exists on the map — MapLibre then inserts\n // connection layers below places, keeping pins visible on top of the lines.\n this.connectionLayerSpecs = this.buildConnectionLayerSpecs(config);\n const connections = new GeoJSONSourceWithLayers<ConnectionFeatureCollection>(\n this.mapLibreMap,\n this.connectionSourceID,\n Object.values(this.connectionLayerSpecs),\n false,\n );\n addLayers(connections._layerSpecs, this.mapLibreMap);\n\n // Only include `placesUnclustered` when it actually exists — the base\n // class iterates every entry to derive `sourceAndLayerIDs`, so an\n // undefined value would crash on themes that don't use the companion.\n return placesUnclustered ? { places, placesUnclustered, connections } : { places, connections };\n }\n\n // Splits the per-name layer specs into the clustered and unclustered\n // buckets. Under `pin-clustered` the `micro` slot belongs to the\n // unclustered bucket so its base-map-style sprites render per leaf\n // regardless of how the cluster aggregator collapses points; every other\n // theme leaves the unclustered bucket empty (single-source mode).\n private partitionSpecsForSources(\n layerSpecs: Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>,\n ): {\n clusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[];\n unclusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[];\n } {\n if (!this.currentClusterMode) {\n return { clusteredSpecs: Object.values(layerSpecs), unclusteredSpecs: [] };\n }\n const unclusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[] = [];\n const clusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[] = [];\n for (const [name, spec] of Object.entries(layerSpecs)) {\n if (name === 'micro') unclusteredSpecs.push(spec);\n else clusteredSpecs.push(spec);\n }\n return { clusteredSpecs, unclusteredSpecs };\n }\n\n private buildLayerSpecs(\n config?: PlacesModuleConfig,\n ): Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>> {\n const layerSpecTemplates = buildPlacesLayerSpecs(\n config,\n this.tomtomMap.mapLibreMap,\n this.tomtomMap.styleLightDarkTheme,\n this.instanceIndex,\n );\n const internalNames = new Set(Object.keys(layerSpecTemplates));\n\n // Rewrite internal layer IDs and any `beforeID` that refers to another\n // internal layer (e.g., `main`, `selected`) into instance-scoped IDs.\n return Object.fromEntries(\n Object.entries(layerSpecTemplates).map(([key, spec]) => {\n const beforeID = (spec as { beforeID?: string }).beforeID;\n return [\n key,\n {\n ...spec,\n id: `${this.layerIDPrefix}-${key}`,\n ...(beforeID && internalNames.has(beforeID)\n ? { beforeID: `${this.layerIDPrefix}-${beforeID}` }\n : {}),\n },\n ];\n }),\n ) as Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>;\n }\n\n // Returns the cluster options to apply on the GeoJSON source for the given\n // theme, or `undefined` to leave clustering off entirely. Only the\n // `pin-clustered` theme opts in.\n private buildClusterOptions(config?: PlacesModuleConfig): GeoJSONSourceClusterOptions | undefined {\n if (config?.theme !== 'pin-clustered') return undefined;\n return {\n cluster: true,\n clusterRadius: DEFAULT_CLUSTER_RADIUS_PX,\n clusterProperties: clusteredPlacesAggregateProperties,\n };\n }\n\n private buildConnectionLayerSpecs(config?: PlacesModuleConfig): ConnectionLayerSpecMap {\n const specs = buildConnectionLayerSpecs(config?.connections);\n const lineID = `${this.layerIDPrefix}-connection-line`;\n const labelID = `${this.layerIDPrefix}-connection-label`;\n // Stack connections under the base-map label layer (`lowestLabel`) so city /\n // street labels and place pins all render on top. `addLayers` resolves\n // `beforeID` in dependency order; two layers sharing the same anchor end up\n // stacked in insertion order (line first → label on top).\n const anchor = mapStyleLayerIDs.lowestLabel;\n return {\n line: { ...specs.line, id: lineID, beforeID: anchor },\n label: { ...specs.label, id: labelID, beforeID: anchor },\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: PlacesModuleConfig | undefined) {\n this.updateLayersAndData(config);\n return config;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previousShownFeatures = this.sourcesWithLayers.places.shownFeatures;\n const previousConnections = this.shownConnections;\n this.initSourcesWithLayers(this.config, true);\n this.config && this._applyConfig(this.config);\n this.show(previousShownFeatures);\n if (previousConnections.length) {\n this.showConnections(previousConnections);\n }\n }\n\n /**\n * Updates the visual theme for displayed places.\n *\n * @param theme - The theme style to apply to place markers.\n *\n * @remarks\n * **Available Themes:**\n * - `pin`: Traditional teardrop-shaped map pins\n * - `circle-icon`: Simple circular markers (previously named `circle`)\n * - `base-map`: Mimics the map's built-in POI layer style with category icons (combines `main`, `selected`, and `micro`). For micro-only rendering, hide the `main` layer via `layers.main.layout.visibility = 'none'`.\n *\n * Changes apply immediately to all currently shown places. Other configuration\n * properties (icon config, text config) remain unchanged.\n *\n * @example\n * ```typescript\n * // Switch to pin markers\n * placesModule.applyTheme('pin');\n *\n * // Use simple circles\n * placesModule.applyTheme('circle-icon');\n *\n * // Match map's POI style (ideal to blend in)\n * placesModule.applyTheme('base-map');\n * ```\n */\n applyTheme(theme: PlacesTheme): void {\n this.applyConfigPart({ theme });\n }\n\n /**\n * Updates the icon configuration for displayed places.\n *\n * @param iconConfig - New icon configuration settings.\n *\n * @remarks\n * - Changes apply immediately to currently shown places\n * - Custom icons are loaded if not already in style\n * - Other configuration properties remain unchanged\n *\n * @example\n * ```typescript\n * placesModule.applyIconConfig({\n * categoryIcons: [\n * { category: 'RESTAURANT', id: 'restaurant-icon', image: '/icons/food.png' }\n * ]\n * });\n * ```\n */\n applyIconConfig(iconConfig: PlaceIconConfig): void {\n this.applyConfigPart({ icon: iconConfig });\n }\n\n /**\n * Updates the text/label configuration for displayed places.\n *\n * @param textConfig - New text configuration settings.\n *\n * @remarks\n * Supports both functions and MapLibre expressions for dynamic text.\n *\n * @example\n * ```typescript\n * // Use function\n * placesModule.applyTextConfig({\n * field: (place) => place.properties.poi?.name || 'Unknown'\n * });\n *\n * // Use MapLibre expression\n * placesModule.applyTextConfig({\n * field: ['get', 'title'],\n * size: 14,\n * color: '#333'\n * });\n * ```\n */\n applyTextConfig(textConfig: PlaceTextConfig): void {\n this.applyConfigPart({ text: textConfig });\n }\n\n private applyConfigPart(partialConfig: Partial<PlacesModuleConfig>): void {\n const config = { ...this.config, ...partialConfig };\n this.updateLayersAndData(config);\n this.config = config;\n this.emitConfigChange();\n }\n\n /**\n * Applies additional feature properties to displayed places.\n *\n * @param extraFeatureProps - Object mapping property names to values or functions.\n *\n * @remarks\n * Useful for adding computed properties or metadata for styling/filtering.\n *\n * @example\n * ```typescript\n * placesModule.applyExtraFeatureProps({\n * category: (place) => place.properties.poi?.localizedCategories?.[0],\n * rating: (place) => place.properties.poi?.rating || 0,\n * isOpen: true\n * });\n * ```\n */\n applyExtraFeatureProps(extraFeatureProps: { [key: string]: any }): void {\n const config = { ...this.config, extraFeatureProps };\n this.updateData(config);\n this.config = config;\n this.emitConfigChange();\n }\n\n private updateLayersAndData(config: PlacesModuleConfig | undefined): void {\n this.setupImages(config);\n\n // Toggling clustering on/off requires recreating the GeoJSON source — MapLibre\n // fixes the `cluster` flag at source-creation time. The fast `updateLayersAndSource`\n // path can't help here, so we tear the module down and rebuild it from scratch via\n // the same flow used for style restorations.\n const nextClusterMode = this.buildClusterOptions(config)?.cluster === true;\n if (nextClusterMode !== this.currentClusterMode) {\n this.rebuildSourcesAndLayers(config);\n return;\n }\n\n const newLayerSpecs = this.buildLayerSpecs(config);\n // The two-source pin-clustered layout means specs per source can no\n // longer change in lock-step here: a layer's `source` is fixed at add\n // time, and partitioning re-evaluates it. Within a single cluster\n // mode the assignment is stable (clustered: everything-but-micro;\n // unclustered: micro), so we can update each source's specs in place.\n const newSplit = this.currentClusterMode\n ? this.partitionSpecsForSources(newLayerSpecs)\n : { clusteredSpecs: Object.values(newLayerSpecs), unclusteredSpecs: [] };\n const oldSplit = this.currentClusterMode\n ? this.partitionSpecsForSources(this.layerSpecs)\n : { clusteredSpecs: Object.values(this.layerSpecs), unclusteredSpecs: [] };\n updateLayersAndSource(\n newSplit.clusteredSpecs,\n oldSplit.clusteredSpecs,\n this.sourcesWithLayers.places,\n this.mapLibreMap,\n );\n if (this.sourcesWithLayers.placesUnclustered && newSplit.unclusteredSpecs.length > 0) {\n updateLayersAndSource(\n newSplit.unclusteredSpecs,\n oldSplit.unclusteredSpecs,\n this.sourcesWithLayers.placesUnclustered,\n this.mapLibreMap,\n );\n }\n this.layerSpecs = newLayerSpecs;\n this.applyPlacesVisibility();\n this.updateData(config);\n this.updateConnectionLayersAndData(config);\n }\n\n // Rebuilds the underlying source + layers when the cluster flag changes.\n // Mirrors `restoreDataAndConfigImpl` (used on style reload) but without the\n // recursive `_applyConfig` call — the caller is *already* applying config.\n private rebuildSourcesAndLayers(config: PlacesModuleConfig | undefined): void {\n const previousShownFeatures = this.sourcesWithLayers.places.shownFeatures;\n const previousConnections = this.shownConnections;\n for (const spec of Object.values(this.layerSpecs)) {\n if (this.mapLibreMap.getLayer(spec.id)) this.mapLibreMap.removeLayer(spec.id);\n }\n for (const spec of Object.values(this.connectionLayerSpecs)) {\n if (this.mapLibreMap.getLayer(spec.id)) this.mapLibreMap.removeLayer(spec.id);\n }\n if (this.mapLibreMap.getSource(this.sourceID)) this.mapLibreMap.removeSource(this.sourceID);\n // The unclustered companion only exists under `pin-clustered`; on\n // teardown we always check before removing so leaving and re-entering\n // the theme doesn't leave a stale source on the map.\n if (this.mapLibreMap.getSource(this.unclusteredSourceID)) {\n this.mapLibreMap.removeSource(this.unclusteredSourceID);\n }\n if (this.mapLibreMap.getSource(this.connectionSourceID)) this.mapLibreMap.removeSource(this.connectionSourceID);\n\n this.initSourcesWithLayers(config, true);\n this.setupImages(config);\n const prepared = preparePlacesForDisplay(previousShownFeatures, this.instanceIndex, config);\n // Opt out of `setLayersVisible` inside `show` — it would mark every\n // layer in the source as visible, defeating the spec-level\n // `visibility: 'none'` we use to keep theme-hidden cluster slots\n // (badge / count / mixed badge / mixed count) from leaking onto\n // themes that don't cluster. `applyPlacesVisibility` runs after and\n // honours those flags.\n this.sourcesWithLayers.places.show(prepared, { automaticVisibility: false });\n this.sourcesWithLayers.placesUnclustered?.show(prepared, { automaticVisibility: false });\n this.applyPlacesVisibility();\n if (previousConnections.length) {\n this.shownConnections = [...previousConnections];\n this.updateConnectionsData(config);\n }\n }\n\n private updateConnectionLayersAndData(config: PlacesModuleConfig | undefined): void {\n const newSpecs = this.buildConnectionLayerSpecs(config);\n const connectionsSource = this.sourcesWithLayers.connections;\n updateLayersAndSource(\n Object.values(newSpecs),\n Object.values(this.connectionLayerSpecs),\n connectionsSource,\n this.mapLibreMap,\n );\n this.connectionLayerSpecs = newSpecs;\n this.updateConnectionsData(config);\n }\n\n private updateConnectionsData(config: PlacesModuleConfig | undefined): void {\n const shownPlaces = this.sourcesWithLayers.places.shownFeatures;\n this.sourcesWithLayers.connections.show(\n prepareConnectionsForDisplay(this.shownConnections, shownPlaces, config),\n );\n }\n\n // Data-driven \"visible iff any features\" — but skip specs carrying\n // `visibility: 'none'` so theme-hidden slots (e.g., `micro` on pin/circle-icon) and\n // caller-hidden layers (via `layers.*`) stay hidden. We iterate `this.layerSpecs`\n // rather than `placesSource._layerSpecs` because the latter is stale for updated\n // layers after a theme switch.\n //\n // The two-source split under `pin-clustered` doesn't change the visibility\n // contract: every layer's id still appears in `this.layerSpecs`, so a\n // single sweep here flips visibility correctly across both sources.\n private applyPlacesVisibility(): void {\n const hasData = !!this.sourcesWithLayers.places.shownFeatures.features.length;\n const visibility = hasData ? 'visible' : 'none';\n for (const spec of Object.values(this.layerSpecs)) {\n if (spec.layout?.visibility === 'none') {\n continue;\n }\n this.mapLibreMap.setLayoutProperty(spec.id, 'visibility', visibility, { validate: false });\n }\n }\n\n private setupImages(config: PlacesModuleConfig | undefined): void {\n // Ensure default pin is added:\n if (config?.icon) {\n // If we have custom icons, ensure they're added to the map style:\n for (const customIcon of config.icon.categoryIcons ?? []) {\n // Create unique ID for each custom icon, including availability level if present\n const iconID = customIcon.availabilityLevel\n ? `${customIcon.id}-${customIcon.availabilityLevel}`\n : customIcon.id;\n\n addOrUpdateImage(\n 'if-not-in-sprite',\n suffixNumber(iconID, this.instanceIndex),\n customIcon.image as string | HTMLImageElement,\n this.mapLibreMap,\n {\n pixelRatio: customIcon.pixelRatio ?? 2,\n },\n );\n }\n\n if (config.icon.default) {\n if (config.icon.default.image) {\n addOrUpdateImage(\n 'if-not-in-sprite',\n this.defaultPlaceIconID,\n config.icon.default.image.image as string | HTMLImageElement,\n this.mapLibreMap,\n {\n pixelRatio: config.icon.default.image.pixelRatio ?? 2,\n },\n );\n }\n if (config.icon.default.style) {\n addOrUpdateImage(\n 'if-not-in-sprite',\n this.defaultPlaceIconID,\n defaultPin(config.icon.default.style),\n this.mapLibreMap,\n { pixelRatio: 2 },\n );\n }\n }\n } else {\n // Ensure default pin is added:\n addOrUpdateImage('if-not-in-sprite', this.defaultPlaceIconID, defaultPin(), this.mapLibreMap, {\n pixelRatio: 2,\n });\n }\n }\n\n private updateData(config: PlacesModuleConfig | undefined): void {\n const prepared = preparePlacesForDisplay(\n this.sourcesWithLayers.places.shownFeatures,\n this.instanceIndex,\n config,\n );\n this.sourcesWithLayers.places.source.runtimeSource?.setData(prepared);\n // Mirror the same data into the unclustered companion (if any) so the\n // micro layer stays in sync with the rest of the module.\n this.sourcesWithLayers.placesUnclustered?.source.runtimeSource?.setData(prepared);\n }\n\n /**\n * Displays the given places on the map.\n *\n * @param places - Place data to display. Can be a single Place, array of Places,\n * or a Places FeatureCollection.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown places\n * - Applies current module styling configuration\n * - Automatically generates labels if text config is set\n * - Waits for module to be ready before displaying\n *\n * **Data Sources:**\n * - TomTom Search API results\n * - Custom place objects matching the Place interface\n * - GeoJSON Point features\n *\n * @example\n * Display search results:\n * ```typescript\n * import { search } from '@tomtom-international/maps-sdk-js/services';\n *\n * const results = await search({ query: 'coffee' });\n * await placesModule.show(results);\n * ```\n *\n * @example\n * Display single place:\n * ```typescript\n * await placesModule.show({\n * type: 'Feature',\n * geometry: { type: 'Point', coordinates: [4.9041, 52.3676] },\n * properties: {\n * address: { freeformAddress: 'Amsterdam' },\n * poi: { name: 'Amsterdam Central' }\n * }\n * });\n * ```\n *\n * @example\n * Display multiple places:\n * ```typescript\n * await placesModule.show([place1, place2, place3]);\n * ```\n */\n async show(places: Place | Place[] | Places) {\n await this.waitUntilModuleReady();\n // Calling `show` swaps the underlying place set, which invalidates any\n // previously-shown connections (their endpoints may no longer exist). Clear\n // them before showing new places so stale lines don't linger.\n this.clearConnectionsSync();\n const prepared = preparePlacesForDisplay(places, this.instanceIndex, this.config);\n // Opt out of the blanket visibility flip — `applyPlacesVisibility` honors\n // spec-level `visibility: 'none'` so theme-hidden layers stay hidden.\n this.sourcesWithLayers.places.show(prepared, { automaticVisibility: false });\n // Mirror into the unclustered companion (only present under\n // `pin-clustered`) so the `micro` layer renders the same place set.\n this.sourcesWithLayers.placesUnclustered?.show(prepared, { automaticVisibility: false });\n this.applyPlacesVisibility();\n for (const handler of this.shownFeaturesHandlers) {\n handler(places);\n }\n }\n\n /**\n * Removes all places from the map.\n *\n * @remarks\n * - Clears all displayed places\n * - Does not reset styling configuration\n * - Module remains initialized and ready for new data\n *\n * @example\n * ```typescript\n * await placesModule.clear();\n * ```\n */\n async clear() {\n await this.waitUntilModuleReady();\n this.clearConnectionsSync();\n this.sourcesWithLayers.places.clear();\n this.sourcesWithLayers.placesUnclustered?.clear();\n }\n\n /**\n * Displays connection lines between pairs of places on the map.\n *\n * @param connections - List of connections to render. Each connection has a `from`\n * and `to` endpoint, expressed either as a {@link Place} or as the id of a place\n * currently shown by this module.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown connections\n * - Connections whose id reference cannot be resolved against the currently shown\n * places are silently skipped\n * - When {@link PlaceConnectionsConfig.label} is set on the module config, the\n * returned string is rendered as a label along each line, slightly offset to\n * the side. Without a label function, no label is shown.\n * - Calling `show()` or `clear()` pre-clears connections, since their endpoints\n * may no longer be on the map.\n *\n * @example\n * ```typescript\n * const places = await PlacesModule.get(map, {\n * connections: {\n * label: (c) => `${c.distanceMeters} m`\n * }\n * });\n * await places.show([station, ...cafes]);\n * await places.showConnections(\n * cafes.features.map((cafe) => ({\n * from: station.id,\n * to: cafe.id,\n * distanceMeters: Math.round(cafe.properties.dist ?? 0),\n * })),\n * );\n * ```\n */\n async showConnections(connections: PlaceConnectionDisplay[]) {\n await this.waitUntilModuleReady();\n this.shownConnections = [...connections];\n this.updateConnectionsData(this.config);\n }\n\n /**\n * Removes all connection lines from the map, leaving displayed places intact.\n *\n * @example\n * ```typescript\n * await placesModule.clearConnections();\n * ```\n */\n async clearConnections() {\n await this.waitUntilModuleReady();\n this.clearConnectionsSync();\n }\n\n private clearConnectionsSync(): void {\n this.shownConnections = [];\n this.sourcesWithLayers.connections?.clear();\n }\n\n /**\n * Returns the currently shown places.\n *\n * @returns The places currently displayed on the map.\n *\n * @remarks\n * Returns the exact data that was passed to the `show()` method.\n *\n * @example\n * ```typescript\n * const shown = placesModule.getShown();\n * console.log(`Showing ${shown.places.features.length} places`);\n * ```\n */\n getShown() {\n return {\n places: this.sourcesWithLayers.places.shownFeatures,\n connections: [...this.shownConnections],\n };\n }\n\n /**\n * Programmatically sets an event state on a specific place.\n *\n * @param options - Configuration for the event state to apply.\n *\n * @remarks\n * Use this to make places appear clicked or hovered programmatically.\n *\n * @example\n * ```typescript\n * // Make first place appear clicked\n * placesModule.putEventState({\n * index: 0,\n * state: 'click',\n * mode: 'put'\n * });\n * ```\n */\n putEventState(options: PutEventStateOptions) {\n this.sourcesWithLayers.places.putEventState(options);\n }\n\n /**\n * Removes an event state from a specific place.\n *\n * @param options - Configuration for which event state to remove.\n *\n * @example\n * ```typescript\n * placesModule.cleanEventState({ index: 0 });\n * ```\n */\n cleanEventState(options: CleanEventStateOptions): void {\n this.sourcesWithLayers.places.cleanEventState(options);\n }\n\n /**\n * Removes event states from multiple places.\n *\n * @param options - Optional filter for which states to remove.\n *\n * @example\n * ```typescript\n * // Remove all event states\n * placesModule.cleanEventStates();\n *\n * // Remove only hover states\n * placesModule.cleanEventStates({ states: ['hover'] });\n * ```\n */\n cleanEventStates(options?: CleanEventStatesOptions) {\n this.sourcesWithLayers.places.cleanEventStates(options);\n }\n\n get events(): CombinedEvents<Place<DisplayPlaceProps>, PlacesModuleConfig, Place | Place[] | Places> {\n return new CombinedEvents(\n new UserEvents<Place<DisplayPlaceProps>>(\n this.eventsProxy,\n this.sourcesWithLayers.places,\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n}\n","import type { ExpressionFilterSpecification, FilterSpecification, LegacyFilterSpecification } from 'maplibre-gl';\nimport type { FilterShowMode, FilterSyntaxVersion, MultiSyntaxFilter, ValuesFilter } from './types';\n\n/**\n * @ignore\n */\nconst isExpressionFilter = (filter: FilterSpecification): filter is ExpressionFilterSpecification => {\n if (filter === true || filter === false) {\n return true;\n }\n\n if (!Array.isArray(filter) || filter.length === 0) {\n return false;\n }\n switch (filter[0]) {\n case 'has':\n return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type';\n\n case 'in':\n return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2]));\n\n case '!in':\n case '!has':\n case 'none':\n return false;\n\n case '==':\n case '!=':\n case '>':\n case '>=':\n case '<':\n case '<=':\n return filter.length !== 3 || Array.isArray(filter[1]) || Array.isArray(filter[2]);\n\n case 'any':\n case 'all':\n for (const f of filter.slice(1)) {\n if (!isExpressionFilter(f as FilterSpecification) && typeof f !== 'boolean') {\n return false;\n }\n }\n return true;\n\n default:\n return true;\n }\n};\n\n/**\n * @ignore\n */\nexport const getSyntaxVersion = (expression: FilterSpecification): FilterSyntaxVersion =>\n isExpressionFilter(expression) ? 'expression' : 'legacy';\n\n/**\n * @ignore\n */\nexport const getMergedAnyFilter = (filters: MultiSyntaxFilter[]): MultiSyntaxFilter | null => {\n if (!filters?.length) {\n return null;\n }\n if (filters.length === 1) {\n return filters[0];\n }\n return {\n expression: ['any', ...filters.map((filter) => filter?.expression)],\n legacy: ['any', ...filters.map((filter) => filter?.legacy)],\n };\n};\n\n/**\n * @ignore\n */\nexport const getMergedAllFilter = (\n filterToAdd: MultiSyntaxFilter,\n originalFilter: FilterSpecification | undefined,\n): FilterSpecification => {\n if (originalFilter) {\n return ['all', filterToAdd[getSyntaxVersion(originalFilter)], originalFilter] as FilterSpecification;\n }\n return filterToAdd.expression;\n};\n\n/**\n * @ignore\n */\nexport const buildMappedValuesFilter = <T>(\n propName: string,\n showMode: FilterShowMode,\n values: T[],\n): MultiSyntaxFilter => {\n if (values.length === 1) {\n const comparator = showMode === 'only' ? '==' : '!=';\n return {\n expression: [comparator, ['get', propName], values[0]] as ExpressionFilterSpecification,\n legacy: [comparator, propName, values[0]] as LegacyFilterSpecification,\n };\n }\n const filterArrayNew = ['in', ['get', propName], ['literal', values]];\n if (showMode === 'only') {\n return {\n expression: filterArrayNew as ExpressionFilterSpecification,\n legacy: ['in', propName, ...values] as LegacyFilterSpecification,\n };\n }\n return {\n expression: ['!', filterArrayNew as ExpressionFilterSpecification],\n legacy: ['!in', propName, ...values] as LegacyFilterSpecification,\n };\n};\n\n/**\n * @ignore\n */\nexport const buildValuesFilter = <T>(\n propName: string,\n filter: ValuesFilter<T>,\n valuesMapping?: (value: T) => unknown,\n): MultiSyntaxFilter =>\n buildMappedValuesFilter(propName, filter.show, valuesMapping ? filter.values.map(valuesMapping) : filter.values);\n","import type { MapStylePOICategory } from '../../places';\n\n/**\n * Predefined groups of related POI categories for convenient filtering.\n *\n * @remarks\n * This object maps group names to arrays of {@link MapStylePOICategory} values.\n * Each group contains POI categories that share a common theme or purpose,\n * making it easier to filter multiple related POI types with a single identifier.\n *\n * **Available Groups:**\n * - `FOOD_DRINKS_GROUP` - Dining and beverage establishments (restaurants, cafes, pubs, etc.)\n * - `SHOPPING_GROUP` - Retail stores and shopping centers (shops, markets, supermarkets, etc.)\n * - `TRANSPORTATION_GROUP` - Transportation hubs and stops (airports, stations, terminals, etc.)\n * - `HEALTH_GROUP` - Healthcare facilities and services (hospitals, clinics, pharmacies, etc.)\n * - `PARKING_GROUP` - Parking facilities (garages and open parking areas)\n * - `HOLIDAY_TOURISM_GROUP` - Tourist attractions and recreational sites (museums, parks, beaches, etc.)\n * - `EV_CHARGING_STATIONS_GROUP` - Electric vehicle charging locations\n * - `GAS_STATIONS_GROUP` - Fuel stations for traditional vehicles\n * - `ACCOMMODATION_GROUP` - Lodging facilities (hotels, motels, camping grounds, etc.)\n * - `ENTERTAINMENT_GROUP` - Entertainment venues (cinemas, theaters, nightlife, casinos, etc.)\n * - `SPORTS_LEISURE_GROUP` - Sports and leisure facilities (stadiums, gyms, pools, golf courses, etc.)\n * - `EDUCATION_GROUP` - Educational institutions (schools, universities, libraries, etc.)\n * - `GOVERNMENT_GROUP` - Government and public safety facilities (offices, courts, embassies, police, fire stations)\n *\n * @example\n * Filter to show only food-related POIs:\n * ```ts\n * import { poiCategoryGroups } from '@tomtom-international/maps-sdk-js/map';\n *\n * const foodCategories = poiCategoryGroups.FOOD_DRINKS_GROUP;\n * console.log(foodCategories);\n * // ['RESTAURANT', 'FAST_FOOD', 'CAFE_PUB', 'PUB', 'WINERY', ...]\n * ```\n *\n * @example\n * Use with POI module filtering:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'show',\n * values: ['FOOD_DRINKS_GROUP', 'ENTERTAINMENT_GROUP']\n * }\n * });\n * ```\n *\n * @see {@link POICategoryGroup} - Type representing all available group names\n * @see {@link MapStylePOICategory} - Individual POI category identifiers\n *\n * @group POIs\n */\nexport const poiCategoryGroups: Record<string, MapStylePOICategory[]> = {\n FOOD_DRINKS_GROUP: [\n 'RESTAURANT',\n 'FAST_FOOD',\n 'CAFE_PUB',\n 'PUB',\n 'WINERY',\n 'PUB_FOOD',\n 'SOUL_FOOD',\n 'DELICATESSEN',\n ],\n SHOPPING_GROUP: [\n 'SHOP',\n 'SHOPPING_CENTER',\n 'CLOTHING_SHOP',\n 'MARKET',\n 'FOOD_MARKET',\n 'SUPERMARKETS_HYPERMARKETS',\n 'DEPARTMENT_STORE',\n 'CONVENIENCE_STORE',\n 'GROCERY_STORE',\n 'HARDWARE_STORE',\n 'ELECTRICAL_APPLIANCES_SHOP',\n ],\n TRANSPORTATION_GROUP: [\n 'AIRPORT',\n 'FERRY_TERMINAL',\n 'HELIPAD_HELICOPTER_LANDING',\n 'PUBLIC_TRANSPORT_STOP',\n 'RAILWAY_STATION',\n 'BUS_STOP',\n 'TAXI_STAND',\n ],\n HEALTH_GROUP: [\n 'DOCTOR',\n 'EMERGENCY_MEDICAL_SERVICE',\n 'EMERGENCY_ROOM',\n 'HEALTH_CARE_SERVICE',\n 'HOSPITAL',\n 'PHARMACY',\n 'DENTIST',\n 'WELFARE_ORGANIZATION',\n ],\n PARKING_GROUP: ['PARKING_GARAGE', 'OPEN_PARKING_AREA'],\n HOLIDAY_TOURISM_GROUP: [\n 'AMUSEMENT_PARK',\n 'BEACH',\n 'HOLIDAY_RENTAL',\n 'GEOGRAPHIC_FEATURE',\n 'TOURIST_ATTRACTION',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n 'MUSEUM',\n 'SCENIC_PANORAMIC_VIEW',\n 'TOURIST_INFORMATION_OFFICE',\n ],\n EV_CHARGING_STATIONS_GROUP: ['ELECTRIC_VEHICLE_STATION'],\n GAS_STATIONS_GROUP: ['GAS_STATION'],\n ACCOMMODATION_GROUP: ['CAMPING_GROUND', 'HOTEL_MOTEL', 'HOLIDAY_RENTAL'],\n ENTERTAINMENT_GROUP: [\n 'CINEMA',\n 'THEATER',\n 'MOVIE_THEATER',\n 'NIGHTLIFE',\n 'CONCERT_HALL',\n 'ENTERTAINMENT',\n 'CLUB_ASSOCIATION',\n 'CASINO',\n ],\n SPORTS_LEISURE_GROUP: [\n 'SPORTS_CENTER',\n 'WATER_SPORT',\n 'SWIMMING_POOL',\n 'GOLF_COURSE',\n 'STADIUM',\n 'BEACH',\n 'ICE_SKATING_RINK',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n ],\n EDUCATION_GROUP: ['SCHOOL', 'COLLEGE_UNIVERSITY', 'LIBRARY', 'CULTURAL_CENTER'],\n GOVERNMENT_GROUP: ['GOVERNMENT_OFFICE', 'COURTHOUSE', 'EMBASSY', 'FIRE_STATION_BRIGADE', 'POLICE_STATION'],\n};\n\n/**\n * POI category group type.\n *\n * @remarks\n * Represents predefined groups of related POI categories for convenient filtering.\n * Each group contains multiple {@link MapStylePOICategory} values that share a common theme.\n *\n * Using category groups simplifies filtering by allowing you to show or hide\n * multiple related POI types with a single filter value.\n *\n * **Available groups:**\n * - `FOOD_DRINKS_GROUP` - Restaurants, cafes, fast food, wineries, etc.\n * - `SHOPPING_GROUP` - Stores, malls, markets, supermarkets, etc.\n * - `TRANSPORTATION_GROUP` - Airports, train stations, bus stops, ferry terminals, etc.\n * - `HEALTH_GROUP` - Hospitals, clinics, pharmacies, doctors, dentists, etc.\n * - `PARKING_GROUP` - Parking garages and open parking areas\n * - `HOLIDAY_TOURISM_GROUP` - Tourist attractions, museums, beaches, scenic views, etc.\n * - `EV_CHARGING_STATIONS_GROUP` - Electric vehicle charging stations\n * - `GAS_STATIONS_GROUP` - Gas and petrol stations\n * - `ACCOMMODATION_GROUP` - Hotels, motels, camping grounds, etc.\n * - `ENTERTAINMENT_GROUP` - Cinemas, theaters, nightlife, casinos, etc.\n * - `SPORTS_LEISURE_GROUP` - Stadiums, sports centers, swimming pools, golf courses, etc.\n * - `EDUCATION_GROUP` - Schools, universities, libraries, cultural centers\n * - `GOVERNMENT_GROUP` - Government offices, courthouses, embassies, police, fire stations\n *\n * @example\n * Filter to show only food-related POIs:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'show',\n * values: ['FOOD_DRINKS_GROUP']\n * }\n * });\n * ```\n *\n * @example\n * Hide parking and gas stations:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'hide',\n * values: ['PARKING_GROUP', 'GAS_STATIONS_GROUP']\n * }\n * });\n * ```\n *\n * @example\n * Combine multiple groups for tourism use case:\n * ```ts\n * const tourismFilter = {\n * mode: 'show',\n * values: [\n * 'HOLIDAY_TOURISM_GROUP',\n * 'ACCOMMODATION_GROUP',\n * 'FOOD_DRINKS_GROUP',\n * 'ENTERTAINMENT_GROUP'\n * ]\n * };\n * ```\n *\n * @see {@link poiCategoryGroups} - The object containing all group definitions\n * @see {@link MapStylePOICategory} - For individual POI categories\n *\n * @group POIs\n */\nexport type POICategoryGroup = keyof typeof poiCategoryGroups;\n","import type { Point } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { POIsModuleFeature } from '../types/poisModuleFeature';\n\n/**\n * @ignore\n */\nexport const poisMapping = (feature: MapGeoJSONFeature): POIsModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry as Point,\n properties: {\n id: String(properties?.id),\n name: properties?.name,\n category: properties?.category,\n group: properties?.group,\n priority: properties?.priority,\n },\n };\n};\n","import { POICategory } from '@tomtom-org/maps-sdk/core';\nimport { isNil } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport { toBaseMapPOICategory } from '../places';\nimport type { ValuesFilter } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n ModuleEvents,\n POI_SOURCE_ID,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { buildMappedValuesFilter, getMergedAllFilter } from '../shared/mapLibreFilterUtils';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { poiLayerIDs } from './layers/poisLayers';\nimport { poiCategoryGroups } from './types/poiCategoryGroups';\nimport type { FilterablePOICategory, POIsModuleConfig } from './types/poisModuleConfig';\nimport type { POIsModuleFeature } from './types/poisModuleFeature';\nimport { poisMapping } from './util/poisMapping';\n\n/**\n * Gets the specified filtered categories icon IDs to be used in map filtering.\n * @param categories list of filtered categories.\n * @ignore\n */\nexport const getStyleCategories = (categories: FilterablePOICategory[]): string[] => {\n const categoryIds: string[] = [];\n categories.forEach((category: FilterablePOICategory) => {\n if (category in poiCategoryGroups) {\n categoryIds.push(...poiCategoryGroups[category].map(toBaseMapPOICategory));\n } else {\n categoryIds.push(toBaseMapPOICategory(category as POICategory));\n }\n });\n return [...new Set(categoryIds)];\n};\n\n/**\n * IDs of sources and layers for places of interest module.\n */\ntype PoIsSourcesAndLayers = {\n /**\n * Places of interest with corresponding layer ids.\n * TODO: technically source ID is vectorTiles if POIs stay included in base map for Orbis\n */\n poi: StyleSourceWithLayers;\n};\n\n/**\n * POIs Module for controlling Points of Interest displayed in the map style.\n *\n * This module manages the built-in POI layer from the vector map, allowing you to\n * show/hide POIs and filter them by category. POIs are already part of the map style\n * and include businesses, landmarks, and other points of interest.\n *\n * @remarks\n * **Features:**\n * - Toggle POI visibility on/off\n * - Filter by POI categories or category groups\n * - Event handling for POI interactions\n * - Based on vector tile data in the map style\n *\n * **POI Categories:**\n * - Individual categories (e.g., RESTAURANT, HOTEL_MOTEL, PARKING_GARAGE)\n * - Category groups (e.g., FOOD_DRINKS_GROUP, SHOPPING_GROUP, TRANSPORTATION_GROUP)\n *\n * **Difference from PlacesModule:**\n * - POIsModule: Controls existing POIs in the map style\n * - PlacesModule: Displays custom place data from Search API or other sources\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { POIsModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module\n * const poisModule = await POIsModule.get(map);\n *\n * // Toggle visibility\n * poisModule.setVisible(false);\n * poisModule.setVisible(true);\n * ```\n *\n * @example\n * Filter specific categories:\n * ```typescript\n * // Show only restaurants and hotels\n * poisModule.filterCategories({\n * show: 'only',\n * values: ['RESTAURANT', 'HOTEL_MOTEL']\n * });\n *\n * // Hide parking garages\n * poisModule.filterCategories({\n * show: 'all_except',\n * values: ['PARKING_GARAGE', 'OPEN_PARKING_AREA']\n * });\n * ```\n *\n * @example\n * Filter using category groups:\n * ```typescript\n * // Show only food and shopping POIs\n * poisModule.filterCategories({\n * show: 'only',\n * values: ['FOOD_DRINKS_GROUP', 'SHOPPING_GROUP']\n * });\n *\n * // Hide transportation POIs\n * pois.filterCategories({\n * show: 'all_except',\n * values: ['TRANSPORTATION_GROUP']\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('Clicked POI:', feature.properties.name);\n * console.log('Category:', feature.properties.category);\n * });\n * ```\n *\n * @see [POIs Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/pois)\n *\n * @group POIs\n */\nexport class POIsModule extends AbstractMapModule<PoIsSourcesAndLayers, POIsModuleConfig> {\n private categoriesFilter?: ValuesFilter<FilterablePOICategory> | null;\n private originalFilter?: FilterSpecification;\n\n /**\n * Retrieves a POIsModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional initial configuration for visibility and filters.\n *\n * @returns A promise that resolves to the initialized POIsModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `filters.categories`: Category filter to apply on initialization\n *\n * @throws Error if the POI source is not found in the map style\n *\n * @example\n * Default initialization:\n * ```typescript\n * const poisModule = await POIsModule.get(map);\n * ```\n *\n * @example\n * With initial filter:\n * ```typescript\n * const poisModule = await POIsModule.get(map, {\n * visible: true,\n * filters: {\n * categories: {\n * show: 'only',\n * values: ['RESTAURANT', 'CAFE_PUB']\n * }\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: POIsModuleConfig): Promise<POIsModule> {\n await waitUntilMapIsReady(map);\n return new POIsModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: POIsModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const poiRuntimeSource = this.mapLibreMap.getSource(POI_SOURCE_ID);\n if (!poiRuntimeSource) {\n throw notInTheStyle(`init ${POIsModule.name} with source ID ${POI_SOURCE_ID}`);\n }\n const poi = new StyleSourceWithLayers(this.mapLibreMap, poiRuntimeSource, (layer) =>\n poiLayerIDs.includes(layer.id),\n );\n // TODO: check if the layers are present in the style, and if not, throw exception?\n const mainLayer = poi.sourceAndLayerIDs.layerIDs[0];\n if (this.mapLibreMap.getLayer(mainLayer)) {\n this.originalFilter = this.mapLibreMap.getFilter(mainLayer) as FilterSpecification;\n }\n return { poi };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: POIsModuleConfig | undefined) {\n if (config && !isNil(config.visible)) {\n this._setVisible(config.visible, false);\n } else if (!this._initializing && !this.isVisible()) {\n // applying default:\n this._setVisible(true, false);\n }\n\n this._filterCategories(config?.filters?.categories, false);\n return config;\n }\n\n /**\n * Checks if POI layers are currently visible.\n *\n * @returns `true` if at least one POI layer is visible, `false` if all are hidden.\n *\n * @example\n * ```typescript\n * if (pois.isVisible()) {\n * console.log('POIs are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.poi.isAnyLayerVisible();\n }\n\n /**\n * Sets the visibility of POI layers.\n *\n * @param visible - `true` to show POIs, `false` to hide them.\n *\n * @remarks\n * Changes are applied immediately if the map is ready.\n *\n * @example\n * ```typescript\n * pois.setVisible(false); // Hide all POIs\n * pois.setVisible(true); // Show all POIs\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.poi.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Filters POIs by category or category group.\n *\n * @param categoriesFilter - Filter configuration specifying which categories to show/hide.\n * Pass `undefined` to reset to default (show all).\n *\n * @remarks\n * **Filter Modes:**\n * - `only`: Show only the specified categories, hide all others\n * - `all_except`: Show all categories except the specified ones\n *\n * **Category Types:**\n * - Individual categories (e.g., 'RESTAURANT', 'HOTEL_MOTEL')\n * - Category groups (e.g., 'FOOD_DRINKS_GROUP', 'SHOPPING_GROUP')\n *\n * **Available Category Groups:**\n * - FOOD_DRINKS_GROUP\n * - SHOPPING_GROUP\n * - TRANSPORTATION_GROUP\n * - HEALTH_GROUP\n * - PARKING_GROUP\n * - HOLIDAY_TOURISM_GROUP\n * - EV_CHARGING_STATIONS_GROUP\n * - GAS_STATIONS_GROUP\n * - ACCOMMODATION_GROUP\n * - ENTERTAINMENT_GROUP\n * - SPORTS_LEISURE_GROUP\n * - EDUCATION_GROUP\n * - GOVERNMENT_GROUP\n *\n * @example\n * Show only restaurants:\n * ```typescript\n * pois.filterCategories({\n * show: 'only',\n * values: ['RESTAURANT']\n * });\n * ```\n *\n * @example\n * Hide parking:\n * ```typescript\n * pois.filterCategories({\n * show: 'all_except',\n * values: ['PARKING_GROUP']\n * });\n * ```\n *\n * @example\n * Reset filter (show all):\n * ```typescript\n * pois.filterCategories(undefined);\n * ```\n */\n filterCategories(categoriesFilter?: ValuesFilter<FilterablePOICategory> | undefined): void {\n this._filterCategories(categoriesFilter);\n }\n\n private _filterCategories(\n categoriesFilter: ValuesFilter<FilterablePOICategory> | undefined,\n updateConfig = true,\n ): void {\n if (categoriesFilter) {\n if (this.tomtomMap.mapReady) {\n const poiFilter = buildMappedValuesFilter(\n 'category',\n categoriesFilter.show,\n getStyleCategories(categoriesFilter.values),\n );\n\n this.mapLibreMap.setFilter('POI', getMergedAllFilter(poiFilter, this.originalFilter));\n }\n if (updateConfig) {\n this.config = {\n ...this.config,\n filters: {\n categories: categoriesFilter,\n },\n };\n }\n } else if (this.categoriesFilter) {\n if (updateConfig) {\n // reset categories config to default\n this.config = {\n ...this.config,\n filters: {\n categories: {\n show: 'all_except',\n values: [],\n },\n },\n };\n }\n if (this.tomtomMap.mapReady) {\n // Applies default:\n this.mapLibreMap.setFilter('POI', this.originalFilter);\n }\n }\n\n this.categoriesFilter = categoriesFilter;\n if (updateConfig) {\n this.emitConfigChange();\n }\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible POI features with properly typed properties.\n *\n * @example\n * ```typescript\n * const shown = poisModule.getShown();\n * console.log(`Visible POIs: ${shown.poi.length}`);\n * shown.poi.forEach(poi => {\n * console.log(poi.properties.name, poi.properties.category);\n * });\n * ```\n */\n getShown(): { poi: POIsModuleFeature[] } {\n return {\n poi: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.poi.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(poisMapping),\n };\n }\n\n /**\n * Gets the events interface for handling user interactions with POIs.\n *\n * @returns A `UserEvents` instance for registering event handlers.\n *\n * @remarks\n * **Supported Events:**\n * - `click`: User clicks on a POI\n * - `contextmenu`: User right-clicks on a POI\n * - `hover`: Mouse enters a POI\n * - `long-hover`: Mouse hovers over POI for extended time\n *\n * **Event Feature Properties:**\n * - `id`: Unique POI identifier\n * - `name`: POI name in native language\n * - `category`: POI category\n * - `iconID`: Icon sprite ID\n * - `group`: Category group\n * - `priority`: Importance level (lower = more important)\n *\n * @example\n * ```typescript\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('POI:', feature.properties.name);\n * console.log('Category:', feature.properties.category);\n * console.log('ID:', feature.properties.id);\n * });\n *\n * pois.events.on('hover', (feature) => {\n * showTooltip(feature.properties.name);\n * });\n * ```\n */\n get events(): CombinedEvents<POIsModuleFeature, POIsModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents<POIsModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.poi,\n this.config?.events,\n poisMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { LayerSpecification } from 'maplibre-gl';\nimport { poiLayerIDs } from '../pois';\nimport type { LayerSpecFilter } from '../shared';\nimport type { BaseMapLayerGroupName, BaseMapLayerGroups } from './types/baseMapModuleConfig';\n\ntype LayerGroupMapping = {\n layerIDMatches: string[];\n layerTypes: LayerSpecification['type'][];\n};\n\nconst layerGroupMappings: Record<BaseMapLayerGroupName, LayerGroupMapping> = {\n land: {\n layerIDMatches: ['lulc'],\n layerTypes: ['fill'],\n },\n borders: {\n layerIDMatches: ['borders'],\n layerTypes: ['line'],\n },\n water: {\n layerIDMatches: ['water'],\n layerTypes: ['fill', 'line'],\n },\n buildings2D: {\n layerIDMatches: ['building'],\n layerTypes: ['fill', 'line'],\n },\n buildings3D: {\n layerIDMatches: ['building'],\n layerTypes: ['fill-extrusion'],\n },\n houseNumbers: {\n layerIDMatches: ['house number'],\n layerTypes: ['symbol'],\n },\n roadLines: {\n layerIDMatches: ['road', 'tunnel', 'bridge', 'surface'],\n layerTypes: ['fill', 'line'],\n },\n roadLabels: {\n layerIDMatches: ['road', 'tunnel', 'bridge', 'surface'],\n layerTypes: ['symbol'],\n },\n roadShields: {\n layerIDMatches: ['shield'],\n layerTypes: ['symbol'],\n },\n placeLabels: {\n layerIDMatches: ['places'],\n layerTypes: ['symbol'],\n },\n smallerTownLabels: {\n layerIDMatches: ['town', 'village', 'neighbourhood'],\n layerTypes: ['symbol'],\n },\n cityLabels: {\n layerIDMatches: ['city', 'capital'],\n layerTypes: ['symbol'],\n },\n capitalLabels: {\n layerIDMatches: ['capital'],\n layerTypes: ['symbol'],\n },\n stateLabels: {\n layerIDMatches: ['state'],\n layerTypes: ['symbol'],\n },\n countryLabels: {\n layerIDMatches: ['places - country'],\n layerTypes: ['symbol'],\n },\n};\n\nconst isMatching = (group: BaseMapLayerGroupName, layer: LayerSpecification) => {\n const mapping = layerGroupMappings[group];\n return (\n mapping.layerIDMatches.some((part) => layer.id.toLowerCase().includes(part)) &&\n mapping.layerTypes.includes(layer.type)\n );\n};\n\n/**\n * @ignore\n */\nexport const buildLayerGroupFilter = (layerGroups: BaseMapLayerGroups): LayerSpecFilter => {\n const mode = layerGroups.mode;\n const groups = layerGroups.names;\n if (mode === 'include') {\n return (layer) => groups.some((group) => isMatching(group, layer));\n }\n if (mode === 'exclude') {\n return (layer) => !groups.some((group) => isMatching(group, layer));\n }\n // No filtering if we don't recognize the mode:\n console.error('Unrecognized layer group mode:', mode);\n return () => true;\n};\n\n/**\n * @ignore\n */\nexport const filterLayerByGroups = (layer: LayerSpecification, layerGroups?: BaseMapLayerGroups): boolean => {\n const mode = layerGroups?.mode;\n const groups = layerGroups?.names;\n if (mode && groups?.length) {\n if (mode === 'include') {\n return groups.some((group) => isMatching(group, layer));\n }\n if (mode === 'exclude') {\n return !groups.some((group) => isMatching(group, layer));\n }\n }\n return true;\n};\n\n/**\n * @ignore\n */\nexport const buildBaseMapLayerGroupFilter =\n (layerGroupsFilter?: BaseMapLayerGroups): LayerSpecFilter =>\n (layer: LayerSpecification): boolean =>\n (!layerGroupsFilter || filterLayerByGroups(layer, layerGroupsFilter)) && !poiLayerIDs.includes(layer.id);\n","import { isNil } from 'lodash-es';\n\nimport {\n AbstractMapModule,\n BASE_MAP_SOURCE_ID,\n CombinedEvents,\n ModuleEvents,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport { TomTomMap } from '../TomTomMap';\nimport { buildBaseMapLayerGroupFilter, buildLayerGroupFilter } from './layerGroups';\nimport type { BaseMapLayerGroups, BaseMapModuleConfig, BaseMapModuleInitConfig } from './types/baseMapModuleConfig';\n\ntype BaseSourceAndLayers = {\n vectorTiles: StyleSourceWithLayers;\n};\n\n/**\n * Base Map Module for controlling standard map layers and their visibility.\n *\n * This module manages the fundamental map layers including background, water, land, roads,\n * buildings, labels, and other vector tile layers from the base map style.\n *\n * @remarks\n * **Managed Layers:**\n * - Background and terrain\n * - Water bodies and coastlines\n * - Country and administrative borders\n * - Buildings (2D and 3D)\n * - Road lines, labels, and shields\n * - Place labels at various zoom levels\n * - House numbers\n *\n * **Does NOT Include:**\n * - Traffic flow/incidents (use {@link TrafficFlowModule} or {@link TrafficIncidentsModule})\n * - Points of Interest/POIs (use {@link POIsModule})\n * - Hillshade/terrain shading (use {@link HillshadeModule})\n *\n * **Use Cases:**\n * - Toggle base map visibility on/off\n * - Show only specific layer groups (e.g., roads only)\n * - Create custom map appearances by hiding certain elements\n * - Build overlay maps with selective base layers\n *\n * @example\n * Basic usage:\n * ```typescript\n * // Get module with default configuration\n * const baseMap = await BaseMapModule.get(map);\n *\n * // Toggle visibility\n * baseMap.setVisible(false); // Hide all base layers\n * baseMap.setVisible(true); // Show all base layers\n *\n * // Check current state\n * if (baseMap.isVisible()) {\n * console.log('Base map is visible');\n * }\n * ```\n *\n * @example\n * Working with layer groups:\n * ```typescript\n * // Show only roads and borders\n * const baseMap = await BaseMapModule.get(map, {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'borders']\n * }\n * });\n *\n * // Hide buildings and labels\n * baseMap.setVisible(false, {\n * layerGroups: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D', 'placeLabels']\n * }\n * });\n *\n * // Show only water and land\n * baseMap.setVisible(true, {\n * layerGroups: {\n * mode: 'include',\n * names: ['water', 'land']\n * }\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map);\n *\n * // Listen for clicks on base map features\n * baseMap.events.on('click', (feature, lngLat) => {\n * console.log('Clicked base map feature:', feature);\n * });\n *\n * // Remove event listeners\n * baseMap.events.off('click');\n * ```\n *\n * @see [Base Map Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/base-map)\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n *\n * @group Base Map\n */\nexport class BaseMapModule extends AbstractMapModule<BaseSourceAndLayers, BaseMapModuleConfig> {\n /**\n * Asynchronously retrieves a BaseMapModule instance for the given map.\n *\n * This is the recommended way to create a BaseMapModule. It ensures the map\n * is fully loaded before initializing the module.\n *\n * @param tomtomMap - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for module initialization.\n *\n * @returns A promise that resolves to the initialized BaseMapModule.\n *\n * @remarks\n * **Initialization:**\n * - Waits for map to be ready before creating module\n * - Validates that required sources exist in the map style\n * - Applies initial configuration if provided\n *\n * **Configuration Options:**\n * - `visible`: Initial visibility state\n * - `layerGroupsFilter`: Which layer groups to include/exclude\n * - `layerGroupsVisibility`: Fine-grained visibility per group\n *\n * @throws Error if the base map source is not found in the style\n *\n * @example\n * Default initialization:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map);\n * ```\n *\n * @example\n * With configuration:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map, {\n * visible: true,\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings3D', 'houseNumbers']\n * }\n * });\n * ```\n *\n * @example\n * Show only specific groups:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map, {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land', 'borders']\n * },\n * visible: true\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: BaseMapModuleInitConfig): Promise<BaseMapModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new BaseMapModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: BaseMapModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config: BaseMapModuleInitConfig | undefined) {\n const source = this.mapLibreMap.getSource(BASE_MAP_SOURCE_ID);\n if (!source) {\n throw notInTheStyle(`init ${BaseMapModule.name} with source ID ${BASE_MAP_SOURCE_ID}`);\n }\n\n return {\n vectorTiles: new StyleSourceWithLayers(\n this.mapLibreMap,\n source,\n buildBaseMapLayerGroupFilter(config?.layerGroupsFilter),\n ),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: BaseMapModuleConfig | undefined) {\n if (config && !isNil(config.visible)) {\n this._setVisible(config.visible, undefined, false);\n } else if (!this._initializing && !this.isVisible()) {\n // applying default:\n this._setVisible(true, undefined, false);\n }\n\n if (config?.layerGroupsVisibility) {\n this._setVisible(\n config.layerGroupsVisibility.visible,\n { layerGroups: config.layerGroupsVisibility },\n false,\n );\n }\n\n // We merge the given config with the previous one to ensure init config parameters are kept:\n // (the init config can have more parameters than the runtime one)\n return this.config || config ? { ...this.config, ...config } : undefined;\n }\n\n /**\n * Checks if any base map layers are currently visible.\n *\n * @returns `true` if at least one base map layer is visible, `false` if all are hidden.\n *\n * @remarks\n * This checks the actual visibility state of layers in the map, not just the\n * module's configuration setting.\n *\n * @example\n * ```typescript\n * if (baseMap.isVisible()) {\n * console.log('Base map is rendered');\n * } else {\n * console.log('Base map is hidden');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.vectorTiles.isAnyLayerVisible();\n }\n\n /**\n * Sets the visibility of base map layers.\n *\n * @param visible - `true` to show layers, `false` to hide them.\n * @param options - Optional settings for fine-grained control.\n * @param options.layerGroups - Target specific layer groups instead of all layers.\n *\n * @remarks\n * **Behavior:**\n * - Without `options.layerGroups`: Affects all base map layers\n * - With `options.layerGroups`: Affects only specified layer groups\n * - Changes are applied immediately if map is ready\n *\n * **Layer Groups:**\n * Available groups: `land`, `water`, `borders`, `buildings2D`, `buildings3D`,\n * `houseNumbers`, `roadLines`, `roadLabels`, `roadShields`, `placeLabels`,\n * `smallerTownLabels`, `cityLabels`, `capitalLabels`, `stateLabels`, `countryLabels`\n *\n * @example\n * Show/hide all layers:\n * ```typescript\n * baseMap.setVisible(false); // Hide everything\n * baseMap.setVisible(true); // Show everything\n * ```\n *\n * @example\n * Control specific groups:\n * ```typescript\n * // Hide only buildings\n * baseMap.setVisible(false, {\n * layerGroups: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D']\n * }\n * });\n *\n * // Show everything except labels\n * baseMap.setVisible(true, {\n * layerGroups: {\n * mode: 'exclude',\n * names: ['placeLabels', 'cityLabels', 'countryLabels']\n * }\n * });\n * ```\n *\n * @example\n * Toggle visibility:\n * ```typescript\n * const isVisible = baseMap.isVisible();\n * baseMap.setVisible(!isVisible); // Toggle\n * ```\n */\n setVisible(visible: boolean, options?: { layerGroups?: BaseMapLayerGroups }): void {\n this._setVisible(visible, options);\n }\n\n private _setVisible(visible: boolean, options?: { layerGroups?: BaseMapLayerGroups }, updateConfig = true): void {\n if (updateConfig) {\n if (!options?.layerGroups) {\n // We remove the layer groups visibility from the config if it was there:\n delete this.config?.layerGroupsVisibility;\n this.config = { ...this.config, visible };\n } else {\n this.config = { ...this.config, layerGroupsVisibility: { ...options.layerGroups, visible } };\n }\n }\n\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.vectorTiles.setLayersVisible(\n visible,\n options?.layerGroups && buildLayerGroupFilter(options.layerGroups),\n );\n }\n\n if (updateConfig) {\n this.emitConfigChange();\n }\n }\n\n /**\n * Gets the unified events interface for this module, covering both user interactions\n * and module lifecycle events.\n *\n * **User interaction events** (`click`, `contextmenu`, `hover`, `long-hover`):\n * ```typescript\n * const unsub = baseMap.events.on('click', (feature, lngLat) => {\n * console.log('Clicked on:', feature.properties);\n * });\n * baseMap.events.off('hover'); // remove by type\n * ```\n *\n * **Module lifecycle events** (`config-change`):\n * ```typescript\n * const unsub = baseMap.events.on('config-change', (config) => {\n * console.log('Config changed:', config);\n * });\n * unsub(); // remove by returned function\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, BaseMapModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.vectorTiles, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { MapModuleCommonConfig } from '../../shared';\n\n/**\n * Available base map layer group identifiers.\n *\n * @remarks\n * Use these names with {@link BaseMapModule} to control layer visibility.\n *\n * @see {@link BaseMapLayerGroupName}\n * @see {@link BaseMapModuleInitConfig.layerGroupsFilter}\n *\n * @group Base Map\n */\nexport const baseMapLayerGroupNames = [\n 'land',\n 'water',\n 'borders',\n 'buildings2D',\n 'buildings3D',\n 'houseNumbers',\n 'roadLines',\n 'roadLabels',\n 'roadShields',\n 'placeLabels',\n 'smallerTownLabels',\n 'cityLabels',\n 'capitalLabels',\n 'stateLabels',\n 'countryLabels',\n] as const;\n\n/**\n * Name of a base map layer group.\n *\n * Identifies specific categories of base map layers that can be controlled together.\n *\n * @remarks\n * **Available Layer Groups:**\n * - `land` - Land areas and terrain\n * - `water` - Water bodies (oceans, lakes, rivers)\n * - `borders` - Country and administrative boundaries\n * - `buildings2D` - 2D building footprints\n * - `buildings3D` - 3D building models\n * - `houseNumbers` - House number labels\n * - `roadLines` - Road line geometries\n * - `roadLabels` - Street name labels\n * - `roadShields` - Highway shields (e.g., I-95, A1)\n * - `placeLabels` - General place labels\n * - `smallerTownLabels` - Small town/village labels\n * - `cityLabels` - City labels\n * - `capitalLabels` - Capital city labels\n * - `stateLabels` - State/province labels\n * - `countryLabels` - Country name labels\n *\n * @example\n * ```typescript\n * const group: BaseMapLayerGroupName = 'roadLines';\n * const labels: BaseMapLayerGroupName[] = ['cityLabels', 'countryLabels'];\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroupName = (typeof baseMapLayerGroupNames)[number];\n\n/**\n * Layer group visibility configuration with explicit visible state.\n *\n * Extends {@link BaseMapLayerGroups} to include a visibility flag, allowing\n * you to show or hide specific groups of base map layers.\n *\n * @example\n * ```typescript\n * // Hide all buildings\n * const config: BaseMapLayerGroupsVisibility = {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * };\n *\n * // Show only roads\n * const roadsOnly: BaseMapLayerGroupsVisibility = {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels'],\n * visible: true\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroupsVisibility = BaseMapLayerGroups & { visible: boolean };\n\n/**\n * Layer group filter for selective base map display.\n *\n * Defines which layer groups to include or exclude from the base map module.\n * Can be expressed as explicit inclusions (show only these) or exclusions\n * (show all except these).\n *\n * @remarks\n * **Filter Modes:**\n * - `include`: Only the specified groups are shown, all others are hidden\n * - `exclude`: All groups are shown except the specified ones\n *\n * **Common Use Cases:**\n * - Show only roads and labels (minimal map)\n * - Hide buildings for cleaner appearance\n * - Show only water and land (base terrain)\n * - Remove labels for overlay maps\n *\n * @example\n * ```typescript\n * // Show only roads and borders\n * const roadsOnly: BaseMapLayerGroups = {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'borders']\n * };\n *\n * // Show everything except buildings\n * const noBuildings: BaseMapLayerGroups = {\n * mode: 'exclude',\n * names: ['buildings2D', 'buildings3D']\n * };\n *\n * // Show only terrain (no labels, no roads)\n * const terrainOnly: BaseMapLayerGroups = {\n * mode: 'include',\n * names: ['land', 'water']\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroups = {\n /**\n * Filter mode determining whether groups are included or excluded.\n *\n * @remarks\n * - `include`: Only the specified groups are considered, all others are ignored\n * - `exclude`: All base map groups except the specified ones are considered\n *\n * @example\n * ```typescript\n * mode: 'include' // Whitelist approach\n * mode: 'exclude' // Blacklist approach\n * ```\n */\n mode: 'include' | 'exclude';\n\n /**\n * Names of the layer groups to include or exclude.\n *\n * @remarks\n * The meaning depends on the `mode`:\n * - In `include` mode: Only these groups will be shown\n * - In `exclude` mode: These groups will be hidden, all others shown\n *\n * @example\n * ```typescript\n * // Show only these\n * names: ['roadLines', 'roadLabels', 'water']\n *\n * // Hide these\n * names: ['buildings2D', 'buildings3D', 'houseNumbers']\n * ```\n */\n names: BaseMapLayerGroupName[];\n};\n\n/**\n * Configuration for the BaseMapModule (initialization or runtime).\n *\n * Controls visibility and behavior of base map layer groups. Can be used both\n * during module initialization and for runtime updates.\n *\n * @remarks\n * This configuration allows fine-grained control over which base map elements\n * are displayed, enabling you to create custom map appearances for different\n * use cases.\n *\n * @example\n * ```typescript\n * // Hide specific layer groups\n * const config: BaseMapModuleConfig = {\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * }\n * };\n *\n * // Show only certain groups\n * const minimalConfig: BaseMapModuleConfig = {\n * visible: true,\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['roadLines', 'water', 'land'],\n * visible: true\n * }\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapModuleConfig = MapModuleCommonConfig & {\n /**\n * Controls the visibility of all layers associated with this module.\n *\n * @default true\n */\n visible?: boolean;\n\n /**\n * Optional visibility configuration for specific layer groups.\n *\n * @remarks\n * **Important:** The layer groups specified here must be included in the\n * module (not excluded by `layerGroupsFilter` during initialization).\n *\n * Use this to control visibility of layer groups at runtime without\n * reinitializing the module.\n *\n * @example\n * ```typescript\n * // Hide all building layers\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * }\n *\n * // Show only labels\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['placeLabels', 'cityLabels', 'countryLabels'],\n * visible: true\n * }\n * ```\n */\n layerGroupsVisibility?: BaseMapLayerGroupsVisibility;\n};\n\n/**\n * Initialization configuration for the BaseMapModule.\n *\n * Extends {@link BaseMapModuleConfig} with additional options available only\n * during module initialization.\n *\n * @remarks\n * **Initialization vs Runtime:**\n * - `layerGroupsFilter`: Only available at init - determines which groups to load\n * - `layerGroupsVisibility`: Available at init and runtime - controls visibility\n *\n * **Use Cases:**\n * - Create minimal maps with only essential layers\n * - Build custom map styles by excluding certain features\n * - Optimize performance by not loading unnecessary layers\n *\n * @example\n * ```typescript\n * // Initialize with only roads and water\n * const initConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'water', 'land']\n * },\n * visible: true\n * };\n *\n * // Exclude buildings from initialization\n * const noBuildingsConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings2D', 'buildings3D', 'houseNumbers']\n * }\n * };\n *\n * // Minimal map for data overlay\n * const overlayBaseConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land', 'borders']\n * },\n * visible: true\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapModuleInitConfig = BaseMapModuleConfig & {\n /**\n * Layer groups to include/exclude during module initialization.\n *\n * @remarks\n * **One-time configuration:** This can only be set during initialization.\n * Once the module is created, you cannot change which groups are loaded,\n * only their visibility via `layerGroupsVisibility`.\n *\n * @example\n * ```typescript\n * // Load only essential layers\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['land', 'water', 'roadLines', 'borders']\n * }\n *\n * // Load everything except 3D buildings\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings3D']\n * }\n *\n * // Minimal overlay map\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land']\n * }\n * ```\n */\n layerGroupsFilter?: BaseMapLayerGroups;\n};\n","import type { FeatureCollection } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { ToBeAddedLayerSpec, ToBeAddedLayerSpecWithoutSource } from '../shared';\nimport { AbstractMapModule, CombinedEvents, GeoJSONSourceWithLayers, ModuleEvents, UserEvents } from '../shared';\nimport { addLayers, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { CustomGeoJSONModuleConfig } from './types/customGeoJSONModuleConfig';\n\ntype CustomGeoJSONSourcesWithLayers<TSources extends Record<string, FeatureCollection>> = {\n [K in keyof TSources]: GeoJSONSourceWithLayers<TSources[K]>;\n};\n\n/**\n * Per-source {@link CombinedEvents} surface keyed by the source name supplied in\n * {@link CustomGeoJSONModuleConfig.sources}.\n *\n * @group Custom\n */\nexport type CustomGeoJSONEvents<TSources extends Record<string, FeatureCollection>> = {\n [K in keyof TSources]: CombinedEvents<MapGeoJSONFeature, CustomGeoJSONModuleConfig<TSources>, TSources[K]>;\n};\n\n/**\n * Module for displaying customer-owned GeoJSON data with TomTom SDK support.\n *\n * Wraps one or more MapLibre GeoJSON sources together with caller-supplied layer specs,\n * and adds the usual TomTom SDK lifecycle behaviour on top: style-change restoration,\n * `config-change` / `shown-features` events, and per-source user interaction events\n * (`click`, `hover`, `long-hover`, `contextmenu`).\n *\n * Unlike opinionated modules such as {@link PlacesModule} or {@link GeometriesModule},\n * this module makes no assumptions about how data is rendered — the caller passes raw\n * MapLibre layer specs (`circle`, `heatmap`, `fill`, `line`, `symbol`, …).\n *\n * @typeParam TSources - A record mapping source names to the {@link FeatureCollection}\n * type each source carries. Pass this generic when you want type-safe `show` /\n * `getShown` per source.\n *\n * @example\n * Single source, default generic:\n * ```typescript\n * const module = await CustomGeoJSONModule.get(map, {\n * sources: {\n * points: {\n * layers: [{ type: 'circle', paint: { 'circle-radius': 4, 'circle-color': '#0a3653' } }],\n * },\n * },\n * });\n *\n * await module.show(myFeatureCollection, 'points');\n * module.events.points.on('click', (feature, lngLat) => console.log(feature, lngLat));\n * ```\n *\n * @example\n * Multiple sources with typed payloads:\n * ```typescript\n * type Sources = {\n * heatmap: FeatureCollection<Point>;\n * buildings: FeatureCollection<Polygon, { name: string }>;\n * };\n *\n * const module = await CustomGeoJSONModule.get<Sources>(map, {\n * sources: {\n * heatmap: { layers: [{ type: 'heatmap', paint: { 'heatmap-radius': 12 } }] },\n * buildings: { layers: [{ type: 'fill', paint: { 'fill-color': '#5a5' } }] },\n * },\n * });\n *\n * await module.show(heatmapData, 'heatmap');\n * await module.show(buildingData, 'buildings');\n * ```\n *\n * @example\n * Same data, multiple renderings — call `show` once without a source name:\n * ```typescript\n * type Buildings = FeatureCollection<Point, { Name: string }>;\n *\n * const module = await CustomGeoJSONModule.get<{ heatmap: Buildings; markers: Buildings }>(map, {\n * sources: {\n * heatmap: { layers: heatmapLayers },\n * markers: { layers: markerLayers },\n * },\n * });\n *\n * await module.show(buildingData); // applied to both sources\n * ```\n *\n * @group Custom\n */\nexport class CustomGeoJSONModule<\n TSources extends Record<string, FeatureCollection> = Record<string, FeatureCollection>,\n> extends AbstractMapModule<CustomGeoJSONSourcesWithLayers<TSources>, CustomGeoJSONModuleConfig<TSources>> {\n // Resolved (post-auto-gen) IDs and layer specs, indexed by source name.\n // Stable across style changes because they are derived deterministically from instanceIndex + config.\n private resolvedSourceIDs!: { [K in keyof TSources]: string };\n private resolvedLayerSpecs!: { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] };\n\n // Cached last-shown data per source so style-change restore can replay show() calls.\n // Lazy-initialised because the base-class constructor invokes _initSourcesWithLayers\n // before subclass field initialisers run.\n private lastShown!: Partial<{ [K in keyof TSources]: TSources[K] }>;\n\n // Per-source shown-features handler arrays. References are kept stable so that handlers\n // registered through one events getter call survive subsequent getter calls.\n // Lazy-initialised for the same reason as lastShown.\n private shownFeaturesHandlersBySource!: Map<keyof TSources, ((data: TSources[keyof TSources]) => void)[]>;\n\n /**\n * Creates a new {@link CustomGeoJSONModule} once the map is ready.\n *\n * @param tomtomMap The TomTomMap instance.\n * @param config Module configuration. `sources` is required and must contain at\n * least one entry; each entry must have at least one layer.\n */\n static async get<TSources extends Record<string, FeatureCollection> = Record<string, FeatureCollection>>(\n tomtomMap: TomTomMap,\n config: CustomGeoJSONModuleConfig<TSources>,\n ): Promise<CustomGeoJSONModule<TSources>> {\n await waitUntilMapIsReady(tomtomMap);\n validateConfig(config);\n return new CustomGeoJSONModule<TSources>(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config: CustomGeoJSONModuleConfig<TSources>) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(\n config: CustomGeoJSONModuleConfig<TSources> | undefined,\n restore?: boolean,\n ): CustomGeoJSONSourcesWithLayers<TSources> {\n // config is required by the public API; AbstractMapModule's signature allows undefined.\n const effectiveConfig = config ?? (this.config as CustomGeoJSONModuleConfig<TSources>);\n if (!restore) {\n this.lastShown = {};\n this.shownFeaturesHandlersBySource = new Map();\n }\n this.resolveIDs(effectiveConfig);\n // Images must be registered BEFORE the GeoJSONSourceWithLayers instances are\n // created, so symbol layers referencing them via `icon-image: '<id>'` never\n // render against a missing image. This also covers the style-change restore\n // path because `_initSourcesWithLayers` is called both on init and on restore.\n this.registerImages(effectiveConfig);\n\n const sources = {} as CustomGeoJSONSourcesWithLayers<TSources>;\n for (const sourceName of Object.keys(effectiveConfig.sources) as Array<keyof TSources>) {\n const sourceID = this.resolvedSourceIDs[sourceName];\n const layerSpecs = this.resolvedLayerSpecs[sourceName];\n const cluster = effectiveConfig.sources[sourceName].cluster;\n sources[sourceName] = new GeoJSONSourceWithLayers<TSources[typeof sourceName]>(\n this.mapLibreMap,\n sourceID,\n layerSpecs,\n true,\n cluster,\n );\n\n if (!this.shownFeaturesHandlersBySource.has(sourceName)) {\n this.shownFeaturesHandlersBySource.set(sourceName, []);\n }\n }\n return sources;\n }\n\n private registerImages(config: CustomGeoJSONModuleConfig<TSources>): void {\n if (!config.images) return;\n\n for (const [imageID, spec] of Object.entries(config.images)) {\n if (this.mapLibreMap.hasImage(imageID)) continue;\n this.mapLibreMap.addImage(imageID, spec.image, spec.options);\n }\n }\n\n private resolveIDs(config: CustomGeoJSONModuleConfig<TSources>): void {\n const sourceIDs = {} as { [K in keyof TSources]: string };\n const layerSpecs = {} as { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] };\n for (const sourceName of Object.keys(config.sources) as Array<keyof TSources>) {\n const sourceSpec = config.sources[sourceName];\n const resolvedSourceID =\n sourceSpec.sourceID ?? `custom-geojson-${this.instanceIndex}-${String(sourceName)}`;\n sourceIDs[sourceName] = resolvedSourceID;\n layerSpecs[sourceName] = sourceSpec.layers.map(\n (layer, layerIndex) =>\n ({\n ...layer,\n id: layer.id ?? `${resolvedSourceID}-layer-${layerIndex}`,\n }) as ToBeAddedLayerSpecWithoutSource,\n );\n }\n this.resolvedSourceIDs = sourceIDs;\n this.resolvedLayerSpecs = layerSpecs;\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(\n config: CustomGeoJSONModuleConfig<TSources> | undefined,\n ): CustomGeoJSONModuleConfig<TSources> | undefined {\n if (!config) {\n // Resetting the config to undefined is not supported — the module requires\n // a sources structure to remain operational. Keep the existing config.\n return this.config;\n }\n\n const previousConfig = this.config;\n const previousLayerSpecs = previousConfig ? this.resolvedLayerSpecs : undefined;\n this.resolveIDs(config);\n\n // Register any new images before layers are diffed in, so symbol layers added\n // by this applyConfig never render against a missing image. Additive only:\n // images already on the map are skipped; removing an entry from config.images\n // does not removeImage it (other layers or future show() data may still need it).\n this.registerImages(config);\n\n // On the very first apply (called by the base-class constructor right after\n // _initSourcesWithLayers), there's no previous layer set to diff against — the\n // layers were just added by _initSourcesWithLayers.\n if (previousConfig && previousLayerSpecs) {\n this.updateLayersForExistingSources(previousLayerSpecs);\n }\n\n if (config.visible !== undefined) {\n this.applyVisibility(config.visible);\n }\n\n return config;\n }\n\n private updateLayersForExistingSources(\n previousLayerSpecs: { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] },\n ): void {\n for (const sourceName of Object.keys(this.resolvedLayerSpecs) as Array<keyof TSources>) {\n const previous = previousLayerSpecs[sourceName];\n const next = this.resolvedLayerSpecs[sourceName];\n const sourceWithLayers = this.sourcesWithLayers[sourceName];\n if (!previous || !sourceWithLayers) continue;\n\n updateLayersAndSource(next, previous, sourceWithLayers, this.mapLibreMap);\n\n const layersToAdd: ToBeAddedLayerSpec[] = [];\n for (const layerSpec of sourceWithLayers._layerSpecs) {\n if (!this.mapLibreMap.getLayer(layerSpec.id)) {\n layersToAdd.push(layerSpec);\n }\n }\n if (layersToAdd.length > 0) {\n addLayers(layersToAdd, this.mapLibreMap);\n // addLayers always adds with visibility 'none'. If the source already has\n // features shown, reveal the new layers to keep them in sync.\n if (sourceWithLayers.shownFeatures.features.length > 0) {\n sourceWithLayers.setLayersVisible(true);\n }\n }\n }\n }\n\n private applyVisibility(visible: boolean): void {\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.sourcesWithLayers[sourceName].setLayersVisible(visible);\n }\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n const sourceNames = Object.keys(this.lastShown) as Array<keyof TSources>;\n this.initSourcesWithLayers(this.config, true);\n if (this.config) this._applyConfig(this.config);\n // Call showOnSource (sync) directly rather than show() (async) — the module is\n // already ready at this point so the waitUntilModuleReady() wrapper is a no-op,\n // and using the sync path keeps restore fully synchronous and avoids leaking\n // unhandled rejections if a shown-features handler throws.\n for (const sourceName of sourceNames) {\n const data = this.lastShown[sourceName];\n if (data) this.showOnSource(sourceName, data);\n }\n }\n\n /**\n * Displays the given GeoJSON data on the named source — or on every source when\n * no name is supplied.\n *\n * Replaces any previously shown data. Layer visibility is set automatically based\n * on whether the feature collection is non-empty.\n *\n * @param data The GeoJSON feature collection to display.\n * @param sourceName The source name (must match a key from\n * {@link CustomGeoJSONModuleConfig.sources}). When omitted, `data` is shown on\n * every source — best used when every source shares the same `FeatureCollection`\n * shape.\n */\n async show<K extends keyof TSources>(data: TSources[K], sourceName?: K): Promise<void> {\n await this.waitUntilModuleReady();\n if (sourceName === undefined) {\n for (const name of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.showOnSource(name, data as TSources[typeof name]);\n }\n return;\n }\n this.showOnSource(sourceName, data);\n }\n\n private showOnSource<K extends keyof TSources>(sourceName: K, data: TSources[K]): void {\n const normalized = ensureFeatureIDs(data);\n this.lastShown[sourceName] = normalized;\n this.sourcesWithLayers[sourceName].show(normalized);\n const handlers = this.shownFeaturesHandlersBySource.get(sourceName);\n if (handlers) {\n for (const handler of handlers) {\n handler(normalized);\n }\n }\n }\n\n /**\n * Clears data from the given source — or from all sources when no name is supplied.\n */\n async clear(sourceName?: keyof TSources): Promise<void> {\n await this.waitUntilModuleReady();\n if (sourceName !== undefined) {\n this.sourcesWithLayers[sourceName].clear();\n delete this.lastShown[sourceName];\n return;\n }\n for (const name of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.sourcesWithLayers[name].clear();\n delete this.lastShown[name];\n }\n }\n\n /**\n * Returns the feature collection currently shown on each source.\n *\n * Sources that have never been shown (or have been cleared) return the empty\n * feature collection that `GeoJSONSourceWithLayers` keeps as initial state.\n */\n getShown(): { [K in keyof TSources]: TSources[K] } {\n const result = {} as { [K in keyof TSources]: TSources[K] };\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n result[sourceName] = this.sourcesWithLayers[sourceName].shownFeatures;\n }\n return result;\n }\n\n /**\n * Toggles visibility of every layer across every source.\n *\n * @remarks\n * Calling {@link show} on a source after `setVisible(false)` will re-reveal that\n * source's layers via `GeoJSONSourceWithLayers`' automatic visibility behaviour.\n * To keep a source hidden, call `setVisible(false)` after the next `show`.\n */\n setVisible(visible: boolean): void {\n this.config = { ...(this.config as CustomGeoJSONModuleConfig<TSources>), visible };\n this.applyVisibility(visible);\n this.emitConfigChange();\n }\n\n /**\n * Per-source events surface.\n *\n * Each key on the returned object is one of the source names declared in\n * {@link CustomGeoJSONModuleConfig.sources}. Each value is a {@link CombinedEvents}\n * instance covering both user interactions (`click`, `hover`, `long-hover`,\n * `contextmenu`) and module lifecycle events (`config-change`, `shown-features`).\n *\n * `config-change` handlers are module-wide (the same handler array is shared\n * across every source's `CombinedEvents`). `shown-features` handlers fire only for\n * their own source.\n *\n * @example\n * ```typescript\n * module.events.buildings.on('click', (feature, lngLat) => { ... });\n * module.events.heatmap.on('shown-features', (data) => { ... });\n * module.events.buildings.on('config-change', (config) => { ... });\n * ```\n */\n get events(): CustomGeoJSONEvents<TSources> {\n const result = {} as CustomGeoJSONEvents<TSources>;\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n const sourceWithLayers = this.sourcesWithLayers[sourceName];\n const shownHandlers = this.shownFeaturesHandlersBySource.get(sourceName) ?? [];\n result[sourceName] = new CombinedEvents(\n new UserEvents<MapGeoJSONFeature>(this.eventsProxy, sourceWithLayers, this.config?.events),\n new ModuleEvents<CustomGeoJSONModuleConfig<TSources>, TSources[typeof sourceName]>(\n this.configChangeHandlers,\n shownHandlers as ((data: TSources[typeof sourceName]) => void)[],\n ),\n );\n }\n return result;\n }\n}\n\nconst validateConfig = <TSources extends Record<string, FeatureCollection>>(\n config: CustomGeoJSONModuleConfig<TSources>,\n): void => {\n if (!config.sources || Object.keys(config.sources).length === 0) {\n throw new Error('CustomGeoJSONModule requires at least one source in config.sources.');\n }\n for (const [sourceName, sourceSpec] of Object.entries(config.sources)) {\n if (!sourceSpec.layers || sourceSpec.layers.length === 0) {\n throw new Error(`CustomGeoJSONModule source \"${sourceName}\" requires at least one layer.`);\n }\n }\n};\n\n// Guarantees every feature has matching top-level `feature.id` and `properties.id`,\n// generating an index-based ID when neither is set. This makes feature lookup reliable\n// across both cluster mode (no `promoteId`, where MapLibre uses the top-level\n// `feature.id`) and non-cluster mode (where `promoteId: 'id'` makes MapLibre treat\n// `properties.id` as the feature id). Without it, features missing IDs collapse onto\n// `feature.id === undefined` and event handlers can't distinguish between them.\nconst ensureFeatureIDs = <T extends FeatureCollection>(data: T): T => {\n let needsRewrite = false;\n for (const feature of data.features) {\n if (feature.id == null || feature.properties?.id !== feature.id) {\n needsRewrite = true;\n break;\n }\n }\n if (!needsRewrite) return data;\n return {\n ...data,\n features: data.features.map((feature, index) => {\n const id = feature.id ?? feature.properties?.id ?? index;\n return {\n ...feature,\n id,\n properties: { ...feature.properties, id },\n };\n }),\n } as T;\n};\n","// ─── Geometry (utility) ──────────────────────────────────────────────────────\n\n/** The world ring produced by turf.mask */\nexport const TURF_MASK_WORLD_RING = [\n [180, 90],\n [-180, 90],\n [-180, -90],\n [180, -90],\n [180, 90],\n];\n\n// ─── Default geometry styling ─────────────────────────────────────────────────\n\n/** Default fill/line color for geometries. */\nexport const DEFAULT_COLOR = '#0A3653';\n\n/** Default fill opacity. */\nexport const DEFAULT_FILL_OPACITY = 0.15;\n\n/** Default line color. */\nexport const DEFAULT_LINE_OPACITY = 1;\n\n/** Default line width in pixels. */\nexport const DEFAULT_LINE_WIDTH = 2;\n\n// ─── Themed geometry styling (outline / filled / inverted) ───────────────────\n\n/** Fill opacity for the outline theme (transparent fill). */\nexport const OUTLINE_THEME_FILL_OPACITY = 0;\n\n/** Line color for the outline theme. */\nexport const OUTLINE_THEME_LINE_COLOR = '#555555';\n\n/** Line opacity for the outline theme. */\nexport const OUTLINE_THEME_LINE_OPACITY = 0.9;\n\n/** Line width for the outline theme. */\nexport const OUTLINE_THEME_LINE_WIDTH = 5;\n\n/** Fill opacity for the filled/inverted theme. */\nexport const FILLED_THEME_FILL_OPACITY = 0.6;\n\n/** Line color for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_COLOR = 'grey';\n\n/** Line opacity for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_OPACITY = 1;\n\n/** Line width for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_WIDTH = 1;\n\n// ─── Label styling ────────────────────────────────────────────────────────────\n\n/** Text color shared by title and border labels. */\nexport const TITLE_COLOR = '#333333';\n\n/** Text halo color shared by title and border labels. */\nexport const TITLE_HALO_COLOR = '#FFFFFF';\n\n/** Text size for geometry title (point) labels. */\nexport const TITLE_SIZE = 15;\n\n/** Text padding for geometry title (point) labels. */\nexport const TITLE_PADDING = 5;\n\n/** Minimum zoom level for border labels. */\nexport const BORDER_LABEL_MIN_ZOOM = 3;\n\n/** Text size for border labels. */\nexport const BORDER_LABEL_TEXT_SIZE = 15;\n\n/** Symbol spacing for border labels, in pixels. */\nexport const BORDER_LABEL_SYMBOL_SPACING = 200;\n\n/** Text halo width for border labels, in pixels. */\nexport const BORDER_LABEL_TEXT_HALO_WIDTH = 2;\n","import { isNil } from 'lodash-es';\nimport type { FillLayerSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, SymbolLayerSpecWithoutSource } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport type { GeometriesModuleConfig } from '../types/geometriesModuleConfig';\nimport {\n BORDER_LABEL_MIN_ZOOM,\n BORDER_LABEL_SYMBOL_SPACING,\n BORDER_LABEL_TEXT_HALO_WIDTH,\n BORDER_LABEL_TEXT_SIZE,\n DEFAULT_COLOR,\n DEFAULT_FILL_OPACITY,\n DEFAULT_LINE_OPACITY,\n DEFAULT_LINE_WIDTH,\n OUTLINE_THEME_FILL_OPACITY,\n OUTLINE_THEME_LINE_COLOR,\n OUTLINE_THEME_LINE_OPACITY,\n OUTLINE_THEME_LINE_WIDTH,\n TITLE_COLOR,\n TITLE_HALO_COLOR,\n TITLE_PADDING,\n TITLE_SIZE,\n} from './constants';\n\n/**\n * @ignore\n */\nexport const geometryFillSpec: LayerSpecTemplate<FillLayerSpecification> = {\n type: 'fill',\n paint: {\n 'fill-color': ['coalesce', ['get', 'color'], DEFAULT_COLOR],\n 'fill-opacity': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_FILL_OPACITY, DEFAULT_FILL_OPACITY],\n 'fill-antialias': false,\n },\n};\n\n/**\n * @ignore\n */\nexport const geometryOutlineSpec: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n paint: {\n 'line-color': [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n ['coalesce', ['get', 'color'], OUTLINE_THEME_LINE_COLOR],\n ['coalesce', ['get', 'color'], DEFAULT_COLOR],\n ],\n 'line-opacity': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_OPACITY, DEFAULT_LINE_OPACITY],\n 'line-width': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_WIDTH, DEFAULT_LINE_WIDTH],\n },\n};\n\n/**\n * Builds Geometry layer specifications for fill and outline layers.\n * @ignore\n */\nexport const buildGeometryLayerSpecs = (\n fillLayerId: string,\n outlineLayerId: string,\n config?: GeometriesModuleConfig,\n): [SymbolLayerSpecWithoutSource, SymbolLayerSpecWithoutSource] => {\n const colorConfig = config?.colorConfig;\n const lineConfig = config?.lineConfig;\n\n const fillLayerSpec = {\n ...geometryFillSpec,\n id: fillLayerId,\n paint: {\n ...geometryFillSpec.paint,\n ...(!isNil(colorConfig?.fillOpacity) && { 'fill-opacity': colorConfig?.fillOpacity }),\n ...(colorConfig?.fillColor && { 'fill-color': ['get', 'color'] }),\n },\n } as unknown as SymbolLayerSpecWithoutSource;\n\n const outlineLayerSpec = {\n ...geometryOutlineSpec,\n id: outlineLayerId,\n paint: {\n ...geometryOutlineSpec.paint,\n ...(!isNil(lineConfig?.lineColor) && { 'line-color': lineConfig?.lineColor }),\n ...(!isNil(lineConfig?.lineWidth) && { 'line-width': lineConfig?.lineWidth }),\n ...(!isNil(lineConfig?.lineOpacity) && { 'line-opacity': lineConfig?.lineOpacity }),\n },\n } as unknown as SymbolLayerSpecWithoutSource;\n\n return [fillLayerSpec, outlineLayerSpec];\n};\n\n/**\n * Build geometry layer specification for title.\n * @ignore\n */\nexport const buildGeometryTitleLayerSpec = (\n layerId: string,\n config?: GeometriesModuleConfig,\n): Omit<SymbolLayerSpecification, 'source'> => {\n const textConfig = config?.textConfig;\n\n return {\n type: 'symbol',\n id: layerId,\n layout: {\n 'text-field': ['get', 'title'],\n ...(textConfig?.textField && { 'text-field': textConfig.textField }),\n 'text-padding': TITLE_PADDING,\n 'text-size': TITLE_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n 'symbol-placement': 'point',\n },\n paint: {\n 'text-color': TITLE_COLOR,\n 'text-halo-color': TITLE_HALO_COLOR,\n 'text-halo-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 10, 1.5],\n 'text-translate-anchor': 'viewport',\n },\n };\n};\n\n/**\n * Build geometry layer specification for line labels.\n * Adds labels along the polygon border.\n * @ignore\n */\nexport const buildGeometryLineLabelLayerSpec = (\n layerId: string,\n config?: GeometriesModuleConfig,\n): Omit<SymbolLayerSpecification, 'source'> => {\n const lineLabelConfig = config?.lineLabelConfig;\n const minzoom = lineLabelConfig?.minZoom ?? BORDER_LABEL_MIN_ZOOM;\n\n return {\n type: 'symbol',\n id: layerId,\n minzoom,\n layout: {\n 'text-field': ['get', 'title'],\n 'symbol-placement': 'line',\n 'text-size': lineLabelConfig?.textSize ?? BORDER_LABEL_TEXT_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n 'symbol-spacing': lineLabelConfig?.symbolSpacing ?? BORDER_LABEL_SYMBOL_SPACING,\n 'text-keep-upright': true,\n 'text-offset': lineLabelConfig?.textOffset ?? [0, 1],\n },\n paint: {\n 'text-color': lineLabelConfig?.textColor ?? TITLE_COLOR,\n 'text-halo-color': lineLabelConfig?.textHaloColor ?? TITLE_HALO_COLOR,\n 'text-halo-width': lineLabelConfig?.textHaloWidth ?? BORDER_LABEL_TEXT_HALO_WIDTH,\n ...(!isNil(lineLabelConfig?.textOpacity) && { 'text-opacity': lineLabelConfig?.textOpacity }),\n },\n };\n};\n","export const colorPalettes = {\n warm: [\n '#793F0D',\n '#AC703D',\n '#C38E63',\n '#E49969',\n '#E5AE86',\n '#EEC5A9',\n '#6E7649',\n '#9D9754',\n '#C7C397',\n '#B4A851',\n '#DFD27C',\n '#E7E3B5',\n '#846D74',\n '#B7A6AD',\n '#D3C9CE',\n ],\n browns: [\n '#edc4b3',\n '#e6b8a2',\n '#deab90',\n '#d69f7e',\n '#cd9777',\n '#c38e70',\n '#b07d62',\n '#9d6b53',\n '#8a5a44',\n '#774936',\n ],\n cold: ['#344464', '#548ca4', '#549cac', '#2c445c', '#a4ccd4', '#acbccc', '#b4c4d4', '#acd4cc', '#5c8ca4'],\n fadedBlues: [\n '#152033',\n '#1d2d44',\n '#2e455d',\n '#3e5c76',\n '#4c6884',\n '#597491',\n '#67809e',\n '#6e86a5',\n '#7b91ad',\n '#879bb4',\n ],\n blues: ['#03045e', '#023e8a', '#0077b6', '#0096c7', '#00b4d8', '#48cae4', '#90e0ef', '#ade8f4', '#caf0f8'],\n greens: ['#004b23', '#006400', '#007200', '#008000', '#38b000', '#70e000', '#9ef01a', '#ccff33'],\n fadedGreenToBlue: [\n '#d9ed92',\n '#b5e48c',\n '#99d98c',\n '#76c893',\n '#52b69a',\n '#34a0a4',\n '#168aad',\n '#1a759f',\n '#1e6091',\n '#184e77',\n ],\n blueToRed: [\n '#033270',\n '#1368aa',\n '#4091c9',\n '#9dcee2',\n '#fedfd4',\n '#f29479',\n '#f26a4f',\n '#ef3c2d',\n '#cb1b16',\n '#65010c',\n ],\n greenToYellow: [\n '#007f5f',\n '#2b9348',\n '#55a630',\n '#80b918',\n '#aacc00',\n '#bfd200',\n '#d4d700',\n '#dddf00',\n '#eeef20',\n '#ffff3f',\n ],\n pastel: [\n '#fec5bb',\n '#fcd5ce',\n '#fae1dd',\n '#f8edeb',\n '#e8e8e4',\n '#d8e2dc',\n '#ece4db',\n '#ffe5d9',\n '#ffd7ba',\n '#fec89a',\n ],\n retro: [\n '#f94144',\n '#f3722c',\n '#f8961e',\n '#f9844a',\n '#f9c74f',\n '#90be6d',\n '#43aa8b',\n '#4d908e',\n '#577590',\n '#277da1',\n ],\n contrastRetro: [\n '#001219',\n '#005f73',\n '#0a9396',\n '#94d2bd',\n '#e9d8a6',\n '#ee9b00',\n '#ca6702',\n '#bb3e03',\n '#ae2012',\n '#9b2226',\n ],\n fadedRainbow: ['#ffadad', '#ffd6a5', '#fdffb6', '#caffbf', '#9bf6ff', '#a0c4ff', '#bdb2ff', '#ffc6ff', '#fffffc'],\n pastelRainbow: [\n '#54478c',\n '#2c699a',\n '#048ba8',\n '#0db39e',\n '#16db93',\n '#83e377',\n '#b9e769',\n '#efea5a',\n '#f1c453',\n '#f29e4c',\n ],\n};\n\nexport type ColorPaletteOptions = keyof typeof colorPalettes;\nexport const colorPaletteIDs = Object.keys(colorPalettes) as ColorPaletteOptions[];\n","import {\n bboxCenter,\n bboxFromCoordsArray,\n generateId,\n type PolygonFeature,\n PolygonFeatures,\n} from '@tomtom-org/maps-sdk/core';\nimport { mask } from '@turf/turf';\nimport type { Feature, FeatureCollection, GeoJsonProperties, MultiPolygon, Point, Polygon, Position } from 'geojson';\nimport type { DataDrivenPropertyValueSpecification } from 'maplibre-gl';\nimport { type ColorPaletteOptions, colorPalettes } from './layers/colorPalettes';\nimport type { GeometriesModuleConfig } from './types/geometriesModuleConfig';\nimport type { DisplayGeometryProps, ExtraGeometryDisplayProps } from './types/geometryDisplayProps';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/**\n * Build geometry Title. The type can be a string or a Maplibre expression.\n * @param feature - Geometry\n * @param config - Geometry module configuration\n * @returns\n */\nconst buildTitle = (\n feature: Feature<Polygon | MultiPolygon, DisplayGeometryProps | GeoJsonProperties>,\n config: GeometriesModuleConfig,\n): DataDrivenPropertyValueSpecification<string> | string | undefined => {\n if (config.textConfig?.textField) {\n return config.textConfig.textField;\n }\n return feature.properties?.address?.freeformAddress;\n};\n\n/**\n * Builds a geometry color string or MapLibre expression.\n * @param config - Geometry module configuration\n * @param index - Number to use as index to pick color from palette option\n */\nconst buildColor = (\n config: GeometriesModuleConfig,\n index: number,\n): DataDrivenPropertyValueSpecification<string> | string | undefined => {\n const color = config?.colorConfig?.fillColor;\n if (typeof color === 'string' && colorPalettes[color as ColorPaletteOptions]) {\n const palette = colorPalettes[color as ColorPaletteOptions];\n return palette[index % palette.length];\n }\n return color;\n};\n\n/** Finds the longest coordinate array in a MultiPolygon for title placement. */\nconst getLongestArray = (coordinates: Position[][][]) =>\n coordinates.flat().reduce((result, coord) => (coord.length > result.length ? coord : result), []);\n\n/**\n * Converts a polygon into a world-minus-polygon donut for the `'inverted'` theme.\n *\n * Uses `turf.mask` to punch the polygon as a hole in a world bounding box.\n *\n * @remarks\n * Called automatically when a feature or config has `theme: 'inverted'`.\n *\n * @group Geometries\n */\nexport const invertFeature = (feature: PolygonFeature): PolygonFeature => {\n const masked = mask(feature);\n return {\n ...feature,\n geometry: masked.geometry,\n } as PolygonFeature;\n};\n\ntype InputFeatureProps = {\n theme?: GeometryTheme;\n title?: string;\n color?: string;\n id?: string;\n};\n/**\n * Prepares polygon features for display by applying theme, colors, and titles.\n *\n * @param geometry - Polygon features to prepare.\n * @param config - Module configuration for styling.\n * @returns Processed features ready for MapLibre rendering.\n * @ignore\n */\nexport const prepareGeometryForDisplay = (\n geometry: PolygonFeatures<GeoJsonProperties | DisplayGeometryProps>,\n config: GeometriesModuleConfig = {},\n): PolygonFeatures<ExtraGeometryDisplayProps> => ({\n ...geometry,\n features: geometry.features.map((feature, index) => {\n const props = (feature.properties ?? {}) as InputFeatureProps;\n const effectiveTheme = props.theme ?? config.theme;\n const processedFeature = effectiveTheme === 'inverted' ? invertFeature(feature as PolygonFeature) : feature;\n const processedProps = (processedFeature.properties ?? {}) as InputFeatureProps;\n // buildTitle/buildColor may return MapLibre expressions; cast since output type expects string\n const title = (processedProps.title ?? buildTitle(processedFeature, config)) as string | undefined;\n const color = (processedProps.color ?? buildColor(config, index)) as string | undefined;\n return {\n ...processedFeature,\n properties: {\n ...processedFeature.properties,\n ...(effectiveTheme && { theme: effectiveTheme }),\n ...(title && { title }),\n ...(color && { color }),\n id: processedProps.id ?? generateId(),\n },\n };\n }),\n});\n\n/**\n * Create a Feature<Point> with coordinates where title will be placed.\n * If feature properties contains a coordinates value, it will use it.\n * In case there is not coordinates value, it will get the biggest Polygon inside a feature and calculate\n * the bounding box for those coordinates and finally calculate the bounding box center to place the title.\n * @param geometries\n * @returns\n * @ignore\n */\nexport const prepareTitleForDisplay = (geometries: PolygonFeatures): FeatureCollection<Point> => {\n const features = geometries.features.map((feature) => {\n let coordinates: Position[] | Position | null;\n\n if (feature.properties?.placeCoordinates) {\n coordinates = feature.properties?.placeCoordinates;\n } else if (feature.geometry.type === 'MultiPolygon') {\n const biggestPolygon = getLongestArray(feature.geometry.coordinates);\n const bbox = bboxFromCoordsArray(biggestPolygon);\n coordinates = (bbox && bboxCenter(bbox)) || null;\n } else {\n coordinates = feature.geometry.coordinates.flat();\n }\n\n const id = feature.id ?? feature.properties?.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: { type: 'Point', coordinates },\n properties: {\n ...feature.properties,\n id, // we need id in properties due to promoteId feature\n },\n } as Feature<Point>;\n });\n\n return { type: 'FeatureCollection', bbox: geometries.bbox, features };\n};\n","import type { PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection, Point } from 'geojson';\nimport type { SymbolLayerSpecification } from 'maplibre-gl';\nimport type { SymbolLayerSpecWithoutSource, ToBeAddedLayerSpec } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { changeLayerProps, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildGeometryLayerSpecs,\n buildGeometryLineLabelLayerSpec,\n buildGeometryTitleLayerSpec,\n} from './layers/geometryLayers';\nimport { prepareGeometryForDisplay, prepareTitleForDisplay } from './prepareGeometryForDisplay';\nimport type {\n GeometriesModuleConfig,\n GeometryBeforeLayerConfig,\n GeometryTextConfig,\n} from './types/geometriesModuleConfig';\n\n/**\n * IDs of sources and layers from a geometry module.\n */\ntype GeometrySourcesWithLayers = {\n geometry: GeoJSONSourceWithLayers<PolygonFeatures>;\n geometryLabel: GeoJSONSourceWithLayers<FeatureCollection<Point>>;\n};\n\n/**\n * Geometries Module for displaying polygon areas with custom styling on the map.\n *\n * This module enables visualization of geographic areas (polygons) with customizable\n * colors, borders, and labels. Ideal for displaying search results, administrative\n * boundaries, service areas, or any polygon-based geographic data.\n *\n * @remarks\n * **Features:**\n * - Display single or multiple polygon geometries\n * - Customizable fill colors and opacity\n * - Configurable borders (color, width, opacity)\n * - Optional text labels for geometries\n * - Support for data-driven styling via MapLibre expressions\n * - Layer ordering control\n * - Event handling for user interactions\n *\n * **Data Format:**\n * - Accepts GeoJSON Polygon and MultiPolygon features\n * - Supports FeatureCollection for multiple geometries\n * - Compatible with TomTom Search API geometry results\n *\n * **Styling:**\n * - Use predefined color palettes or custom colors\n * - Apply MapLibre expressions for dynamic styling\n * - Per-feature styling via feature properties\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { GeometriesModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Initialize module\n * const geometriesModule = await GeometriesModule.get(map);\n *\n * // Display a polygon\n * await geometriesModule.show({\n * type: 'Feature',\n * geometry: {\n * type: 'Polygon',\n * coordinates: [[[4.88, 52.37], [4.89, 52.37], [4.89, 52.38], [4.88, 52.38], [4.88, 52.37]]]\n * },\n * properties: {\n * title: 'Area of Interest'\n * }\n * });\n * ```\n *\n * @example\n * Custom styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * colorConfig: {\n * fillColor: '#FF5733',\n * fillOpacity: 0.3\n * },\n * lineConfig: {\n * lineColor: '#C70039',\n * lineWidth: 3\n * },\n * textConfig: {\n * textField: ['get', 'name']\n * }\n * });\n *\n * await geometriesModule.show(polygonFeatures);\n * ```\n *\n * @example\n * Multiple geometries with different colors:\n * ```typescript\n * await geometriesModule.show({\n * type: 'FeatureCollection',\n * features: [\n * {\n * type: 'Feature',\n * geometry: { type: 'Polygon', coordinates: [...] },\n * properties: { color: '#FF0000', title: 'Red Zone' }\n * },\n * {\n * type: 'Feature',\n * geometry: { type: 'Polygon', coordinates: [...] },\n * properties: { color: '#00FF00', title: 'Green Zone' }\n * }\n * ]\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * geometriesModule.events.on('click', (feature, lngLat) => {\n * console.log('Clicked geometry:', feature.properties.title);\n * console.log('At coordinates:', lngLat);\n * });\n *\n * geometriesModule.events.on('hover', (feature) => {\n * showTooltip(feature.properties.title);\n * });\n * ```\n *\n * @see [Geometries Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/geometries)\n *\n * @group Geometries\n */\nexport class GeometriesModule extends AbstractMapModule<GeometrySourcesWithLayers, GeometriesModuleConfig> {\n private titleLayerSpecs!: SymbolLayerSpecWithoutSource;\n private geometryFillLayerSpecs!: SymbolLayerSpecWithoutSource;\n private geometryOutlineLayerSpecs!: SymbolLayerSpecWithoutSource;\n private lineLabelLayerSpecs: SymbolLayerSpecWithoutSource | null = null;\n\n private sourceID!: string;\n private fillLayerID!: string;\n private outlineLayerID!: string;\n private lineLabelLayerID!: string;\n\n private titleSourceID!: string;\n private titleLayerID!: string;\n\n // Cached for restoreDataAndConfigImpl on style change.\n private lastRawInput: PolygonFeatures | null = null;\n private readonly shownFeaturesHandlers: ((features: PolygonFeatures) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n *\n * @remarks\n * **Configuration Options:**\n * - `colorConfig`: Fill color and opacity settings\n * - `lineConfig`: Border/outline styling\n * - `textConfig`: Label display configuration\n * - `beforeLayerConfig`: Layer ordering (place above/below other layers)\n *\n * **Multiple Instances:**\n * You can create multiple GeometriesModule instances on the same map,\n * each managing different sets of geometries with different styles.\n *\n * @example\n * Default initialization:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map);\n * ```\n *\n * @example\n * With custom styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * colorConfig: {\n * fillColor: 'blue',\n * fillOpacity: 0.25\n * },\n * lineConfig: {\n * lineColor: 'darkblue',\n * lineWidth: 2,\n * lineOpacity: 0.8\n * },\n * textConfig: {\n * textField: ['get', 'title']\n * },\n * beforeLayerConfig: 'top'\n * });\n * ```\n *\n * @example\n * Data-driven styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * colorConfig: {\n * // Color based on feature properties\n * fillColor: [\n * 'match',\n * ['get', 'type'],\n * 'residential', '#FFEB3B',\n * 'commercial', '#2196F3',\n * 'industrial', '#9E9E9E',\n * '#E0E0E0' // default\n * ],\n * fillOpacity: 0.4\n * }\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: GeometriesModuleConfig): Promise<GeometriesModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new GeometriesModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: GeometriesModuleConfig) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: GeometriesModuleConfig, restore?: boolean): GeometrySourcesWithLayers {\n if (!restore) {\n this.sourceID = `geometry-${this.instanceIndex}`;\n this.titleSourceID = `geometryTitle-${this.instanceIndex}`;\n const layerIdPrefix = `geometry-${this.instanceIndex}`;\n this.fillLayerID = `${layerIdPrefix}_Fill`;\n this.outlineLayerID = `${layerIdPrefix}_Outline`;\n this.lineLabelLayerID = `${layerIdPrefix}_LineLabel`;\n this.titleLayerID = `${layerIdPrefix}_Title`;\n }\n\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n config,\n );\n const titleLayerSpec = buildGeometryTitleLayerSpec(this.titleLayerID, config);\n this.titleLayerSpecs = titleLayerSpec;\n this.geometryFillLayerSpecs = geometryFillSpec;\n this.geometryOutlineLayerSpecs = geometryOutlineSpec;\n\n const lineLabelSpec =\n config?.lineLabelConfig === undefined\n ? null\n : buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, config);\n this.lineLabelLayerSpecs = lineLabelSpec as SymbolLayerSpecWithoutSource | null;\n\n return {\n geometry: new GeoJSONSourceWithLayers(this.mapLibreMap, this.sourceID, [\n { ...geometryFillSpec },\n { ...geometryOutlineSpec },\n ...(lineLabelSpec ? [lineLabelSpec as ToBeAddedLayerSpec<SymbolLayerSpecification>] : []),\n ]),\n geometryLabel: new GeoJSONSourceWithLayers(this.mapLibreMap, this.titleSourceID, [\n titleLayerSpec as ToBeAddedLayerSpec<SymbolLayerSpecification>,\n ]),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: GeometriesModuleConfig | undefined) {\n if (config?.textConfig || config?.colorConfig || config?.lineConfig) {\n this.updateLayerAndData(config);\n }\n if (config?.beforeLayerConfig) {\n this._moveBeforeLayer(config.beforeLayerConfig, false);\n }\n return config;\n }\n\n private moveBeforeLayerID(beforeLayerId?: string) {\n for (const layer of this.sourcesWithLayers.geometry.sourceAndLayerIDs.layerIDs) {\n this.mapLibreMap.moveLayer(layer, beforeLayerId);\n }\n }\n\n /**\n * Positions the geometry layers relative to other map layers.\n *\n * @param layerConfig - Layer positioning configuration.\n * Can be `'top'` to place above all layers, or a specific layer ID.\n *\n * @remarks\n * **Use Cases:**\n * - Place geometries above base map but below labels\n * - Ensure geometries appear above/below specific features\n * - Control visual hierarchy of multiple data layers\n *\n * **Available Layer IDs:**\n * Use predefined layer IDs from `mapStyleLayerIDs` or custom layer IDs.\n *\n * @example\n * ```typescript\n * import { mapStyleLayerIDs } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Place below labels\n * geometries.moveBeforeLayer(mapStyleLayerIDs.lowestLabel);\n *\n * // Place on top\n * geometries.moveBeforeLayer('top');\n * ```\n */\n moveBeforeLayer(layerConfig: GeometryBeforeLayerConfig) {\n this._moveBeforeLayer(layerConfig);\n }\n\n private _moveBeforeLayer(layerConfig: GeometryBeforeLayerConfig, updateConfig = true) {\n this.moveBeforeLayerID(layerConfig === 'top' ? this.titleLayerID : mapStyleLayerIDs[layerConfig]);\n if (updateConfig) {\n this.config = { ...this.config, beforeLayerConfig: layerConfig };\n this.emitConfigChange();\n }\n }\n\n /**\n * Updates the text/label configuration for displayed geometries.\n *\n * @param textConfig - New text configuration settings.\n *\n * @remarks\n * **Configuration:**\n * - `textField`: MapLibre expression for label text content\n * - Supports dynamic text based on feature properties\n * - Changes apply to currently shown and future geometries\n *\n * @example\n * ```typescript\n * // Show feature property as label\n * geometries.applyTextConfig({\n * textField: ['get', 'name']\n * });\n *\n * // Conditional labels\n * geometries.applyTextConfig({\n * textField: [\n * 'case',\n * ['has', 'label'],\n * ['get', 'label'],\n * ['get', 'title']\n * ]\n * });\n * ```\n */\n applyTextConfig(textConfig: GeometryTextConfig) {\n const config = { ...this.config, textConfig };\n this.updateLayerAndData(config);\n // TODO: is this consistent with _applyConfig?\n this.sourcesWithLayers.geometryLabel.show(\n prepareTitleForDisplay(this.sourcesWithLayers.geometry.shownFeatures),\n );\n this.config = config;\n this.emitConfigChange();\n }\n\n private updateLayerAndData(config: GeometriesModuleConfig) {\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n config,\n );\n const newTitleLayerSpecs = buildGeometryTitleLayerSpec(this.titleLayerID, config);\n\n changeLayerProps(geometryFillSpec, this.geometryFillLayerSpecs, this.mapLibreMap);\n changeLayerProps(geometryOutlineSpec, this.geometryOutlineLayerSpecs, this.mapLibreMap);\n changeLayerProps(newTitleLayerSpecs, this.titleLayerSpecs, this.mapLibreMap);\n\n this.geometryFillLayerSpecs = geometryFillSpec;\n this.geometryOutlineLayerSpecs = geometryOutlineSpec;\n this.titleLayerSpecs = newTitleLayerSpecs;\n\n if (this.lineLabelLayerSpecs) {\n const newLineLabelSpec = buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, config);\n changeLayerProps(\n newLineLabelSpec as SymbolLayerSpecWithoutSource,\n this.lineLabelLayerSpecs,\n this.mapLibreMap,\n );\n this.lineLabelLayerSpecs = newLineLabelSpec as SymbolLayerSpecWithoutSource;\n }\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previousInput = this.lastRawInput;\n this.initSourcesWithLayers(this.config, true);\n this.config && this._applyConfig(this.config);\n if (previousInput) {\n // Fire-and-forget: base class requires sync, but show() is async.\n void this.show(previousInput);\n }\n }\n\n /**\n * Displays the given polygon geometries on the map.\n *\n * @param geometries - Polygon features to display. Can be a single Feature,\n * array of Features, or a FeatureCollection.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown geometries\n * - Applies current module styling configuration\n * - Waits for module to be ready before displaying\n * - Automatically handles both Polygon and MultiPolygon types\n *\n * **Feature Properties:**\n * - `title`: Used for labels if text config is set\n * - `color`: Override fill color per feature\n * - Custom properties accessible in styling expressions\n *\n * @example\n * Single polygon:\n * ```typescript\n * await geometries.show({\n * type: 'Feature',\n * geometry: {\n * type: 'Polygon',\n * coordinates: [[[4.88, 52.37], [4.89, 52.37], [4.89, 52.38], [4.88, 52.37]]]\n * },\n * properties: {\n * title: 'Amsterdam Center',\n * color: '#FF5733'\n * }\n * });\n * ```\n *\n * @example\n * Multiple polygons:\n * ```typescript\n * await geometries.show({\n * type: 'FeatureCollection',\n * features: [\n * { type: 'Feature', geometry: {...}, properties: {...} },\n * { type: 'Feature', geometry: {...}, properties: {...} }\n * ]\n * });\n * ```\n *\n * @example\n * From search API response:\n * ```typescript\n * import { search } from '@tomtom-international/maps-sdk-js/services';\n *\n * const result = await search.geometrySearch({\n * query: 'Amsterdam',\n * geometryList: [{ type: 'CIRCLE', position: [52.37, 4.89], radius: 5000 }]\n * });\n *\n * if (result.results[0].dataSources?.geometry) {\n * await geometries.show(result.results[0].dataSources.geometry);\n * }\n * ```\n */\n async show(geometries: PolygonFeatures) {\n await this.waitUntilModuleReady();\n this.lastRawInput = geometries;\n const { transformFeaturesForDisplay } = this.config ?? {};\n const transformed = transformFeaturesForDisplay ? transformFeaturesForDisplay(geometries) : geometries;\n const geometry = this.sourcesWithLayers.geometry;\n geometry.show(prepareGeometryForDisplay(transformed, this.config));\n this.sourcesWithLayers.geometryLabel.show(prepareTitleForDisplay(geometry.shownFeatures));\n for (const handler of this.shownFeaturesHandlers) {\n handler(geometries);\n }\n }\n\n /**\n * Removes all geometries from the map.\n *\n * @remarks\n * - Clears both geometry layers and labels\n * - Does not reset styling configuration\n * - Module remains initialized and ready for new data\n *\n * @example\n * ```typescript\n * // Clear displayed geometries\n * await geometries.clear();\n *\n * // Show new geometries\n * await geometries.show(newGeometries);\n * ```\n */\n async clear() {\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.geometry.clear();\n this.sourcesWithLayers.geometryLabel.clear();\n }\n\n /**\n * Returns the currently shown geometries.\n *\n * @returns The geometries currently displayed on the map.\n *\n * @remarks\n * Returns the exact data that was passed to the `show()` method.\n *\n * @example\n * ```typescript\n * const shown = geometriesModule.getShown();\n * console.log(`Geometries: ${shown.geometry.features.length}`);\n * ```\n */\n getShown() {\n return {\n geometry: this.sourcesWithLayers.geometry.shownFeatures,\n geometryLabel: this.sourcesWithLayers.geometryLabel.shownFeatures,\n };\n }\n\n /**\n * Gets the events interface for handling user interactions with geometries.\n *\n * @returns A `UserEvents` instance for registering event handlers.\n *\n * @remarks\n * **Supported Events:**\n * - `click`: User clicks on a geometry\n * - `contextmenu`: User right-clicks on a geometry\n * - `hover`: Mouse enters a geometry\n * - `long-hover`: Mouse hovers over geometry for extended time\n *\n * **Event Features:**\n * - Receive the original feature data passed to `show()`\n * - Access feature properties and geometry\n * - Get click/hover coordinates\n *\n * @example\n * Basic event handling:\n * ```typescript\n * geometries.events.on('click', (feature, lngLat) => {\n * console.log('Clicked:', feature.properties);\n * console.log('Location:', lngLat);\n * });\n * ```\n *\n * @example\n * Multiple handlers:\n * ```typescript\n * // Highlight on hover\n * geometries.events.on('hover', (feature) => {\n * highlightGeometry(feature.id);\n * });\n *\n * // Show details on click\n * geometries.events.on('click', (feature) => {\n * showDetailPanel(feature.properties);\n * });\n *\n * // Context menu\n * geometries.events.on('contextmenu', (feature, lngLat) => {\n * showContextMenu(lngLat, feature);\n * });\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, GeometriesModuleConfig, PolygonFeatures> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.geometry, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n}\n","import { type PolygonFeature, type PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport { difference, featureCollection, mask } from '@turf/turf';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/** Custom label generator for reachable range features. */\nexport type ReachableRangeLabelFn = (feature: PolygonFeature, index: number) => string;\n\n/** Unit suffix for each budget type, used for auto-generated labels. */\nconst BUDGET_UNITS: Readonly<Record<string, string>> = {\n timeMinutes: 'min',\n distanceKM: 'km',\n remainingChargeCPT: '% remaining',\n spentChargePCT: '% spent',\n spentFuelLiters: 'L',\n};\n\n/**\n * Splits each reachable-range polygon into two features for the inverted theme:\n * - Donut fill (no `theme` prop) — pre-inverted geometry via `mask`/`difference`; no border or label.\n * - Border polygon (`theme: 'filled'`) — original geometry; fill suppressed; carries border and line label.\n *\n * Separating fills and borders prevents the line-label layer from tracing inner donut holes.\n *\n * Donut fills intentionally have `theme` stripped. `prepareGeometryForDisplay` inverts any feature\n * whose resolved theme is `'inverted'` (`props.theme ?? config.theme`). Since donut geometry is already\n * pre-inverted via `mask`/`difference`, stripping `theme` prevents double inversion without the need of\n * a flag or marker leaking into the user-facing API.\n */\nconst buildDonutFeatures = (features: PolygonFeature[]): PolygonFeature[] => {\n const donutFills = features.map((feature, i) => {\n const donutGeo = i === 0 ? mask(feature) : difference(featureCollection([features[i - 1], feature]));\n const { title: _title, theme: _theme, ...rest } = feature.properties ?? {};\n return { ...feature, geometry: donutGeo?.geometry ?? feature.geometry, properties: rest } as PolygonFeature;\n });\n\n const borderPolygons = features.map((feature) => ({\n ...feature,\n id: undefined,\n properties: { ...feature.properties, theme: 'filled' as GeometryTheme },\n })) as PolygonFeature[];\n\n return [...donutFills, ...borderPolygons];\n};\n\n/**\n * Attaches labels and applies theme-specific geometry to reachable range polygon features.\n *\n * Features must be ordered **largest budget first** (e.g. 30 min, 20 min, 10 min).\n * For the `inverted` theme, polygons are converted to donuts and one extra innermost feature is appended.\n *\n * @param features Polygon features from `calculateReachableRange`, ordered largest budget first.\n * @param labels Label per feature in matching order (e.g. `['30 min', '20 min', '10 min']`).\n * @param theme Visual theme. Defaults to `'filled'`.\n *\n * @example\n * ```typescript\n * const features = [range30min, range20min, range10min];\n * const labels = ['30 min', '20 min', '10 min'];\n *\n * module.show({\n * type: 'FeatureCollection',\n * features: buildReachableRangeFeatures(features, labels, 'inverted'),\n * });\n * ```\n *\n * @group Geometries\n */\nexport const buildReachableRangeFeatures = (\n features: PolygonFeature[],\n labels: string[],\n theme: GeometryTheme = 'filled',\n): PolygonFeature[] => {\n const labelled = features.map((f, i) => ({\n ...f,\n properties: { ...f.properties, title: labels[i], theme },\n }));\n if (theme === 'inverted') {\n return buildDonutFeatures(labelled);\n }\n return labelled;\n};\n\n/**\n * Prepares the output of `calculateReachableRanges` for display with {@link GeometriesModule}.\n *\n * Derives labels from each feature's `budget` property by default, applies theme-specific\n * geometry, and preserves the bounding box. Pass the returned value directly to `module.show()`.\n *\n * Note: when using {@link reachableRangeGeometryConfig} this is called automatically — you only\n * need this function for explicit control over labels or theme.\n *\n * Features must be ordered **largest budget first** (e.g. 30 min, 20 min, 10 min).\n *\n * @param result Raw output of `calculateReachableRanges`.\n * @param theme Visual theme. Defaults to `'filled'`.\n * @param label Custom label generator; when omitted, labels derived from `budget` property.\n *\n * @example\n * ```typescript\n * // explicit control:\n * module.show(prepareReachableRangesForDisplay(result, 'inverted'));\n * module.show(prepareReachableRangesForDisplay(result, 'filled', (f) => myLabel(f)));\n * ```\n *\n * @group Geometries\n */\nexport const prepareReachableRangesForDisplay = (\n result: PolygonFeatures,\n theme: GeometryTheme = 'filled',\n label?: ReachableRangeLabelFn,\n): PolygonFeatures => {\n const features = result.features as PolygonFeature[];\n const labels = features.map((f, i) => {\n if (label) {\n return label(f, i);\n }\n const budget = f.properties?.['budget'] as { type?: string; value?: number } | undefined;\n if (budget?.value !== undefined && budget?.type) {\n return `${budget.value} ${BUDGET_UNITS[budget.type] ?? budget.type}`;\n }\n return String(i + 1);\n });\n\n return {\n type: 'FeatureCollection',\n ...(result.bbox && { bbox: result.bbox }),\n features: buildReachableRangeFeatures(features, labels, theme),\n };\n};\n","import type { ColorPaletteOptions } from './layers/colorPalettes';\nimport {\n FILLED_THEME_FILL_OPACITY,\n FILLED_THEME_LINE_COLOR,\n FILLED_THEME_LINE_OPACITY,\n FILLED_THEME_LINE_WIDTH,\n OUTLINE_THEME_FILL_OPACITY,\n OUTLINE_THEME_LINE_COLOR,\n OUTLINE_THEME_LINE_OPACITY,\n OUTLINE_THEME_LINE_WIDTH,\n} from './layers/constants';\nimport { prepareReachableRangesForDisplay, type ReachableRangeLabelFn } from './prepareReachableRangesForDisplay';\nimport type { GeometriesModuleConfig, GeometryBeforeLayerConfig } from './types/geometriesModuleConfig';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/**\n * Config for themed polygon display with palette-based coloring.\n *\n * Includes data-driven expressions so each feature's `theme` property controls its\n * styling, line labels config, and positions layers below map labels.\n *\n * For reachable ranges, use {@link reachableRangeGeometryConfig} which extends this\n * with auto-generated labels.\n *\n * @param palette Color palette for polygon fills. Defaults to `'fadedRainbow'`.\n * @param beforeLayerConfig Layer positioning. Defaults to `'lowestLabel'`.\n *\n * @group Geometries\n */\nexport const themedGeometryConfig = (\n palette: ColorPaletteOptions = 'fadedRainbow',\n beforeLayerConfig: GeometryBeforeLayerConfig = 'lowestLabel',\n): GeometriesModuleConfig => ({\n beforeLayerConfig,\n lineConfig: {\n // outline theme: thick colored line; filled/inverted: thin grey line\n lineWidth: ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_WIDTH, FILLED_THEME_LINE_WIDTH],\n lineColor: [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n ['coalesce', ['get', 'color'], OUTLINE_THEME_LINE_COLOR],\n FILLED_THEME_LINE_COLOR,\n ],\n lineOpacity: [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n OUTLINE_THEME_LINE_OPACITY,\n FILLED_THEME_LINE_OPACITY,\n ],\n },\n colorConfig: {\n fillColor: palette,\n // outline theme: transparent fill; filled/inverted: semi-transparent fill\n fillOpacity: [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n OUTLINE_THEME_FILL_OPACITY,\n FILLED_THEME_FILL_OPACITY,\n ],\n },\n // Non-undefined lineLabelConfig is required for GeometriesModule to always create the line label layer.\n lineLabelConfig: {},\n});\n\n/**\n * Returns a {@link GeometriesModuleConfig} for reachable ranges.\n *\n * Builds on {@link themedGeometryConfig} and overrides `transformFeaturesForDisplay` so the\n * raw output of `calculateReachableRanges` can be passed directly to {@link GeometriesModule.show}\n * — labels are derived from each feature's `budget` property.\n *\n * @param palette Color palette. Defaults to `'fadedRainbow'`.\n * @param theme Visual theme. Defaults to `'filled'`.\n * @param beforeLayerConfig Layer positioning. Defaults to `'lowestLabel'`.\n * @param label Custom label generator; when omitted, labels are derived from `budget` property.\n *\n * @example\n * ```typescript\n * const module = await GeometriesModule.get(map, reachableRangeGeometryConfig());\n * const result = await calculateReachableRanges([...]);\n * module.show(result); // labels and theme applied automatically\n * ```\n *\n * @group Geometries\n */\nexport const reachableRangeGeometryConfig = (\n palette: ColorPaletteOptions = 'fadedRainbow',\n theme?: GeometryTheme,\n beforeLayerConfig?: GeometryBeforeLayerConfig,\n label?: ReachableRangeLabelFn,\n): GeometriesModuleConfig => {\n const base = themedGeometryConfig(palette, beforeLayerConfig);\n const transformFeaturesForDisplay: NonNullable<GeometriesModuleConfig['transformFeaturesForDisplay']> = (result) =>\n prepareReachableRangesForDisplay(result, theme, label);\n\n // For filled/outline themes the base layer config is sufficient — no geometry pre-processing needed.\n if (theme !== 'inverted') {\n return { ...base, transformFeaturesForDisplay };\n }\n\n // Inverted theme: features are pre-processed into donut/border pairs. Border polygons carry\n // theme: 'filled'; donut fills have no theme and fall through to the default case.\n return {\n ...base,\n lineConfig: {\n ...base.lineConfig,\n lineOpacity: ['case', ['==', ['get', 'theme'], 'filled'], FILLED_THEME_LINE_OPACITY, 0],\n },\n colorConfig: {\n ...base.colorConfig,\n fillOpacity: ['case', ['==', ['get', 'theme'], 'filled'], 0, FILLED_THEME_FILL_OPACITY],\n },\n lineLabelConfig: {\n textOpacity: ['case', ['==', ['get', 'theme'], 'filled'], 1, 0],\n textOffset: ['case', ['==', ['get', 'theme'], 'filled'], ['literal', [0, -1]], ['literal', [0, 1]]],\n },\n transformFeaturesForDisplay,\n };\n};\n","/**\n * Visual theme for polygon display with {@link GeometriesModule}.\n *\n * - `'filled'` — Colored fill with thin border (default)\n * - `'outline'` — Transparent fill with thick colored border\n * - `'inverted'` — Colors the area **outside** the polygon (donut geometry)\n *\n * Set via {@link GeometriesModuleConfig.theme} at config level, or per-feature in\n * `properties.theme`.\n *\n * @group Geometries\n */\nexport type GeometryTheme = 'filled' | 'outline' | 'inverted';\n\n/**\n * All valid {@link GeometryTheme} values.\n *\n * @group Geometries\n */\nexport const geometryThemes = ['filled', 'outline', 'inverted'] as const;\n","import {\n AbstractMapModule,\n CombinedEvents,\n HILLSHADE_SOURCE_ID,\n ModuleEvents,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { HillshadeModuleConfig } from '.';\n\n/**\n * IDs of sources and layers for hillshade module.\n */\ntype HillshadeSourcesWithLayers = {\n hillshade: StyleSourceWithLayers;\n};\n\n/**\n * Map module for displaying terrain shading (hillshade).\n *\n * The HillshadeModule adds realistic terrain depth perception to the map by rendering\n * shadow and highlight effects based on elevation data. This enhances the 3D appearance\n * of mountainous and hilly terrain.\n *\n * @remarks\n * **Features:**\n * - Realistic terrain shading\n * - Uses vector tile elevation data\n * - Lightweight performance impact\n * - Seamlessly integrates with other map layers\n * - Toggle visibility on/off\n *\n * **Common Use Cases:**\n * - Outdoor recreation maps (hiking, skiing)\n * - Geographic/topographic applications\n * - Environmental visualization\n * - Landscape planning tools\n *\n * @example\n * ```typescript\n * // Create the module with hillshade visible\n * const hillshade = await HillshadeModule.getInstance(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * hillshade.setVisible(false);\n *\n * // Listen to events\n * hillshade.events.on('click', (feature, lngLat) => {\n * console.log('Clicked hillshade at:', lngLat);\n * });\n * ```\n *\n * @see [Hillshade Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/hillshade)\n *\n * @group Hillshade\n */\nexport class HillshadeModule extends AbstractMapModule<HillshadeSourcesWithLayers, HillshadeModuleConfig> {\n /**\n * Retrieves a HillshadeModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization and visibility.\n *\n * @returns A promise that resolves to the initialized HillshadeModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `ensureAddedToStyle`: Auto-add hillshade to style if missing\n *\n * **Style Requirement:**\n * - Hillshade must be included in the map style or added via `ensureAddedToStyle`\n * - Some styles may not support hillshade (e.g., satellite)\n *\n * @throws Error if hillshade source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map);\n * ```\n *\n * @example\n * Auto-add to style if missing:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map, {\n * visible: true\n * });\n * ```\n *\n * @example\n * Start hidden:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map, {\n * visible: false\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: HillshadeModuleConfig): Promise<HillshadeModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, HILLSHADE_SOURCE_ID, 'hillshade');\n return new HillshadeModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: HillshadeModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const hillshadeSource = this.mapLibreMap.getSource(HILLSHADE_SOURCE_ID);\n if (!hillshadeSource) {\n throw notInTheStyle(`init ${HillshadeModule.name} with source ID ${HILLSHADE_SOURCE_ID}`);\n }\n return { hillshade: new StyleSourceWithLayers(this.mapLibreMap, hillshadeSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: HillshadeModuleConfig | undefined) {\n this._setVisible(config?.visible ?? false, false);\n return config;\n }\n\n /**\n * Sets the visibility of the hillshade layer.\n *\n * @param visible - `true` to show hillshade, `false` to hide it.\n *\n * @remarks\n * Changes are applied immediately if the map is ready.\n *\n * @example\n * ```typescript\n * hillshade.setVisible(true); // Show terrain shading\n * hillshade.setVisible(false); // Hide terrain shading\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.hillshade.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Checks if the hillshade layer is currently visible.\n *\n * @returns `true` if visible, `false` if hidden.\n *\n * @example\n * ```typescript\n * if (hillshade.isVisible()) {\n * console.log('Terrain shading is active');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.hillshade.isAnyLayerVisible();\n }\n\n /**\n * Gets the unified events interface for this module, covering both user interactions\n * and module lifecycle events.\n *\n * **User interaction events** (`click`, `contextmenu`, `hover`, `long-hover`):\n * ```typescript\n * hillshade.events.on('click', (feature, lngLat) => {\n * console.log('Clicked terrain at:', lngLat);\n * });\n * ```\n *\n * **Module lifecycle events** (`config-change`):\n * ```typescript\n * const unsub = hillshade.events.on('config-change', (config) => {\n * console.log('Hillshade config changed:', config);\n * });\n * unsub();\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, HillshadeModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.hillshade, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport type { MapOptions, StyleSpecification } from 'maplibre-gl';\nimport type { MapEventsConfig } from './mapEventsConfig';\n\n/**\n * Array of all available standard style identifiers.\n *\n * This constant provides the complete list of TomTom-hosted standard map styles\n * that can be used when initializing a map. It serves as the source of truth for\n * valid style IDs and is used to derive the {@link StandardStyleID} type.\n *\n * @remarks\n * Use this array when you need to:\n * - Iterate over all available styles (e.g., building a style picker UI)\n * - Validate if a style ID is a standard style\n * - Display available options to users\n *\n * The array is defined as `const` with `as const` assertion to ensure type safety\n * and prevent modifications at runtime.\n *\n * @example\n * ```typescript\n * import { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\n *\n * // Build a dropdown menu with all standard styles\n * const styleSelector = document.getElementById('style-selector');\n * standardStyleIDs.forEach((styleId) => {\n * const option = new Option(styleId);\n * styleSelector.add(option);\n * });\n *\n * // Check if a style ID is valid\n * const isValidStyle = (id: string): boolean => {\n * return standardStyleIDs.includes(id as any);\n * };\n * ```\n *\n * @see {@link StandardStyleID} - The type derived from this array\n *\n * @group Map Style\n */\nexport const standardStyleIDs = [\n 'standardLight',\n 'standardDark',\n // TODO: driving styles not supported in Orbis for now\n 'drivingLight',\n 'drivingDark',\n 'monoLight',\n 'monoDark',\n 'satellite',\n] as const;\n\n/**\n * Identifier for a TomTom-hosted standard map style.\n *\n * Standard styles are officially maintained by TomTom and provide consistent,\n * professionally designed map appearances.\n *\n * @remarks\n * Available styles:\n * - `standardLight`: Default light theme with full detail\n * - `standardDark`: Dark theme for low-light environments\n * - `drivingLight`: Optimized for in-car navigation (light)\n * - `drivingDark`: Optimized for in-car navigation (dark)\n * - `monoLight`: Minimalist monochrome light theme\n * - `monoDark`: Minimalist monochrome dark theme\n * - `satellite`: Satellite imagery basemap\n *\n * @example\n * ```typescript\n * const styleId: StandardStyleID = 'standardLight';\n * ```\n *\n * @group Map Style\n */\nexport type StandardStyleID = (typeof standardStyleIDs)[number];\n\n/**\n * Configuration for a TomTom standard map style.\n *\n * Provides options to customize which modules are included and which style version to use.\n *\n * @example\n * ```typescript\n * // Standard style with all default modules\n * const style: StandardStyle = {\n * id: 'standardLight'\n * };\n *\n * // Exclude traffic modules\n * const styleNoTraffic: StandardStyle = {\n * id: 'standardLight',\n * include: ['hillshade'] // Only include hillshade, exclude traffic\n * };\n *\n * // Use specific style version\n * const versionedStyle: StandardStyle = {\n * id: 'standardDark',\n * version: '1.0.0'\n * };\n * ```\n *\n * @group Map Style\n */\nexport type StandardStyle = {\n /**\n * Standard style identifier.\n *\n * Determines the visual appearance of the map.\n */\n id?: StandardStyleID;\n /**\n * Modules to include when loading the style.\n * * Use this to selectively enable only needed modules for better performance.\n *\n * If not specified, all available modules are included by default.\n * If an empty array is provided, no optional modules will be included.\n *\n * @default All available modules\n *\n * @remarks\n * Available modules:\n * - `trafficIncidents`: Real-time traffic incidents (accidents, closures)\n * - `trafficFlow`: Real-time traffic flow visualization\n * - `hillshade`: Terrain elevation shading\n *\n * @example\n * ```typescript\n * // Include only traffic modules\n * include: ['trafficIncidents', 'trafficFlow']\n *\n * // Include only hillshade\n * include: ['hillshade']\n *\n * // Include no optional modules\n * include: []\n * ```\n */\n include?: StyleModule[];\n /**\n * Style version to load.\n *\n * Allows pinning to a specific style version for consistency.\n * If not specified, uses the latest SDK-supported version.\n *\n * @default Latest SDK-supported version\n *\n * @example\n * ```typescript\n * version: '1.0.0'\n * ```\n */\n version?: string;\n};\n\n/**\n * Configuration for a custom map style.\n *\n * Allows using your own map style either via URL or direct JSON specification.\n *\n * @remarks\n * Use custom styles for:\n * - Branded map appearances\n * - Specialized use cases (indoor maps, thematic maps)\n * - Integration with custom tile servers\n *\n * @example\n * ```typescript\n * // Load from URL\n * const urlStyle: CustomStyle = {\n * url: 'https://example.com/my-custom-style.json'\n * };\n *\n * // Direct JSON specification\n * const jsonStyle: CustomStyle = {\n * json: {\n * version: 8,\n * sources: { ... },\n * layers: [ ... ]\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport type CustomStyle = {\n /**\n * URL to a MapLibre/Mapbox style JSON.\n *\n * The URL should not include the API key - it will be automatically added.\n * Mutually exclusive with the `json` property.\n *\n * @example\n * ```typescript\n * url: 'https://api.tomtom.com/style/1/style/my-custom-style'\n * ```\n */\n url?: string;\n /**\n * Direct style specification as JSON.\n *\n * Provide the complete MapLibre Style Specification object.\n * Mutually exclusive with the `url` property.\n *\n * @see [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/)\n *\n * @example\n * ```typescript\n * json: {\n * version: 8,\n * sources: {\n * 'my-source': { type: 'vector', url: '...' }\n * },\n * layers: [\n * { id: 'background', type: 'background', paint: { 'background-color': '#f0f0f0' } }\n * ]\n * }\n * ```\n */\n json?: StyleSpecification;\n};\n\n/**\n * Array of all available style modules.\n *\n * @group Map Style\n */\nexport const styleModules = ['trafficIncidents', 'trafficFlow', 'hillshade'] as const;\n\n/**\n * Optional map modules that can be included with a style.\n *\n * @remarks\n * - `trafficIncidents`: Shows real-time traffic incidents on the map\n * - `trafficFlow`: Shows real-time traffic flow with color-coded speeds\n * - `hillshade`: Adds terrain elevation shading for topographic context\n *\n * @group Map Style\n */\nexport type StyleModule = (typeof styleModules)[number];\n\n/**\n * Map style specification for initialization.\n *\n * Defines which map style to load and how it should be configured.\n * Supports standard TomTom styles or custom styles.\n *\n * @remarks\n * Three input formats:\n * 1. **Simple ID**: Just pass a standard style ID string\n * 2. **Standard style object**: Use a TomTom style with custom configuration\n * 3. **Custom style object**: Load your own style from URL or JSON\n *\n * @example\n * ```typescript\n * // 1. Simple standard style\n * style: 'standardLight'\n *\n * // 2. Standard style with configuration\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * }\n *\n * // 3. Custom style from URL\n * style: {\n * type: 'custom',\n * url: 'https://example.com/style.json'\n * }\n *\n * // 4. Custom style from JSON\n * style: {\n * type: 'custom',\n * json: { version: 8, sources: {...}, layers: [...] }\n * }\n * ```\n *\n * @group Map Style\n */\nexport type StyleInput = StandardStyleID | (StandardStyle & { type: 'standard' }) | (CustomStyle & { type: 'custom' });\n\n/**\n * MapLibre-specific options for advanced map configuration.\n *\n * Extends MapLibre GL JS MapOptions, excluding style and attribution control\n * which are handled by the TomTom SDK.\n *\n * @remarks\n * Includes options for:\n * - Initial viewport (center, zoom, bearing, pitch)\n * - Interaction controls (zoom, rotation, drag)\n * - Rendering options (antialiasing, terrain)\n * - Localization and accessibility\n *\n * @see [MapLibre MapOptions](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapOptions/)\n *\n * @group Map\n */\nexport type MapLibreOptions = Omit<MapOptions, 'style' | 'attributionControl'>;\n\n/**\n * Parameters for initializing a TomTom map instance.\n *\n * Combines global SDK configuration with map-specific settings like style, events, and MapLibre options.\n * All GlobalConfig properties (key, baseURL, etc.) are optional and will be merged from global configuration.\n * Only mapLibre.container is strictly required.\n *\n * @example\n * ```typescript\n * const mapParams: TomTomMapParams = {\n * key: 'your-api-key',\n * style: 'standardLight',\n * events: {\n * onClick: (event) => console.log('Map clicked', event)\n * },\n * mapLibre: {\n * container: 'map-container',\n * center: [4.9041, 52.3676],\n * zoom: 12\n * }\n * };\n * ```\n *\n * @group Map\n */\nexport type TomTomMapParams = Partial<GlobalConfig> & {\n /**\n * Map style to load.\n *\n * If not specified, defaults to 'standardLight'.\n *\n * @default 'standardLight'\n *\n * @example\n * ```typescript\n * // Use dark theme\n * style: 'standardDark'\n *\n * // Custom configuration\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow']\n * }\n * ```\n */\n style?: StyleInput;\n\n /**\n * Event handler configuration for map interactions.\n *\n * Define callbacks for various map events like clicks, hovers, and movements.\n *\n * @example\n * ```typescript\n * events: {\n * onClick: (event) => {\n * console.log('Clicked at', event.lngLat);\n * },\n * onMoveEnd: () => {\n * console.log('Map moved to', map.getCenter());\n * }\n * }\n * ```\n */\n events?: MapEventsConfig;\n\n /**\n * MapLibre-specific options for map configuration.\n *\n * Includes options for viewport settings, interaction controls, rendering options, and more.\n *\n * @example\n * ```typescript\n * mapLibre: {\n * container: 'map',\n * center: [4.9041, 52.3676],\n * zoom: 12,\n * pitch: 45,\n * bearing: -17.6,\n * antialias: true,\n * maxZoom: 18,\n * minZoom: 8\n * }\n * ```\n */\n mapLibre: MapLibreOptions;\n};\n\n/**\n * Complete TomTom map parameters after merging with global configuration.\n * This type represents the fully resolved configuration used internally by the SDK.\n *\n * @ignore\n */\nexport type InternalTomTomMapParams = GlobalConfig & TomTomMapParams;\n","type RGBColor = [number, number, number, number];\ntype HSLColor = [number, number, number, number];\n\nconst normalizeAngle = (angle: number): number => {\n const normalized = angle % 360;\n return normalized < 0 ? normalized + 360 : normalized;\n};\n\nconst hslToRgb = ([h, s, l, alpha]: HSLColor): RGBColor => {\n const hue = normalizeAngle(h);\n const sat = s / 100;\n const lig = l / 100;\n const f = (n: number) => {\n const k = (n + hue / 30) % 12;\n const a = sat * Math.min(lig, 1 - lig);\n return lig - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));\n };\n return [f(0), f(8), f(4), alpha];\n};\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(Math.max(min, n), max);\n\nconst validateNumbers = (array: number[]): boolean => !array.some(Number.isNaN);\n\nconst parseHex = (hex: string): number => Number.parseInt(hex.padEnd(2, hex), 16) / 255;\n\nconst parseAlpha = (a: number, asPercentage: string | undefined): number => clamp(asPercentage ? a / 100 : a, 0, 1);\n\nconst parseHexColor = (input: string): RGBColor | undefined => {\n const hexRegexp = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/;\n if (!hexRegexp.test(input)) {\n return undefined;\n }\n const step = input.length < 6 ? 1 : 2;\n const r = parseHex(input.slice(1, 1 + step));\n const g = parseHex(input.slice(1 + step, 1 + step * 2));\n const b = parseHex(input.slice(1 + step * 2, 1 + step * 3));\n const a = parseHex(input.slice(1 + step * 3) || 'ff');\n return [r, g, b, a];\n};\n\nconst RGB_REGEXP =\n /^rgba?\\(\\s*([\\de.+-]+)(%)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)(%)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)(%)?(?:\\s*([,/])\\s*([\\de.+-]+)(%)?)?\\s*\\)$/;\n\nconst parseRgbColor = (input: string): RGBColor | undefined => {\n const rgbMatch = RGB_REGEXP.exec(input);\n if (!rgbMatch) {\n return undefined;\n }\n const [, r, rp, f1, g, gp, f2, b, bp, f3, a, ap] = rgbMatch;\n const argFormat = [f1 || ' ', f2 || ' ', f3].join('');\n if (argFormat !== ' ' && argFormat !== ' /' && argFormat !== ',,' && argFormat !== ',,,') {\n return undefined;\n }\n const valFormat = [rp, gp, bp].join('');\n const maxValue = valFormat === '%%%' ? 100 : valFormat === '' ? 255 : 0;\n if (!maxValue) {\n return undefined;\n }\n const rgba: RGBColor = [\n clamp(+r / maxValue, 0, 1),\n clamp(+g / maxValue, 0, 1),\n clamp(+b / maxValue, 0, 1),\n a ? parseAlpha(+a, ap) : 1,\n ];\n return validateNumbers(rgba) ? rgba : undefined;\n};\n\nconst HSL_REGEXP =\n /^hsla?\\(\\s*([\\de.+-]+)(?:deg)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)%(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)%(?:\\s*([,/])\\s*([\\de.+-]+)(%)?)?\\s*\\)$/;\n\nconst parseHslColor = (input: string): RGBColor | undefined => {\n const hslMatch = HSL_REGEXP.exec(input);\n if (!hslMatch) {\n return undefined;\n }\n const [, h, f1, s, f2, l, f3, a, ap] = hslMatch;\n const argFormat = [f1 || ' ', f2 || ' ', f3].join('');\n if (argFormat !== ' ' && argFormat !== ' /' && argFormat !== ',,' && argFormat !== ',,,') {\n return undefined;\n }\n const hsla: HSLColor = [+h, clamp(+s, 0, 100), clamp(+l, 0, 100), a ? parseAlpha(+a, ap) : 1];\n return validateNumbers(hsla) ? hslToRgb(hsla) : undefined;\n};\n\nconst parseCssColor = (cssColor: string): RGBColor | undefined => {\n const input = cssColor.toLowerCase().trim();\n\n if (input === 'transparent') {\n return [0, 0, 0, 0];\n }\n\n const namedColor = namedColors[input];\n if (namedColor) {\n const [r, g, b] = namedColor;\n return [r / 255, g / 255, b / 255, 1];\n }\n\n if (input.startsWith('#')) {\n return parseHexColor(input);\n }\n\n if (input.startsWith('rgb')) {\n return parseRgbColor(input);\n }\n\n return parseHslColor(input);\n};\n\n/**\n * Darkens a CSS color by a given factor (0 = unchanged, 1 = black).\n * Accepts hex (#rgb, #rrggbb, #rrggbbaa), rgb(), hsl(), and CSS named colors.\n * Returns undefined if the color cannot be parsed.\n * @ignore\n */\nexport const darkenColor = (cssColor: string, darkenFactor: number): string | undefined => {\n const parsed = parseCssColor(cssColor);\n if (!parsed) {\n return undefined;\n }\n const brightnessScale = 1 - darkenFactor;\n const red = Math.round(parsed[0] * 255 * brightnessScale);\n const green = Math.round(parsed[1] * 255 * brightnessScale);\n const blue = Math.round(parsed[2] * 255 * brightnessScale);\n return `#${[red, green, blue].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`;\n};\n\nconst namedColors: Record<string, [number, number, number]> = {\n aliceblue: [240, 248, 255],\n antiquewhite: [250, 235, 215],\n aqua: [0, 255, 255],\n aquamarine: [127, 255, 212],\n azure: [240, 255, 255],\n beige: [245, 245, 220],\n bisque: [255, 228, 196],\n black: [0, 0, 0],\n blanchedalmond: [255, 235, 205],\n blue: [0, 0, 255],\n blueviolet: [138, 43, 226],\n brown: [165, 42, 42],\n burlywood: [222, 184, 135],\n cadetblue: [95, 158, 160],\n chartreuse: [127, 255, 0],\n chocolate: [210, 105, 30],\n coral: [255, 127, 80],\n cornflowerblue: [100, 149, 237],\n cornsilk: [255, 248, 220],\n crimson: [220, 20, 60],\n cyan: [0, 255, 255],\n darkblue: [0, 0, 139],\n darkcyan: [0, 139, 139],\n darkgoldenrod: [184, 134, 11],\n darkgray: [169, 169, 169],\n darkgreen: [0, 100, 0],\n darkgrey: [169, 169, 169],\n darkkhaki: [189, 183, 107],\n darkmagenta: [139, 0, 139],\n darkolivegreen: [85, 107, 47],\n darkorange: [255, 140, 0],\n darkorchid: [153, 50, 204],\n darkred: [139, 0, 0],\n darksalmon: [233, 150, 122],\n darkseagreen: [143, 188, 143],\n darkslateblue: [72, 61, 139],\n darkslategray: [47, 79, 79],\n darkslategrey: [47, 79, 79],\n darkturquoise: [0, 206, 209],\n darkviolet: [148, 0, 211],\n deeppink: [255, 20, 147],\n deepskyblue: [0, 191, 255],\n dimgray: [105, 105, 105],\n dimgrey: [105, 105, 105],\n dodgerblue: [30, 144, 255],\n firebrick: [178, 34, 34],\n floralwhite: [255, 250, 240],\n forestgreen: [34, 139, 34],\n fuchsia: [255, 0, 255],\n gainsboro: [220, 220, 220],\n ghostwhite: [248, 248, 255],\n gold: [255, 215, 0],\n goldenrod: [218, 165, 32],\n gray: [128, 128, 128],\n green: [0, 128, 0],\n greenyellow: [173, 255, 47],\n grey: [128, 128, 128],\n honeydew: [240, 255, 240],\n hotpink: [255, 105, 180],\n indianred: [205, 92, 92],\n indigo: [75, 0, 130],\n ivory: [255, 255, 240],\n khaki: [240, 230, 140],\n lavender: [230, 230, 250],\n lavenderblush: [255, 240, 245],\n lawngreen: [124, 252, 0],\n lemonchiffon: [255, 250, 205],\n lightblue: [173, 216, 230],\n lightcoral: [240, 128, 128],\n lightcyan: [224, 255, 255],\n lightgoldenrodyellow: [250, 250, 210],\n lightgray: [211, 211, 211],\n lightgreen: [144, 238, 144],\n lightgrey: [211, 211, 211],\n lightpink: [255, 182, 193],\n lightsalmon: [255, 160, 122],\n lightseagreen: [32, 178, 170],\n lightskyblue: [135, 206, 250],\n lightslategray: [119, 136, 153],\n lightslategrey: [119, 136, 153],\n lightsteelblue: [176, 196, 222],\n lightyellow: [255, 255, 224],\n lime: [0, 255, 0],\n limegreen: [50, 205, 50],\n linen: [250, 240, 230],\n magenta: [255, 0, 255],\n maroon: [128, 0, 0],\n mediumaquamarine: [102, 205, 170],\n mediumblue: [0, 0, 205],\n mediumorchid: [186, 85, 211],\n mediumpurple: [147, 112, 219],\n mediumseagreen: [60, 179, 113],\n mediumslateblue: [123, 104, 238],\n mediumspringgreen: [0, 250, 154],\n mediumturquoise: [72, 209, 204],\n mediumvioletred: [199, 21, 133],\n midnightblue: [25, 25, 112],\n mintcream: [245, 255, 250],\n mistyrose: [255, 228, 225],\n moccasin: [255, 228, 181],\n navajowhite: [255, 222, 173],\n navy: [0, 0, 128],\n oldlace: [253, 245, 230],\n olive: [128, 128, 0],\n olivedrab: [107, 142, 35],\n orange: [255, 165, 0],\n orangered: [255, 69, 0],\n orchid: [218, 112, 214],\n palegoldenrod: [238, 232, 170],\n palegreen: [152, 251, 152],\n paleturquoise: [175, 238, 238],\n palevioletred: [219, 112, 147],\n papayawhip: [255, 239, 213],\n peachpuff: [255, 218, 185],\n peru: [205, 133, 63],\n pink: [255, 192, 203],\n plum: [221, 160, 221],\n powderblue: [176, 224, 230],\n purple: [128, 0, 128],\n rebeccapurple: [102, 51, 153],\n red: [255, 0, 0],\n rosybrown: [188, 143, 143],\n royalblue: [65, 105, 225],\n saddlebrown: [139, 69, 19],\n salmon: [250, 128, 114],\n sandybrown: [244, 164, 96],\n seagreen: [46, 139, 87],\n seashell: [255, 245, 238],\n sienna: [160, 82, 45],\n silver: [192, 192, 192],\n skyblue: [135, 206, 235],\n slateblue: [106, 90, 205],\n slategray: [112, 128, 144],\n slategrey: [112, 128, 144],\n snow: [255, 250, 250],\n springgreen: [0, 255, 127],\n steelblue: [70, 130, 180],\n tan: [210, 180, 140],\n teal: [0, 128, 128],\n thistle: [216, 191, 216],\n tomato: [255, 99, 71],\n turquoise: [64, 224, 208],\n violet: [238, 130, 238],\n wheat: [245, 222, 179],\n white: [255, 255, 255],\n whitesmoke: [245, 245, 245],\n yellow: [255, 255, 0],\n yellowgreen: [154, 205, 50],\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\nimport type { RouteWaypointSize, RouteWidth } from '../types/routeModuleConfig';\n\n/**\n * Main route line foreground color.\n * @ignore\n */\nexport const ROUTE_LINE_FOREGROUND_COLOR = '#36A8F0';\n\n/**\n * Main route outline color.\n * @ignore\n */\nexport const ROUTE_LINE_OUTLINE_COLOR = '#105287';\n\n/**\n * Deselected route line foreground color.\n * @ignore\n */\nexport const DESELECTED_FOREGROUND_COLOR = '#ABAFB3';\n\n/**\n * Deselected route line outline color.\n * @ignore\n */\nexport const DESELECTED_OUTLINE_COLOR = '#3C4956';\n\n/**\n * @ignore\n */\nexport const DESELECTED_SECONDARY_COLOR = '#727C85';\n\n/**\n * Main route line width based on zoom level.\n * @ignore\n */\nexport const ROUTE_LINE_FOREGROUND_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 1,\n 3,\n 5,\n 4,\n 10,\n 7,\n 18,\n 10,\n];\n\n/**\n * Used for showing/hiding layer depending on layer being part of selected route or not.\n *\n * @remarks\n * Add this to layers that depend on whether they are part of the selected route or not.\n *\n * @example:\n * filter: SELECTED_ROUTE_FILTER\n\n * @group Routing\n */\nexport const SELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'selected'];\n\n/**\n * Used for showing/hiding layer depending on layer being part of deselected route or not.\n *\n * @remarks\n * Add this to layers that depend on whether they are part of the deselected route or not.\n *\n * @example:\n * filter: DESELECTED_ROUTE_FILTER\n\n * @group Routing\n */\nexport const DESELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'deselected'];\n\n/**\n * Main route line outline width (medium) based on zoom level.\n * @ignore\n */\nexport const ROUTE_LINE_OUTLINE_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 1,\n 5,\n 5,\n 6,\n 10,\n 10,\n 18,\n 14,\n];\n\nconst LINE_FOREGROUND_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 2, 5, 3, 10, 5, 18, 7],\n m: ROUTE_LINE_FOREGROUND_WIDTH,\n l: ['interpolate', ['linear'], ['zoom'], 1, 5, 5, 6, 10, 11, 18, 15],\n};\n\nconst LINE_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 4, 5, 5, 10, 8, 18, 11],\n m: ROUTE_LINE_OUTLINE_WIDTH,\n l: ['interpolate', ['linear'], ['zoom'], 1, 7, 5, 9, 10, 14, 18, 19],\n};\n\nconst WAYPOINT_ICON_SIZES: Record<RouteWaypointSize, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 8, 0.45, 22, 0.6],\n m: ['interpolate', ['linear'], ['zoom'], 8, 0.6, 22, 0.8],\n l: ['interpolate', ['linear'], ['zoom'], 8, 0.75, 22, 1],\n};\n\n/**\n * Returns the foreground line width expression for the given size preset.\n * @ignore\n */\nexport const getLineForegroundWidth = (size?: RouteWidth): ExpressionSpecification =>\n LINE_FOREGROUND_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the outline line width expression for the given size preset.\n * @ignore\n */\nexport const getLineOutlineWidth = (size?: RouteWidth): ExpressionSpecification => LINE_OUTLINE_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the waypoint icon size expression for the given size preset.\n * @ignore\n */\nexport const getWaypointIconSize = (size?: RouteWaypointSize): ExpressionSpecification =>\n WAYPOINT_ICON_SIZES[size ?? 'm'];\n\nconst TOLL_ROAD_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 8, 5, 9, 10, 12, 18, 15],\n m: ['interpolate', ['linear'], ['zoom'], 1, 9, 5, 11, 10, 15, 18, 20],\n l: ['interpolate', ['linear'], ['zoom'], 1, 12, 5, 14, 10, 19, 18, 24],\n};\n\n/**\n * Returns the toll road outline width expression for the given size preset.\n * @ignore\n */\nexport const getTollRoadOutlineWidth = (size?: RouteWidth): ExpressionSpecification =>\n TOLL_ROAD_OUTLINE_WIDTHS[size ?? 'm'];\n\nconst INSTRUCTION_LINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 16, 8, 22, 12],\n m: ['interpolate', ['linear'], ['zoom'], 16, 12, 22, 17],\n l: ['interpolate', ['linear'], ['zoom'], 16, 17, 22, 24],\n};\n\nconst INSTRUCTION_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 16, 10, 22, 14],\n m: ['interpolate', ['linear'], ['zoom'], 16, 14, 22, 20],\n l: ['interpolate', ['linear'], ['zoom'], 16, 20, 22, 28],\n};\n\n/**\n * Returns the instruction line width expression for the given size preset.\n * @ignore\n */\nexport const getInstructionLineWidth = (size?: RouteWidth): ExpressionSpecification =>\n INSTRUCTION_LINE_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the instruction outline width expression for the given size preset.\n * @ignore\n */\nexport const getInstructionOutlineWidth = (size?: RouteWidth): ExpressionSpecification =>\n INSTRUCTION_OUTLINE_WIDTHS[size ?? 'm'];\n\n/**\n * @ignore\n */\nexport const MAJOR_DELAY_COLOR = '#AD0000';\n/**\n * @ignore\n */\nexport const MODERATE_DELAY_COLOR = '#FB2D09';\n/**\n * @ignore\n */\nexport const MINOR_DELAY_COLOR = '#FFC105';\n/**\n * @ignore\n */\nexport const MINOR_DELAY_LABEL_COLOR = '#f58240';\n/**\n * @ignore\n */\nexport const UNKNOWN_DELAY_COLOR = '#000000';\n/**\n * @ignore\n */\nexport const UNKNOWN_DELAY_BG_COLOR = '#C7D2D8';\n/**\n * Dashed-line color for `magnitudeOfDelay === 'unknown'` (rendered as a red dashed overlay).\n * @ignore\n */\nexport const UNKNOWN_DELAY_DASH_COLOR = 'rgba(190, 39, 27, 1)';\n/**\n * Dashed-line color for `magnitudeOfDelay === 'indefinite'` (rendered as a gray dashed overlay).\n * @ignore\n */\nexport const INDEFINITE_DELAY_DASH_COLOR = 'rgba(137, 150, 168, 1)';\n","import type {\n DataDrivenPropertyValueSpecification,\n FormattedSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport { ChargingStopsConfig, ChargingStopTextConfig } from '../types/routeModuleConfig';\nimport { SELECTED_ROUTE_FILTER } from './shared';\n\nconst chargingStopTextField = (\n config: ChargingStopTextConfig | undefined,\n): DataDrivenPropertyValueSpecification<FormattedSpecification> => {\n if (config?.visible === false) {\n return '';\n }\n\n return (\n config?.title ?? [\n 'format',\n ['get', 'title'],\n '\\n',\n ['get', 'chargingPower'],\n ' • ',\n ['get', 'chargingDuration'],\n ]\n );\n};\n\n/**\n * @ignore\n * @see toDisplayChargingStops\n */\nexport const chargingStopSymbol = (\n config: ChargingStopsConfig | undefined,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n return {\n ...pinLayerBaseSpec,\n filter: SELECTED_ROUTE_FILTER,\n minzoom: 4,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'text-field': chargingStopTextField(config?.text),\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n },\n };\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getInstructionLineWidth, getInstructionOutlineWidth, SELECTED_ROUTE_FILTER } from './shared';\n\nconst commonProps = {\n filter: SELECTED_ROUTE_FILTER,\n minzoom: 16,\n};\n\nconst commonLineLayout: LayerSpecTemplate<LineLayerSpecification>['layout'] = {\n 'line-cap': 'round',\n};\n\n/**\n * @ignore\n */\nexport const instructionOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionOutlineWidth(routeWidth),\n 'line-color': 'grey',\n },\n});\n\n/**\n * @ignore\n */\nexport const instructionLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionLineWidth(routeWidth),\n 'line-color': 'white',\n },\n});\n\n/**\n * @ignore\n */\nexport const INSTRUCTION_ARROW_IMAGE_ID = 'instruction-arrow';\n\n/**\n * @ignore\n */\nexport const instructionArrow: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...commonProps,\n type: 'symbol',\n layout: {\n 'icon-allow-overlap': true,\n 'icon-image': INSTRUCTION_ARROW_IMAGE_ID, // Will be updated with instance suffix in config\n 'icon-rotation-alignment': 'map',\n 'icon-rotate': ['get', 'lastPointBearingDegrees'],\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 16, 1, 22, 2],\n },\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getLineForegroundWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeFerriesLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': '#6dc4ed',\n },\n});\n\n/**\n * @ignore\n */\nexport const routeFerriesSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 6,\n // zoom where the map POI naturally appears:\n maxzoom: 16.5,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-image': 'poi-ferry_terminal',\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 6, 0.8, 16.5, 1],\n // helps smooth the transition from along-route to map-poi, which also has a label in it:\n 'icon-ignore-placement': true,\n },\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n DESELECTED_FOREGROUND_COLOR,\n DESELECTED_OUTLINE_COLOR,\n DESELECTED_ROUTE_FILTER,\n getLineForegroundWidth,\n getLineOutlineWidth,\n ROUTE_LINE_FOREGROUND_COLOR,\n ROUTE_LINE_OUTLINE_COLOR,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const routeLineBaseTemplate: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n 'line-sort-key': ['get', 'index'],\n },\n};\n\n/**\n * @ignore\n */\nexport const routeDeselectedOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_OUTLINE_COLOR,\n 'line-width': getLineOutlineWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeDeselectedLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeOutline = (\n routeWidth?: RouteWidth,\n outlineColor?: string,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: SELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': outlineColor ?? ROUTE_LINE_OUTLINE_COLOR,\n 'line-width': getLineOutlineWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeMainLine = (\n routeWidth?: RouteWidth,\n routeColor?: string,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: SELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': routeColor ?? ROUTE_LINE_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeLineArrows: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line',\n 'icon-image': 'roads-arrow-white',\n // The current arrow icon seems to point backwards otherwise. Check with caution!\n 'icon-rotate': 180,\n },\n};\n\n/**\n * @ignore\n */\nexport const SELECTED_SUMMARY_POPUP_IMAGE_ID = 'selected-route-summary-popup';\n/**\n * @ignore\n */\nexport const DESELECTED_SUMMARY_POPUP_IMAGE_ID = 'deselected-route-summary-popup';\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getTollRoadOutlineWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTollRoadsOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-width': getTollRoadOutlineWidth(routeWidth),\n 'line-color': '#BEBFFA',\n },\n});\n\n/**\n * @ignore\n */\nexport const routeTollRoadsSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 4,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-image': 'poi-toll_plaza',\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 4, 0.8, 16.5, 1],\n },\n};\n","import type { TrafficIncidentCategory } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport { INDEFINITE_DELAY_DASH_COLOR, UNKNOWN_DELAY_DASH_COLOR } from '../../routing/layers/shared';\n\n/**\n * Maps a traffic incident category to the sprite-image ID used by the map style.\n * Returns `null` for categories that have no dedicated icon (e.g. `jam`, which\n * uses a dynamic `traffic-jam-<magnitude>-<size>` image instead).\n *\n * @ignore\n */\nexport const incidentIconID = (category: TrafficIncidentCategory): string | null => {\n switch (category) {\n case 'accident':\n return 'traffic-incidents-accident';\n case 'roadworks':\n return 'traffic-incidents-roadworks';\n case 'road-closed':\n return 'traffic-incidents-road_closed';\n case 'lane-closed':\n case 'narrow-lanes':\n return 'traffic-incidents-lane_closed';\n case 'danger':\n case 'animals-on-road':\n return 'traffic-incidents-danger';\n case 'broken-down-vehicle':\n return 'traffic-incidents-broken_down_vehicle';\n case 'wind':\n return 'traffic-incidents-wind';\n case 'fog':\n return 'traffic-incidents-fog';\n case 'rain':\n return 'traffic-incidents-rain';\n case 'frost':\n return 'traffic-incidents-frost';\n case 'flooding':\n return 'traffic-incidents-flooding';\n default:\n return null;\n }\n};\n\n/**\n * MapLibre filter selecting incident features whose `magnitudeOfDelay` should be\n * drawn with the dashed overlay (i.e. no concrete severity). Used by the routing\n * module's traffic-section overlay — the canonical-style\n * {@link TrafficIncidentOverlayModule} renders unknown/indefinite magnitudes with\n * `line-pattern` sprites instead of dashed overlays.\n *\n * @ignore\n */\nexport const severityDashedFilter: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['unknown', 'indefinite']],\n];\n\n/**\n * MapLibre paint expression for the dashed overlay's color. `'unknown'` is\n * rendered red (indicating reported-but-unconfirmed severity); `'indefinite'`\n * is rendered gray (indicating open-ended delay with no end time).\n *\n * @ignore\n */\nexport const severityLineDashColor: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n UNKNOWN_DELAY_DASH_COLOR,\n // other (indefinite):\n INDEFINITE_DELAY_DASH_COLOR,\n];\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { severityDashedFilter, severityLineDashColor } from '../../traffic/util/trafficIncidentStyle';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n getLineForegroundWidth,\n MAJOR_DELAY_COLOR,\n MINOR_DELAY_COLOR,\n MINOR_DELAY_LABEL_COLOR,\n MODERATE_DELAY_COLOR,\n SELECTED_ROUTE_FILTER,\n UNKNOWN_DELAY_BG_COLOR,\n UNKNOWN_DELAY_COLOR,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const magnitudeOfDelayTextColor: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'minor',\n MINOR_DELAY_LABEL_COLOR,\n 'moderate',\n MODERATE_DELAY_COLOR,\n 'major',\n MAJOR_DELAY_COLOR,\n 'indefinite',\n '#666666',\n // other\n UNKNOWN_DELAY_COLOR,\n];\n\n/**\n * @ignore\n */\nexport const routeIncidentsBGLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n layout: { 'line-cap': 'round' },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'minor',\n MINOR_DELAY_COLOR,\n 'moderate',\n MODERATE_DELAY_COLOR,\n 'major',\n MAJOR_DELAY_COLOR,\n // other\n UNKNOWN_DELAY_BG_COLOR,\n ],\n },\n});\n\n/**\n * @ignore\n */\nexport const routeIncidentsDashedLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n filter: severityDashedFilter,\n layout: { 'line-join': 'round' },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': severityLineDashColor,\n 'line-dasharray': [1.5, 1],\n },\n});\n\nconst routeIncidentsSymbolBase: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 6,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-ignore-placement': true,\n },\n};\n\n/**\n * @ignore\n */\nexport const routeIncidentsJamSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...routeIncidentsSymbolBase,\n filter: ['all', ['has', 'jamIconID'], routeIncidentsSymbolBase.filter as ExpressionSpecification],\n layout: {\n ...routeIncidentsSymbolBase.layout,\n 'icon-image': ['get', 'jamIconID'],\n 'icon-anchor': 'bottom-left',\n 'text-anchor': 'bottom-left',\n // Jam symbols have delay labels in them:\n 'text-field': ['get', 'title'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-offset': [3.9, -1.4],\n 'text-size': 13,\n },\n paint: {\n ...routeIncidentsSymbolBase.paint,\n 'text-color': magnitudeOfDelayTextColor,\n 'text-halo-color': '#FFFFFF',\n 'text-halo-width': 1,\n },\n};\n\n/**\n * @ignore\n */\nexport const routeIncidentsCauseSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...routeIncidentsSymbolBase,\n filter: ['all', ['has', 'causeIconID'], routeIncidentsSymbolBase.filter as ExpressionSpecification],\n layout: {\n ...routeIncidentsSymbolBase.layout,\n 'icon-image': ['get', 'causeIconID'],\n 'icon-anchor': 'bottom-right',\n // Cause symbols have no label in them.\n },\n paint: { ...routeIncidentsSymbolBase.paint },\n};\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getLineForegroundWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTunnelsLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': '#000000',\n 'line-opacity': 0.3,\n },\n});\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n getLineForegroundWidth,\n ROUTE_LINE_FOREGROUND_COLOR,\n ROUTE_LINE_OUTLINE_COLOR,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedBackgroundLine = (\n routeWidth?: RouteWidth,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-color': ROUTE_LINE_OUTLINE_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedDottedLine = (\n routeWidth?: RouteWidth,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-color': ROUTE_LINE_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-dasharray': [0, 1.5],\n },\n});\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT, MAP_MEDIUM_FONT } from '../../shared/layers/commonLayerProps';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './routeMainLineLayers';\nimport { magnitudeOfDelayTextColor } from './routeTrafficSectionLayers';\nimport { DESELECTED_SECONDARY_COLOR, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const TRAFFIC_CLEAR_IMAGE_ID = 'traffic-clear';\n/**\n * @ignore\n */\nexport const TRAFFIC_MAJOR_IMAGE_ID = 'traffic-major';\n/**\n * @ignore\n */\nexport const TRAFFIC_MODERATE_IMAGE_ID = 'traffic-moderate';\n/**\n * @ignore\n */\nexport const TRAFFIC_MINOR_IMAGE_ID = 'traffic-minor';\n\nconst hasFormattedTraffic: ExpressionSpecification = ['has', 'formattedTraffic'];\n\n/**\n * Builds the summary bubble symbol layer specification with instance-specific image IDs.\n * @param instanceIndex - Optional instance index for supporting multiple RoutingModule instances\n * @ignore\n */\nexport const buildSummaryBubbleSymbolPoint = (instanceIndex?: number): LayerSpecTemplate<SymbolLayerSpecification> => {\n const selectedImageID =\n instanceIndex !== undefined\n ? suffixNumber(SELECTED_SUMMARY_POPUP_IMAGE_ID, instanceIndex)\n : SELECTED_SUMMARY_POPUP_IMAGE_ID;\n const deselectedImageID =\n instanceIndex !== undefined\n ? suffixNumber(DESELECTED_SUMMARY_POPUP_IMAGE_ID, instanceIndex)\n : DESELECTED_SUMMARY_POPUP_IMAGE_ID;\n const trafficClearID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_CLEAR_IMAGE_ID, instanceIndex) : TRAFFIC_CLEAR_IMAGE_ID;\n const trafficMajorID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_MAJOR_IMAGE_ID, instanceIndex) : TRAFFIC_MAJOR_IMAGE_ID;\n const trafficModerateID =\n instanceIndex !== undefined\n ? suffixNumber(TRAFFIC_MODERATE_IMAGE_ID, instanceIndex)\n : TRAFFIC_MODERATE_IMAGE_ID;\n const trafficMinorID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_MINOR_IMAGE_ID, instanceIndex) : TRAFFIC_MINOR_IMAGE_ID;\n\n return {\n type: 'symbol',\n layout: {\n 'icon-image': ['case', SELECTED_ROUTE_FILTER, selectedImageID, deselectedImageID],\n 'symbol-placement': 'point',\n 'icon-rotation-alignment': 'viewport',\n 'text-rotation-alignment': 'viewport',\n 'symbol-sort-key': ['case', SELECTED_ROUTE_FILTER, 0, 1],\n 'icon-text-fit': 'both',\n 'icon-text-fit-padding': [10, 5, 5, 10],\n 'text-font': [MAP_MEDIUM_FONT],\n 'text-size': 13,\n 'icon-padding': 0,\n 'text-justify': 'left',\n 'text-line-height': 1.5,\n 'text-field': [\n 'format',\n ['get', 'formattedDuration'],\n {\n 'text-font': ['literal', [MAP_BOLD_FONT]],\n 'text-color': ['case', SELECTED_ROUTE_FILTER, 'black', DESELECTED_SECONDARY_COLOR],\n },\n ['concat', '\\t\\t', ['get', 'formattedDistance']],\n { 'text-color': DESELECTED_SECONDARY_COLOR },\n ['case', hasFormattedTraffic, '\\n', ''],\n {},\n [\n 'image',\n [\n 'case',\n hasFormattedTraffic,\n [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'major',\n trafficMajorID,\n 'moderate',\n trafficModerateID,\n 'minor',\n trafficMinorID,\n trafficClearID,\n ],\n '',\n ],\n ],\n {},\n ['case', hasFormattedTraffic, ['concat', ' ', ['get', 'formattedTraffic']], ''],\n {\n 'text-font': ['literal', [MAP_BOLD_FONT]],\n 'text-color': magnitudeOfDelayTextColor,\n },\n ],\n },\n paint: {\n 'icon-translate': [0, -35],\n 'text-translate': [0, -35],\n },\n };\n};\n\n/**\n * Default summary bubble symbol layer (without instance suffix)\n * @ignore\n */\nexport const summaryBubbleSymbolPoint: LayerSpecTemplate<SymbolLayerSpecification> = buildSummaryBubbleSymbolPoint();\n","import type { PlaceDisplayProps } from '../../places';\n\n/**\n * @group Routing\n */\nexport const START_INDEX = 'start';\n/**\n * @group Routing\n */\nexport const MIDDLE_INDEX = 'middle';\n/**\n * @group Routing\n */\nexport const FINISH_INDEX = 'finish';\n/**\n * @group Routing\n */\nexport type WaypointIndexType = typeof START_INDEX | typeof MIDDLE_INDEX | typeof FINISH_INDEX;\n\n/**\n * Display properties for a waypoint marker on the map.\n *\n * Extends location display properties with waypoint-specific information\n * including position in the route and stop numbering.\n *\n * @remarks\n * Waypoints are displayed differently based on their position:\n * - **Start**: Origin marker (often \"A\" or green pin)\n * - **Middle**: Numbered stop markers (1, 2, 3, etc.)\n * - **Finish**: Destination marker (often \"B\" or red pin)\n *\n * @example\n * ```typescript\n * // Start waypoint\n * const start: WaypointDisplayProps = {\n * id: 'waypoint-0',\n * iconID: 'waypoint-start',\n * index: 0,\n * indexType: 'start',\n * title: 'Amsterdam Central Station'\n * };\n *\n * // Intermediate stop\n * const stop: WaypointDisplayProps = {\n * id: 'waypoint-1',\n * iconID: 'waypoint-stop',\n * index: 1,\n * indexType: 'middle',\n * stopDisplayIndex: 1,\n * title: 'Schiphol Airport'\n * };\n *\n * // Destination\n * const finish: WaypointDisplayProps = {\n * id: 'waypoint-2',\n * iconID: 'waypoint-finish',\n * index: 2,\n * indexType: 'finish',\n * title: 'Rotterdam Central Station'\n * };\n * ```\n *\n * @group Routing\n */\nexport type WaypointDisplayProps = PlaceDisplayProps & {\n /**\n * The index of the waypoint in relation to the other waypoints.\n *\n * @remarks\n * Zero-based index of this waypoint in the complete waypoints array,\n * including start, all stops, and finish.\n *\n * @example\n * ```typescript\n * index: 0 // First waypoint (start)\n * index: 1 // Second waypoint (first stop or finish)\n * index: 2 // Third waypoint\n * ```\n */\n index: number;\n\n /**\n * The type associated to the index, describing how the waypoint sits in the list of waypoints.\n *\n * @remarks\n * Determines the waypoint's role and visual representation:\n * - `start`: Origin point\n * - `middle`: Intermediate stop\n * - `finish`: Final destination\n *\n * This affects icon selection and labeling behavior.\n */\n indexType: WaypointIndexType;\n\n /**\n * The stop index to be displayed.\n *\n * @remarks\n * Stops are the non-soft waypoints added between origin and destination,\n * numbered starting from 1. Only present for middle waypoints.\n *\n * **Display Behavior:**\n * - Start waypoint: undefined\n * - First stop: 1\n * - Second stop: 2\n * - Finish waypoint: undefined\n *\n * Used for displaying stop numbers (e.g., \"Stop 1\", \"Stop 2\") in the UI.\n *\n * @example\n * ```typescript\n * stopDisplayIndex: 1 // First intermediate stop\n * stopDisplayIndex: 2 // Second intermediate stop\n * stopDisplayIndex: undefined // Start or finish waypoint\n * ```\n */\n stopDisplayIndex?: number;\n};\n\n/**\n * @ignore\n */\nexport const INDEX_TYPE = 'indexType';\n\n/**\n * @ignore\n */\nexport const STOP_DISPLAY_INDEX = 'stopDisplayIndex';\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport {\n ICON_ID,\n pinIconBaseLayout,\n pinIconBasePaint,\n pinTextBaseLayout,\n pinTextBasePaint,\n} from '../../shared/layers/symbolLayers';\nimport { INDEX_TYPE, MIDDLE_INDEX, STOP_DISPLAY_INDEX } from '../types/waypointDisplayProps';\n\n/**\n * Waypoint start image ID.\n * @group Routing\n */\nexport const WAYPOINT_START_IMAGE_ID = 'waypointStart';\n/**\n * Waypoint stop image ID.\n *\n * @remarks\n * Used for intermediate waypoints in a route.\n *\n * @group Routing.\n */\nexport const WAYPOINT_STOP_IMAGE_ID = 'waypointStop';\n/**\n * Soft waypoint image ID.\n *\n * @remarks\n * This is currently unsupported in Orbis maps.\n *\n * @group Routing\n */\nexport const WAYPOINT_SOFT_IMAGE_ID = 'waypointSoft';\n/**\n * Waypoint finish image ID.\n *\n * @group Routing\n */\nexport const WAYPOINT_FINISH_IMAGE_ID = 'waypointFinish';\n\nconst isSoftWaypoint: ExpressionSpecification = [\n 'all',\n ['==', ['get', INDEX_TYPE], MIDDLE_INDEX],\n ['!', ['has', STOP_DISPLAY_INDEX]],\n];\n\nconst pinIndexLabelPaint: SymbolLayerSpecification['paint'] = {\n 'text-color': '#ffffff',\n};\n\nconst pinIndexLabelLayout: SymbolLayerSpecification['layout'] = {\n // optional centered text to indicate stop numbers (1, 2 ...):\n 'text-field': ['get', STOP_DISPLAY_INDEX],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': ['interpolate', ['linear'], ['zoom'], 13, 14, 18, 16],\n 'text-offset': [0, -1.6],\n // pin vs circle:\n 'icon-anchor': [\n 'case',\n isSoftWaypoint,\n 'center',\n // else\n 'bottom',\n ],\n 'text-allow-overlap': true,\n};\n\n// TODO: reusable display of pins with indexes, not just waypoints\n/**\n * @ignore\n */\nexport const waypointSymbols: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n paint: {\n ...pinIconBasePaint,\n ...pinIndexLabelPaint,\n },\n layout: {\n ...pinIconBaseLayout,\n ...pinIndexLabelLayout,\n 'symbol-sort-key': [\n 'case',\n ['==', ['get', ICON_ID], WAYPOINT_SOFT_IMAGE_ID],\n 0,\n ['abs', ['-', ['get', 'index'], 1000]],\n ],\n },\n};\n\n/**\n * @ignore\n */\nexport const waypointLabels: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n paint: {\n ...pinTextBasePaint,\n 'text-color': 'black',\n 'text-halo-width': 1.5,\n 'text-halo-color': '#ffffff',\n },\n layout: {\n ...pinTextBaseLayout,\n 'text-anchor': 'top',\n 'text-offset': [0, 0.4],\n },\n};\n","import { mapStyleLayerIDs } from '../../shared';\nimport { darkenColor } from '../../utils/colorUtils';\nimport type { RouteLayersConfig, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport { chargingStopSymbol } from './chargingStopLayers';\nimport { instructionArrow, instructionLine, instructionOutline } from './guidanceLayers';\nimport { routeFerriesLine, routeFerriesSymbol } from './routeFerrySectionLayers';\nimport {\n routeDeselectedLine,\n routeDeselectedOutline,\n routeLineArrows,\n routeMainLine,\n routeOutline,\n} from './routeMainLineLayers';\nimport { routeTollRoadsOutline, routeTollRoadsSymbol } from './routeTollRoadLayers';\nimport {\n routeIncidentsBGLine,\n routeIncidentsCauseSymbol,\n routeIncidentsDashedLine,\n routeIncidentsJamSymbol,\n} from './routeTrafficSectionLayers';\nimport { routeTunnelsLine } from './routeTunnelSectionLayers';\nimport { routeVehicleRestrictedBackgroundLine, routeVehicleRestrictedDottedLine } from './routeVehicleRestrictedLayers';\nimport { getWaypointIconSize } from './shared';\nimport { buildSummaryBubbleSymbolPoint, summaryBubbleSymbolPoint } from './summaryBubbleLayers';\nimport { waypointLabels, waypointSymbols } from './waypointLayers';\n\n/**\n * Helper function to add layer ID prefix to beforeID references, but only for internal routing layer IDs\n * @ignore\n */\nconst prefixBeforeID = (beforeID: string | undefined, layerIDPrefix: string | undefined): string | undefined => {\n if (!beforeID || !layerIDPrefix) {\n return beforeID;\n }\n // Don't prefix map style layer IDs (they start with capital letters or contain specific prefixes)\n if (beforeID.startsWith('route') || beforeID.startsWith('waypoint')) {\n return `${layerIDPrefix}-${beforeID}`;\n }\n return beforeID;\n};\n\n/**\n * Helper function to process additional layers and prefix their beforeID fields\n * @ignore\n */\nconst prefixBeforeIDs = (\n additional: Record<string, any> | undefined,\n layerIDPrefix: string | undefined,\n): Record<string, any> | undefined => {\n if (!additional || !layerIDPrefix) {\n return additional;\n }\n\n return Object.fromEntries(\n Object.entries(additional).map(([key, layer]) => [\n key,\n layer?.beforeID ? { ...layer, beforeID: prefixBeforeID(layer.beforeID, layerIDPrefix) } : layer,\n ]),\n );\n};\n\n/**\n * Merges a base layer spec with a user override, deep-merging paint and layout instead of replacing them.\n * @ignore\n */\nconst mergeLayer = <T extends Record<string, any>>(base: T, override: Record<string, any> | undefined): T => {\n if (!override) return base;\n return {\n ...base,\n ...override,\n ...(override.paint && { paint: { ...base.paint, ...override.paint } }),\n ...(override.layout && { layout: { ...base.layout, ...override.layout } }),\n };\n};\n\n/**\n * Helper function to add instance suffix to image IDs for supporting multiple RoutingModule instances\n * @ignore\n */\nconst suffixImageID = (imageID: string | undefined, instanceIndex: number | undefined): string | undefined => {\n if (!imageID || instanceIndex === undefined) {\n return imageID;\n }\n return `${imageID}-${instanceIndex}`;\n};\n\n/**\n * Generates the routing layers configuration for route visualization on the map.\n * @param config - Optional routing module configuration to customize layer properties.\n * @param layerIDPrefix - Optional prefix to add to layer IDs for supporting multiple instances.\n * @param instanceIndex - Optional instance index for image ID suffixes.\n * @ignore\n */\nexport const buildRoutingLayers = (\n config: RoutingModuleConfig = {},\n layerIDPrefix?: string,\n instanceIndex?: number,\n): Required<RouteLayersConfig> => {\n const configLayers = config.layers;\n const configSectionLayers = configLayers?.sections;\n const routeColor = config.theme?.mainColor;\n const outlineColor = routeColor ? darkenColor(routeColor, 0.4) : undefined;\n const routeWidth = config.theme?.routeWidth;\n const waypointSize = config.theme?.waypointSize;\n const waypointIconSize = getWaypointIconSize(waypointSize);\n\n return {\n mainLines: {\n routeLineArrows: mergeLayer(\n { ...routeLineArrows, beforeID: mapStyleLayerIDs.lowestLabel },\n configLayers?.mainLines?.routeLineArrows,\n ),\n routeLine: mergeLayer(\n {\n ...routeMainLine(routeWidth, routeColor),\n beforeID: prefixBeforeID('routeIncidentBackgroundLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeLine,\n ),\n routeOutline: mergeLayer(\n { ...routeOutline(routeWidth, outlineColor), beforeID: prefixBeforeID('routeLine', layerIDPrefix) },\n configLayers?.mainLines?.routeOutline,\n ),\n routeDeselectedLine: mergeLayer(\n { ...routeDeselectedLine(routeWidth), beforeID: prefixBeforeID('routeOutline', layerIDPrefix) },\n configLayers?.mainLines?.routeDeselectedLine,\n ),\n routeDeselectedOutline: mergeLayer(\n {\n ...routeDeselectedOutline(routeWidth),\n beforeID: prefixBeforeID('routeDeselectedLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeDeselectedOutline,\n ),\n ...prefixBeforeIDs(configLayers?.mainLines?.additional, layerIDPrefix),\n },\n waypoints: {\n routeWaypointSymbol: mergeLayer(\n {\n ...waypointSymbols,\n layout: { ...waypointSymbols.layout, 'icon-size': waypointIconSize },\n beforeID: prefixBeforeID('routeSummaryBubbleSymbol', layerIDPrefix),\n },\n configLayers?.waypoints?.routeWaypointSymbol,\n ),\n routeWaypointLabel: mergeLayer(\n { ...waypointLabels, beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix) },\n configLayers?.waypoints?.routeWaypointLabel,\n ),\n ...prefixBeforeIDs(configLayers?.waypoints?.additional, layerIDPrefix),\n },\n chargingStops: {\n routeChargingStopSymbol: mergeLayer(\n {\n ...chargingStopSymbol(config.chargingStops),\n beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix),\n },\n configLayers?.chargingStops?.routeChargingStopSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.chargingStops?.additional, layerIDPrefix),\n },\n sections: {\n incident: {\n routeIncidentJamSymbol: mergeLayer(\n {\n ...routeIncidentsJamSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentJamSymbol,\n ),\n routeIncidentCauseSymbol: mergeLayer(\n {\n ...routeIncidentsCauseSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentCauseSymbol,\n ),\n routeIncidentBackgroundLine: mergeLayer(\n {\n ...routeIncidentsBGLine(routeWidth),\n beforeID: prefixBeforeID('routeIncidentDashedLine', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentBackgroundLine,\n ),\n routeIncidentDashedLine: mergeLayer(\n {\n ...routeIncidentsDashedLine(routeWidth),\n beforeID: prefixBeforeID('routeTunnelLine', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentDashedLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.incident?.additional, layerIDPrefix),\n },\n ferry: {\n routeFerryLine: mergeLayer(\n { ...routeFerriesLine(routeWidth), beforeID: prefixBeforeID('routeLineArrows', layerIDPrefix) },\n configSectionLayers?.ferry?.routeFerryLine,\n ),\n routeFerrySymbol: mergeLayer(\n {\n ...routeFerriesSymbol,\n beforeID: prefixBeforeID('routeIncidentJamSymbol', layerIDPrefix),\n },\n configSectionLayers?.ferry?.routeFerrySymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.ferry?.additional, layerIDPrefix),\n },\n tollRoad: {\n routeTollRoadOutline: mergeLayer(\n {\n ...routeTollRoadsOutline(routeWidth),\n beforeID: prefixBeforeID('routeDeselectedOutline', layerIDPrefix),\n },\n configSectionLayers?.tollRoad?.routeTollRoadOutline,\n ),\n routeTollRoadSymbol: mergeLayer(\n {\n ...routeTollRoadsSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.tollRoad?.routeTollRoadSymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tollRoad?.additional, layerIDPrefix),\n },\n tunnel: {\n routeTunnelLine: mergeLayer(\n { ...routeTunnelsLine(routeWidth), beforeID: prefixBeforeID('routeLineArrows', layerIDPrefix) },\n configSectionLayers?.tunnel?.routeTunnelLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tunnel?.additional, layerIDPrefix),\n },\n vehicleRestricted: {\n routeVehicleRestrictedBackgroundLine: mergeLayer(\n {\n ...routeVehicleRestrictedBackgroundLine(routeWidth),\n beforeID: prefixBeforeID('routeVehicleRestrictedForegroundLine', layerIDPrefix),\n },\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedBackgroundLine,\n ),\n routeVehicleRestrictedForegroundLine: mergeLayer(\n { ...routeVehicleRestrictedDottedLine(routeWidth), beforeID: mapStyleLayerIDs.lowestLabel },\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedForegroundLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.vehicleRestricted?.additional, layerIDPrefix),\n },\n },\n instructionLines: {\n routeInstructionLine: mergeLayer(\n { ...instructionLine(routeWidth), beforeID: mapStyleLayerIDs.lowestLabel },\n configLayers?.instructionLines?.routeInstructionLine,\n ),\n routeInstructionOutline: mergeLayer(\n {\n ...instructionOutline(routeWidth),\n beforeID: prefixBeforeID('routeInstructionLine', layerIDPrefix),\n },\n configLayers?.instructionLines?.routeInstructionOutline,\n ),\n ...prefixBeforeIDs(configLayers?.instructionLines?.additional, layerIDPrefix),\n },\n instructionArrows: {\n routeInstructionArrowSymbol: mergeLayer(\n {\n ...instructionArrow,\n beforeID: prefixBeforeID('routeInstructionLine', layerIDPrefix),\n ...(instanceIndex !== undefined && {\n layout: {\n ...instructionArrow.layout,\n 'icon-image': suffixImageID(\n instructionArrow.layout?.['icon-image'] as string,\n instanceIndex,\n ),\n },\n }),\n },\n configLayers?.instructionArrows?.routeInstructionArrowSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.instructionArrows?.additional, layerIDPrefix),\n },\n summaryBubbles: {\n routeSummaryBubbleSymbol: mergeLayer(\n instanceIndex === undefined ? summaryBubbleSymbolPoint : buildSummaryBubbleSymbolPoint(instanceIndex),\n configLayers?.summaryBubbles?.routeSummaryBubbleSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.summaryBubbles?.additional, layerIDPrefix),\n },\n };\n};\n\n/**\n * Default routing layers configuration. Calls routingLayers with no parameters.\n *\n * @remarks\n * This configuration defines the complete visual styling for all route-related map layers,\n * including main route lines, waypoints, special road sections (ferries, tunnels, toll roads, etc.),\n * turn-by-turn guidance instructions, and route summary information.\n *\n * **Usage:**\n * - Automatically applied when initializing {@link RoutingModule} without custom layer configuration\n * - Can be used as a reference or starting point for creating custom layer configurations\n * - Individual properties can be selectively overridden while keeping defaults for others\n *\n * @see {@link buildRoutingLayers} for details.\n *\n * @see {@link RouteLayersConfig} for the configuration type definition\n * @see {@link RoutingModule.get} for initialization options\n * @see {@link RoutingModule.applyConfig} for runtime configuration updates\n *\n * @group Routing\n */\nexport const defaultRoutingLayers: Required<RouteLayersConfig> = buildRoutingLayers();\n","import type { StyleImageMetadata } from 'maplibre-gl';\nimport { SVGIconStyleOptions } from '../../shared';\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { parseSvg, pinSvg } from '../../shared/resources';\nimport circleSvgRaw from './circle.svg?raw';\nimport finishSvgRaw from './finish.svg?raw';\nimport instructionArrowSvgRaw from './instruction-line-arrow.svg?raw';\nimport startSvgRaw from './start.svg?raw';\nimport summaryMapBubbleSvgRaw from './summary-map-bubble.svg?raw';\nimport trafficSvgRaw from './traffic.svg?raw';\n\nlet instructionArrowIconImg: HTMLImageElement;\n\n// defensive check for SSR\nif (isDOMImageSupported()) {\n instructionArrowIconImg = svgToImg(parseSvg(instructionArrowSvgRaw));\n}\n\n/**\n * @ignore\n */\nexport { instructionArrowIconImg };\n\n/**\n * @ignore\n */\nexport const summaryMapBubbleImg = (color: string): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg: SVGElement = parseSvg(summaryMapBubbleSvgRaw);\n svg.querySelector('#bubble')?.setAttribute('fill', color);\n svg.querySelector('#pin')?.setAttribute('fill', color);\n return svgToImg(svg);\n};\n\n/**\n * Options to effectively stretch the summary bubble image to fit its text.\n * * They are tightly coupled with the SVG original dimensions.\n * @ignore\n */\nexport const summaryBubbleImageOptions: Partial<StyleImageMetadata> = {\n pixelRatio: 2,\n stretchX: [\n [20, 45],\n [100, 130],\n ],\n stretchY: [[20, 35]],\n content: [10, 10, 130, 45],\n};\n\n/**\n * @ignore\n * @param color\n */\nexport const trafficImg = (color: string): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg: SVGElement = parseSvg(trafficSvgRaw);\n const main = svg.querySelector('#main') as Element;\n main.setAttribute('transform', 'scale(2)');\n main.setAttribute('fill', color);\n return svgToImg(svg);\n};\n\n/**\n * @ignore\n */\nexport const waypointIcon = (\n foregroundSvg: SVGElement | undefined,\n svgOptions: SVGIconStyleOptions | undefined,\n): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg = pinSvg(svgOptions);\n if (foregroundSvg) {\n svg.appendChild(foregroundSvg);\n }\n return svgToImg(svg);\n};\n\n/**\n * @ignore\n */\nexport const waypointStartIcon = (svgOptions: SVGIconStyleOptions | undefined): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return waypointIcon(parseSvg(startSvgRaw), svgOptions);\n};\n\n/**\n * @ignore\n */\nexport const waypointFinishIcon = (svgOptions: SVGIconStyleOptions | undefined): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return waypointIcon(parseSvg(finishSvgRaw), svgOptions);\n};\n\n/**\n * @ignore\n */\nexport const softWaypointIcon = (): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return svgToImg(parseSvg(circleSvgRaw));\n};\n","import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport type { ToBeAddedLayerSpecTemplate, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { buildRoutingLayers } from '../layers/routingLayers';\nimport type { RouteLayersConfig, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport type { RoutingLayersSpecs } from '../types/routingSourcesAndLayers';\n\n/**\n * @ignore\n */\nconst mapLayerSpecs = (\n layerSpecs: Record<string, Partial<ToBeAddedLayerSpecTemplate>> = {},\n layerIDPrefix?: string,\n): ToBeAddedLayerSpecWithoutSource[] =>\n // The key of the entry is the layer ID:\n Object.entries(layerSpecs).map(\n ([id, spec]) =>\n ({\n ...spec,\n id: layerIDPrefix ? `${layerIDPrefix}-${id}` : id,\n }) as ToBeAddedLayerSpecWithoutSource,\n );\n\n/**\n * @ignore\n */\nexport const createLayersSpecs = (\n layerConfigs: RouteLayersConfig = {},\n layerIDPrefix?: string,\n): RoutingLayersSpecs => ({\n mainLines: mapLayerSpecs(layerConfigs.mainLines, layerIDPrefix),\n waypoints: mapLayerSpecs(layerConfigs.waypoints, layerIDPrefix),\n chargingStops: mapLayerSpecs(layerConfigs?.chargingStops, layerIDPrefix),\n ferries: mapLayerSpecs(layerConfigs.sections?.ferry, layerIDPrefix),\n incidents: mapLayerSpecs(layerConfigs.sections?.incident, layerIDPrefix),\n tollRoads: mapLayerSpecs(layerConfigs.sections?.tollRoad, layerIDPrefix),\n tunnels: mapLayerSpecs(layerConfigs.sections?.tunnel, layerIDPrefix),\n vehicleRestricted: mapLayerSpecs(layerConfigs.sections?.vehicleRestricted, layerIDPrefix),\n instructionLines: mapLayerSpecs(layerConfigs.instructionLines, layerIDPrefix),\n instructionArrows: mapLayerSpecs(layerConfigs.instructionArrows, layerIDPrefix),\n summaryBubbles: mapLayerSpecs(layerConfigs.summaryBubbles, layerIDPrefix),\n});\n\n/**\n * @ignore\n */\nexport const routeModuleConfigWithDefaults = (\n config: RoutingModuleConfig | undefined,\n layerIDPrefix: string,\n instanceIndex: number,\n): RoutingModuleConfig => {\n const globalDisplayUnits = TomTomConfig.instance.get().displayUnits;\n const displayUnits = config?.displayUnits;\n return {\n // First apply the provided configuration not to lose any properties:\n ...config,\n ...(displayUnits ? {} : { displayUnits: globalDisplayUnits }),\n layers: buildRoutingLayers(config, layerIDPrefix, instanceIndex),\n };\n};\n","import {\n ChargingStop,\n ChargingStopProps,\n formatDuration,\n generateId,\n type Place,\n type Routes,\n} from '@tomtom-org/maps-sdk/core';\nimport { FeatureCollection, Point } from 'geojson';\nimport { PlaceDisplayProps } from '../../places';\nimport type { DisplayRouteProps, RouteStateProps } from '../types/displayRoutes';\nimport { RoutingModuleConfig } from '../types/routeModuleConfig';\n\nconst getIconID = (chargingStop: ChargingStop, config: RoutingModuleConfig | undefined): string => {\n const iconConfig = config?.chargingStops?.icon;\n if (iconConfig?.mapping) {\n const mapping = iconConfig.mapping;\n switch (mapping.basedOn) {\n case 'chargingSpeed':\n if (chargingStop.properties.chargingConnectionInfo?.chargingSpeed) {\n return mapping.value[chargingStop.properties.chargingConnectionInfo?.chargingSpeed];\n }\n break;\n case 'custom':\n return mapping.fn(chargingStop);\n }\n }\n\n // default: (genesis-like) categorySet ID for \"EV Charging Station\" based on search\n return '7309';\n};\n\nconst formatTitle = (chargingStop: ChargingStop): string => {\n const properties = chargingStop.properties;\n return properties.chargingParkName ?? (properties.chargingParkOperatorName as string);\n};\n\ntype DisplayChargingStopProps = PlaceDisplayProps &\n ChargingStopProps &\n RouteStateProps & {\n chargingDuration: string;\n chargingPower: string;\n };\n\ntype DisplayChargingStops = FeatureCollection<Point, DisplayChargingStopProps>;\n\n/**\n * Generates display-ready charging stations for the given planning context ones.\n * @param routes The routes return for ldEV.\n * @param config The charging stops display configuration.\n * @see chargingStopLayers\n * @ignore\n */\nexport const toDisplayChargingStops = (\n routes: Routes<DisplayRouteProps>,\n config: RoutingModuleConfig | undefined,\n): DisplayChargingStops => {\n const displayChargingStops: Place<DisplayChargingStopProps>[] = [];\n\n if (config?.chargingStops?.visible !== false) {\n for (const route of routes.features) {\n for (const leg of route.properties.sections.leg) {\n const chargingStop = leg.summary.chargingInformationAtEndOfLeg;\n\n if (chargingStop) {\n const properties = chargingStop.properties;\n displayChargingStops.push({\n ...chargingStop,\n properties: {\n ...chargingStop.properties,\n id: chargingStop.properties.chargingParkId ?? generateId(),\n iconID: getIconID(chargingStop, config),\n title: formatTitle(chargingStop),\n chargingPower: `${properties.chargingConnectionInfo?.chargingPowerInkW} kW`,\n chargingDuration: formatDuration(\n properties.chargingTimeInSeconds,\n config?.displayUnits?.time,\n ) as string,\n routeState: route.properties.routeState,\n },\n });\n }\n }\n }\n }\n return { type: 'FeatureCollection', features: displayChargingStops };\n};\n","import { formatDuration, type TrafficSectionProps } from '@tomtom-org/maps-sdk/core';\nimport { incidentIconID } from '../../traffic/util/trafficIncidentStyle';\nimport type { DisplayTrafficSectionProps } from '../types/routeSections';\n\nconst hasJam = (sectionProps: TrafficSectionProps): boolean => sectionProps.categories.includes('jam');\n\nconst buildTitle = (sectionProps: TrafficSectionProps): string | undefined => {\n if (hasJam(sectionProps)) {\n return formatDuration(sectionProps.delayInSeconds);\n }\n return undefined;\n};\n\nconst toTrafficJamIconSuffix = (title: string | undefined): 'collapsed' | 'small' | 'medium' | 'large' => {\n if (!title?.length) {\n return 'collapsed';\n }\n if (title.length < 6) {\n // 1 digit minutes\n return 'small';\n }\n if (title.length < 8) {\n // 2 digit minutes\n return 'medium';\n }\n // hours + minutes\n return 'large';\n};\n\nconst toJamIconID = (sectionProps: TrafficSectionProps, title: string | undefined): string | null => {\n if (!hasJam(sectionProps)) {\n return null;\n }\n const magnitude = sectionProps.magnitudeOfDelay ?? 'unknown';\n return `traffic-jam-${magnitude}-${toTrafficJamIconSuffix(title)}`;\n};\n\nconst toCauseIconID = (sectionProps: TrafficSectionProps): string | null => {\n const firstNonJamCategory = sectionProps.categories.find((category) => category !== 'jam');\n return firstNonJamCategory ? incidentIconID(firstNonJamCategory) : null;\n};\n\n/**\n * @ignore\n */\nexport const toDisplayTrafficSectionProps = (\n sectionProps: TrafficSectionProps,\n): Omit<DisplayTrafficSectionProps, 'routeState' | 'routeIndex'> => {\n const title = buildTitle(sectionProps);\n const jamIconID = toJamIconID(sectionProps, title);\n const causeIconID = toCauseIconID(sectionProps);\n return {\n ...sectionProps,\n ...(jamIconID && { jamIconID }),\n ...(causeIconID && { causeIconID }),\n ...(title && { title }),\n };\n};\n","import type { CommonPlaceProps, Waypoint, WaypointLike, Waypoints } from '@tomtom-org/maps-sdk/core';\nimport { generateId, getPosition } from '@tomtom-org/maps-sdk/core';\nimport type { Point, Position } from 'geojson';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport {\n WAYPOINT_FINISH_IMAGE_ID,\n WAYPOINT_SOFT_IMAGE_ID,\n WAYPOINT_START_IMAGE_ID,\n WAYPOINT_STOP_IMAGE_ID,\n} from '../layers/waypointLayers';\nimport type { PlanningWaypoint } from '../types/planningWaypoint';\nimport type { WaypointsConfig } from '../types/routeModuleConfig';\nimport type { WaypointDisplayProps, WaypointIndexType } from '../types/waypointDisplayProps';\nimport { FINISH_INDEX, MIDDLE_INDEX, START_INDEX } from '../types/waypointDisplayProps';\n\nconst indexTypeFor = (index: number, arrayLength: number): WaypointIndexType =>\n index === 0 ? START_INDEX : index < arrayLength - 1 ? MIDDLE_INDEX : FINISH_INDEX;\n\n/**\n * Builds the title of the given waypoint.\n * @param waypoint The waypoint for which to build the title.\n */\nexport const buildWaypointTitle = (waypoint: Waypoint): string | undefined => {\n const placeProperties = waypoint?.properties as CommonPlaceProps;\n return placeProperties?.poi?.name ?? placeProperties?.address?.freeformAddress ?? undefined;\n};\n\nexport const getImageIDForWaypoint = (\n waypoint: Waypoint,\n indexType: WaypointIndexType,\n instanceIndex?: number,\n): string => {\n if (waypoint.properties.radiusMeters) {\n return instanceIndex !== undefined\n ? suffixNumber(WAYPOINT_SOFT_IMAGE_ID, instanceIndex)\n : WAYPOINT_SOFT_IMAGE_ID;\n }\n let baseImageID: string;\n switch (indexType) {\n case 'start':\n baseImageID = WAYPOINT_START_IMAGE_ID;\n break;\n case 'finish':\n baseImageID = WAYPOINT_FINISH_IMAGE_ID;\n break;\n default:\n baseImageID = WAYPOINT_STOP_IMAGE_ID;\n break;\n }\n return instanceIndex !== undefined ? suffixNumber(baseImageID, instanceIndex) : baseImageID;\n};\n\nconst toWaypointFromPosition = (position: Position): Waypoint => ({\n type: 'Feature',\n geometry: {\n type: 'Point',\n coordinates: position,\n },\n properties: {},\n});\n\nconst toWaypointFromPoint = (point: Point): Waypoint => ({\n type: 'Feature',\n geometry: point,\n properties: {},\n ...(point.bbox && { bbox: point.bbox }),\n});\n\nconst asWaypoint = (waypointInput: WaypointLike): Waypoint => {\n if (Array.isArray(waypointInput)) {\n return toWaypointFromPosition(waypointInput);\n }\n if (waypointInput.type === 'Point') {\n return toWaypointFromPoint(waypointInput);\n }\n return waypointInput as Waypoint;\n};\n\n/**\n * Determines whether the given waypoint is a regular start/stop/destination with guidance attached,\n * as opposed to a circle (soft) waypoint.\n * @param waypoint The waypoint to verify.\n */\nexport const isHardWaypoint = (waypoint: Waypoint): boolean => !waypoint.properties.radiusMeters;\n\n/**\n * Generates display-ready waypoints for the given planning context ones.\n * @param waypoints The planning context waypoints.\n * @param options\n * @param instanceIndex Optional instance index for supporting multiple RoutingModule instances\n */\nexport const toDisplayWaypoints = (\n waypoints: PlanningWaypoint[],\n options: WaypointsConfig | undefined,\n instanceIndex?: number,\n): Waypoints<WaypointDisplayProps> => {\n // Since waypoints are of mixed types (hard and soft), we need to calculate the hard-only indexes\n // in case we have stops with numbered icons:\n let hardWaypointIndex = -1;\n return {\n type: 'FeatureCollection',\n features: waypoints\n .map((waypointInput, index) => {\n if (!waypointInput) {\n // (we consider placeholder waypoints to be \"hard\", since they typically occupy a position in a planner panel)\n hardWaypointIndex++;\n // (will be filtered out below - we don't pre-filter it to keep the original input index)\n return null as unknown as Waypoint<WaypointDisplayProps>;\n }\n const waypoint: Waypoint = asWaypoint(waypointInput);\n const indexType = indexTypeFor(index, waypoints.length);\n const hardWaypoint = isHardWaypoint(waypoint);\n if (hardWaypoint) {\n hardWaypointIndex++;\n }\n const title = buildWaypointTitle(waypoint);\n const id = (waypoint.id as string) ?? generateId();\n return {\n ...waypoint,\n ...(options?.entryPoints === 'main-when-available' && {\n geometry: {\n type: 'Point',\n // We replace the waypoint coordinates with their main entry point ones:\n coordinates: getPosition(waypoint, { useEntryPoint: 'main-when-available' }),\n } as Point,\n }),\n id,\n properties: {\n ...waypoint.properties,\n id,\n index,\n indexType,\n ...(title && { title }),\n iconID: getImageIDForWaypoint(waypoint, indexType, instanceIndex),\n ...(hardWaypoint && indexType === MIDDLE_INDEX && { stopDisplayIndex: hardWaypointIndex }),\n },\n };\n })\n .filter((feature) => feature),\n };\n};\n","import { generateId, Routes } from '@tomtom-org/maps-sdk/core';\nimport { bearing } from '@turf/turf';\nimport type { DisplayRouteProps } from '../types/displayRoutes';\nimport type {\n DisplayInstruction,\n DisplayInstructionArrow,\n DisplayInstructionArrows,\n DisplayInstructions,\n} from '../types/guidance';\n\n/**\n * @ignore\n */\nexport const toDisplayInstructions = (routes: Routes<DisplayRouteProps>): DisplayInstructions => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap(\n (route, routeIndex) =>\n route.properties.guidance?.instructions\n ?.filter((instruction) => instruction.routePath?.length)\n .map(\n (instruction): DisplayInstruction => ({\n type: 'Feature',\n geometry: {\n type: 'LineString',\n coordinates: instruction.routePath.map((pathPoint) => pathPoint.point),\n },\n properties: {\n ...instruction,\n id: generateId(),\n routeIndex,\n routeState: route.properties.routeState,\n },\n }),\n ) || [],\n ),\n});\n\n/**\n * @ignore\n */\nexport const toDisplayInstructionArrows = (routes: Routes<DisplayRouteProps>): DisplayInstructionArrows => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap(\n (route, routeIndex) =>\n route.properties.guidance?.instructions\n ?.filter((instruction) => instruction.routePath?.length && instruction.routePath.length > 1)\n .map((instruction): DisplayInstructionArrow => {\n const instructionLastSegment = [\n instruction.routePath[instruction.routePath.length - 2]?.point,\n instruction.routePath[instruction.routePath.length - 1]?.point,\n ];\n\n return {\n type: 'Feature',\n geometry: { type: 'Point', coordinates: instructionLastSegment[1] },\n properties: {\n ...instruction,\n id: generateId(),\n routeIndex,\n routeState: route.properties.routeState,\n lastPointBearingDegrees: bearing(instructionLastSegment[0], instructionLastSegment[1]),\n },\n };\n }) || [],\n ),\n});\n","import { generateId, Route, Routes, SectionProps, SectionType } from '@tomtom-org/maps-sdk/core';\nimport type { DisplayRouteProps } from '../types/displayRoutes';\nimport type { DisplaySectionProps, RouteSection, RouteSections } from '../types/routeSections';\n\nconst buildRouteSectionsFromRoute = <\n S extends SectionProps = SectionProps,\n D extends DisplaySectionProps = DisplaySectionProps,\n>(\n route: Route<DisplayRouteProps>,\n sectionType: SectionType,\n displaySectionPropsBuilder?: (\n sectionProps: S,\n routeProps?: DisplayRouteProps,\n ) => Omit<D, 'routeState' | 'routeIndex'>,\n): RouteSection<D>[] =>\n (route.properties.sections[sectionType] as S[])?.map((sectionProps) => {\n const id = sectionProps.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'LineString',\n coordinates: route.geometry.coordinates.slice(\n sectionProps.startPointIndex,\n sectionProps.endPointIndex + 1,\n ),\n },\n properties: {\n ...(displaySectionPropsBuilder\n ? displaySectionPropsBuilder(sectionProps, route.properties)\n : sectionProps),\n routeState: route.properties.routeState,\n routeIndex: route.properties.index,\n id, // we need id in properties due to promoteId feature\n } as D,\n };\n }) || [];\n\n/**\n * Builds display-ready LineString features to render the sections of a given type, from the given route.\n * @param routes The routes from which to extract the section props.\n * @param sectionType The type of the sections to extract.\n * @param displaySectionPropsBuilder An optional function which will convert each section props into an extended display-ready version.\n * @ignore\n */\nexport const toDisplayRouteSections = <\n S extends SectionProps = SectionProps,\n D extends DisplaySectionProps = DisplaySectionProps,\n>(\n routes: Routes<DisplayRouteProps>,\n sectionType: SectionType,\n displaySectionPropsBuilder?: (\n sectionProps: S,\n routeProps?: DisplayRouteProps,\n ) => Omit<D, 'routeState' | 'routeIndex'>,\n): RouteSections<D> => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap((route) =>\n buildRouteSectionsFromRoute<S, D>(route, sectionType, displaySectionPropsBuilder),\n ),\n});\n","import type { Routes } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection, Geometry } from 'geojson';\nimport type { GeoJSONSourceWithLayers } from '../../shared';\nimport type { DisplayRouteProps, DisplayRouteRelatedProps } from '../types/displayRoutes';\n\n/**\n * Rebuilds route-related display features such as sections or instructions, with the updated route selection based on the given routes.\n * @ignore\n */\nexport const rebuildFeaturesWithRouteSelection = <T extends FeatureCollection<Geometry, DisplayRouteRelatedProps>>(\n routes: Routes<DisplayRouteProps>,\n featureCollection: T,\n): T => ({\n ...featureCollection,\n features: featureCollection.features.map((features) => ({\n ...features,\n properties: {\n ...features.properties,\n routeState: routes.features[features.properties.routeIndex || 0].properties.routeState,\n },\n })),\n});\n\n/**\n * @ignore\n */\nexport const showFeaturesWithRouteSelection = <T extends FeatureCollection<Geometry, DisplayRouteRelatedProps>>(\n routesWithSelection: Routes<DisplayRouteProps>,\n sourceWithLayers: GeoJSONSourceWithLayers<T>,\n): void =>\n sourceWithLayers.show(rebuildFeaturesWithRouteSelection(routesWithSelection, sourceWithLayers.shownFeatures));\n","import {\n DelayMagnitude,\n DisplayUnits,\n formatDistance,\n formatDuration,\n generateId,\n Route,\n Routes,\n TrafficSectionProps,\n} from '@tomtom-org/maps-sdk/core';\nimport type { DisplayRouteProps, DisplayRouteSummaries } from '../types/displayRoutes';\n\n/**\n * Builds map display-ready routes, applying default style props.\n * @ignore\n */\nexport const toDisplayRoutes = (routes: Route | Routes, selectedIndex = 0): Routes<DisplayRouteProps> => {\n const routesCollection: Routes = 'features' in routes ? routes : { type: 'FeatureCollection', features: [routes] };\n return {\n ...routesCollection,\n features: routesCollection.features.map((route, index) => {\n const id = route.id ?? generateId();\n return {\n ...route,\n id,\n properties: {\n ...route.properties,\n id, // we need id in properties due to promoteId feature\n routeState: index === selectedIndex ? 'selected' : 'deselected',\n },\n };\n }),\n };\n};\n\nconst hasMagnitude = (sections: TrafficSectionProps[], magnitude: DelayMagnitude): boolean =>\n sections.some((section) => section.magnitudeOfDelay === magnitude);\n\nconst summaryDelayMagnitude = (route: Route): DelayMagnitude | undefined => {\n const trafficSections = route.properties.sections.traffic;\n if (!trafficSections?.length) {\n return undefined;\n }\n if (hasMagnitude(trafficSections, 'major')) {\n return 'major';\n }\n if (hasMagnitude(trafficSections, 'moderate')) {\n return 'moderate';\n }\n if (hasMagnitude(trafficSections, 'minor')) {\n return 'minor';\n }\n return undefined;\n};\n/**\n * Builds map display-ready route summaries based on display routes.\n * @ignore\n */\nexport const toDisplayRouteSummaries = (\n routes: Routes<DisplayRouteProps>,\n displayUnits?: DisplayUnits,\n): DisplayRouteSummaries => ({\n type: 'FeatureCollection',\n features: routes.features.map((route) => {\n const summary = route.properties.summary;\n const routeCoordinates = route.geometry.coordinates;\n const formattedTraffic = formatDuration(summary.trafficDelayInSeconds, displayUnits?.time);\n const magnitudeOfDelay = summaryDelayMagnitude(route);\n const id = route.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'Point',\n coordinates: routeCoordinates[Math.round(routeCoordinates.length / 2)],\n },\n properties: {\n id, // we need id in properties due to promoteId feature\n routeIndex: route.properties.index,\n routeState: route.properties.routeState,\n formattedDistance: formatDistance(summary.lengthInMeters, displayUnits?.distance),\n ...(magnitudeOfDelay && { magnitudeOfDelay }),\n ...(formattedTraffic && { formattedTraffic }),\n formattedDuration: formatDuration(summary.travelTimeInSeconds, displayUnits?.time),\n },\n };\n }),\n});\n","import type { Route, Routes, Waypoint, Waypoints } from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport type { StyleImageMetadata } from 'maplibre-gl';\nimport {\n AbstractMapModule,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n SVGIconStyleOptions,\n UserEvents,\n} from '../shared';\nimport { suffixNumber } from '../shared/layers/utils';\nimport { addLayers, addOrUpdateImage, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { INSTRUCTION_ARROW_IMAGE_ID } from './layers/guidanceLayers';\nimport { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './layers/routeMainLineLayers';\nimport { MAJOR_DELAY_COLOR, MINOR_DELAY_LABEL_COLOR, MODERATE_DELAY_COLOR, UNKNOWN_DELAY_COLOR } from './layers/shared';\nimport {\n TRAFFIC_CLEAR_IMAGE_ID,\n TRAFFIC_MAJOR_IMAGE_ID,\n TRAFFIC_MINOR_IMAGE_ID,\n TRAFFIC_MODERATE_IMAGE_ID,\n} from './layers/summaryBubbleLayers';\nimport {\n WAYPOINT_FINISH_IMAGE_ID,\n WAYPOINT_SOFT_IMAGE_ID,\n WAYPOINT_START_IMAGE_ID,\n WAYPOINT_STOP_IMAGE_ID,\n} from './layers/waypointLayers';\nimport {\n instructionArrowIconImg,\n softWaypointIcon,\n summaryBubbleImageOptions,\n summaryMapBubbleImg,\n trafficImg,\n waypointFinishIcon,\n waypointIcon,\n waypointStartIcon,\n} from './resources';\nimport type { DisplayRouteProps, DisplayRouteSummary } from './types/displayRoutes';\nimport type { DisplayInstruction } from './types/guidance';\nimport type { PlanningWaypoint } from './types/planningWaypoint';\nimport type { RoutingModuleConfig } from './types/routeModuleConfig';\nimport type { DisplayTrafficSectionProps, RouteSection } from './types/routeSections';\nimport type { RoutingLayersSpecs, RoutingSourcesWithLayers } from './types/routingSourcesAndLayers';\nimport type { ShowRoutesOptions } from './types/showOptions';\nimport type { WaypointDisplayProps } from './types/waypointDisplayProps';\nimport { createLayersSpecs, routeModuleConfigWithDefaults } from './util/config';\nimport { toDisplayChargingStops } from './util/displayChargingStops';\nimport { toDisplayTrafficSectionProps } from './util/displayTrafficSectionProps';\nimport { toDisplayWaypoints } from './util/displayWaypoints';\nimport { toDisplayInstructionArrows, toDisplayInstructions } from './util/guidance';\nimport { toDisplayRouteSections } from './util/routeSections';\nimport { showFeaturesWithRouteSelection } from './util/routeSelection';\nimport { toDisplayRouteSummaries, toDisplayRoutes } from './util/routes';\n\n/**\n * Map module for displaying and managing route visualizations.\n *\n * The RoutingModule provides comprehensive functionality for displaying routes on the map,\n * including route lines, alternative routes, turn-by-turn guidance, and interactive waypoints.\n * It integrates seamlessly with the TomTom Routing API.\n *\n * @remarks\n * **Features:**\n * - Display route lines with customizable styling\n * - Show alternative routes with different styling\n * - Interactive waypoint markers (drag, add, remove)\n * - Turn-by-turn guidance instructions\n * - Route section highlighting\n * - Distance and duration information\n * - Support for multiple routes simultaneously\n *\n * **Common Use Cases:**\n * - Turn-by-turn navigation displays\n * - Route planning and comparison\n * - Multi-stop route optimization\n * - Interactive route editing\n * - Fleet management route visualization\n *\n * @example\n * ```typescript\n * // Create the module\n * const routingModule = await RoutingModule.getInstance(map, {\n * displayUnits: {\n * distance: { type: 'metric' }\n * }\n * });\n *\n * // Calculate and display a route\n * const result = await calculateRoute({\n * key: 'your-api-key',\n * locations: [\n * [4.9041, 52.3676], // Amsterdam\n * [4.4777, 51.9244] // Rotterdam\n * ],\n * routeOptions: {\n * travelMode: 'car',\n * routeType: 'fastest'\n * }\n * });\n *\n * await routingModule.showRoutes(result);\n * ```\n *\n * @see [Routing Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/routing)\n *\n * @group Routing\n */\nexport class RoutingModule extends AbstractMapModule<RoutingSourcesWithLayers, RoutingModuleConfig> {\n private layersSpecs!: RoutingLayersSpecs;\n private layerIDPrefix!: string;\n private readonly shownFeaturesHandlers: ((\n features: { routes: Route | Routes } | { waypoints: PlanningWaypoint[] | Waypoints },\n ) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n *\n * @remarks\n * **Configuration Options:**\n * - `displayUnits`: Distance units (metric/imperial)\n * - `waypointsSource`: Waypoint entry point options\n * - `layers`: Complete layer styling configuration\n *\n * **Default Styling:**\n * If no custom layers are provided, uses {@link defaultRoutingLayers}.\n *\n * @example\n * Default initialization:\n * ```typescript\n * const routingModule = await RoutingModule.get(map);\n * ```\n *\n * @example\n * With custom configuration:\n * ```typescript\n * const routingModule = await RoutingModule.get(map, {\n * displayUnits: 'imperial',\n * waypointsSource: {\n * entryPoints: 'main-when-available'\n * }\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: RoutingModuleConfig): Promise<RoutingModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new RoutingModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: RoutingModuleConfig) {\n super('geojson', map, config);\n }\n\n private createSourcesWithLayers(layersSpecs: RoutingLayersSpecs): RoutingSourcesWithLayers {\n const sourcePrefix = suffixNumber('routes', this.instanceIndex);\n return {\n mainLines: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-mainLines`,\n layersSpecs.mainLines,\n false,\n ),\n waypoints: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-waypoints`,\n layersSpecs.waypoints,\n false,\n ),\n incidents: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-incidents`,\n layersSpecs.incidents,\n false,\n ),\n ferries: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-ferries`,\n layersSpecs.ferries,\n false,\n ),\n chargingStops: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-chargingStops`,\n layersSpecs.chargingStops,\n false,\n ),\n tollRoads: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-tollRoads`,\n layersSpecs.tollRoads,\n false,\n ),\n tunnels: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-tunnels`,\n layersSpecs.tunnels,\n false,\n ),\n vehicleRestricted: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-vehicleRestricted`,\n layersSpecs.vehicleRestricted,\n false,\n ),\n instructionLines: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-instructionLines`,\n layersSpecs.instructionLines,\n false,\n ),\n instructionArrows: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-instructionArrows`,\n layersSpecs.instructionArrows,\n false,\n ),\n summaryBubbles: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-summaryBubbles`,\n layersSpecs.summaryBubbles,\n false,\n ),\n };\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: RoutingModuleConfig, restore?: boolean): RoutingSourcesWithLayers {\n // `instanceIndex` is auto-assigned by AbstractMapModule; only the layerIDPrefix\n // needs to be (re)computed on first init.\n if (!restore) {\n this.layerIDPrefix = suffixNumber('routes', this.instanceIndex);\n }\n\n this.layersSpecs = createLayersSpecs(\n routeModuleConfigWithDefaults(config, this.layerIDPrefix, this.instanceIndex).layers,\n this.layerIDPrefix,\n );\n const routingSourcesWithLayers: RoutingSourcesWithLayers = this.createSourcesWithLayers(this.layersSpecs);\n addLayers(\n Object.values(routingSourcesWithLayers).flatMap((source) => source._layerSpecs),\n this.mapLibreMap,\n );\n\n const svgIconOptions: SVGIconStyleOptions = {\n // first comes the main theme fill color, if any:\n fillColor: config?.theme?.mainColor,\n // then come any icon style configs for the waypoint icons, if any:\n ...config?.waypoints?.icon?.style,\n };\n const options: Partial<StyleImageMetadata> = { pixelRatio: 2 };\n\n // Generate instance-specific image IDs to support multiple RoutingModule instances\n const waypointStartImageId = suffixNumber(WAYPOINT_START_IMAGE_ID, this.instanceIndex);\n const waypointStopImageId = suffixNumber(WAYPOINT_STOP_IMAGE_ID, this.instanceIndex);\n const waypointSoftImageId = suffixNumber(WAYPOINT_SOFT_IMAGE_ID, this.instanceIndex);\n const waypointFinishImageId = suffixNumber(WAYPOINT_FINISH_IMAGE_ID, this.instanceIndex);\n const instructionArrowImageId = suffixNumber(INSTRUCTION_ARROW_IMAGE_ID, this.instanceIndex);\n const selectedSummaryPopupImageId = suffixNumber(SELECTED_SUMMARY_POPUP_IMAGE_ID, this.instanceIndex);\n const deselectedSummaryPopupImageId = suffixNumber(DESELECTED_SUMMARY_POPUP_IMAGE_ID, this.instanceIndex);\n const trafficClearImageId = suffixNumber(TRAFFIC_CLEAR_IMAGE_ID, this.instanceIndex);\n const trafficMajorImageId = suffixNumber(TRAFFIC_MAJOR_IMAGE_ID, this.instanceIndex);\n const trafficModerateImageId = suffixNumber(TRAFFIC_MODERATE_IMAGE_ID, this.instanceIndex);\n const trafficMinorImageId = suffixNumber(TRAFFIC_MINOR_IMAGE_ID, this.instanceIndex);\n\n // loading of extra assets if not present in the map style:\n this.addImageIfNotExisting(waypointStartImageId, waypointStartIcon(svgIconOptions), options);\n this.addImageIfNotExisting(waypointStopImageId, waypointIcon(undefined, svgIconOptions), options);\n this.addImageIfNotExisting(waypointSoftImageId, softWaypointIcon(), options);\n this.addImageIfNotExisting(waypointFinishImageId, waypointFinishIcon(svgIconOptions), options);\n this.addImageIfNotExisting(instructionArrowImageId, instructionArrowIconImg, options);\n this.addImageIfNotExisting(\n selectedSummaryPopupImageId,\n summaryMapBubbleImg('white'),\n summaryBubbleImageOptions,\n );\n this.addImageIfNotExisting(\n deselectedSummaryPopupImageId,\n summaryMapBubbleImg('#EEEEEE'),\n summaryBubbleImageOptions,\n );\n this.addImageIfNotExisting(trafficClearImageId, trafficImg(UNKNOWN_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficMajorImageId, trafficImg(MAJOR_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficModerateImageId, trafficImg(MODERATE_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficMinorImageId, trafficImg(MINOR_DELAY_LABEL_COLOR), options);\n\n // If we have custom icons, ensure they're added to the map style:\n for (const customChargingStopIcon of config?.chargingStops?.icon?.customIcons ?? []) {\n this.addImageIfNotExisting(customChargingStopIcon.id, customChargingStopIcon.image as string, {\n pixelRatio: customChargingStopIcon.pixelRatio ?? 2,\n });\n }\n\n return routingSourcesWithLayers;\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config?: RoutingModuleConfig) {\n const mergedConfig = routeModuleConfigWithDefaults(config, this.layerIDPrefix, this.instanceIndex);\n\n // If there was already some config set, we must update the changes:\n if (this.config) {\n // replace existing configuration with new one\n const newLayersSpecs = createLayersSpecs(mergedConfig.layers, this.layerIDPrefix);\n\n // here we assume that keys for layer specs and sources are the same, please keep it that way to simplify the logic\n Object.keys(newLayersSpecs).forEach((layersSpecID) => {\n const id = layersSpecID as keyof RoutingSourcesWithLayers;\n updateLayersAndSource(\n newLayersSpecs[id],\n this.layersSpecs[id],\n this.sourcesWithLayers[id],\n this.mapLibreMap,\n );\n });\n // we need to add layers correctly\n const listOfSources = Object.values(this.sourcesWithLayers) as GeoJSONSourceWithLayers[];\n addLayers(\n listOfSources.flatMap((source) => source._layerSpecs),\n this.mapLibreMap,\n );\n // set the correct visibility if there are new layers\n listOfSources.forEach((source) => source.setLayersVisible(!!source.shownFeatures.features.length));\n this.layersSpecs = newLayersSpecs;\n }\n\n // Update waypoint icon images if mainColor changed\n const mainColorChanged = config?.theme?.mainColor !== this.config?.theme?.mainColor;\n if (mainColorChanged) {\n const svgIconOptions: SVGIconStyleOptions = {\n fillColor: config?.theme?.mainColor,\n ...config?.waypoints?.icon?.style,\n };\n const options: Partial<StyleImageMetadata> = { pixelRatio: 2 };\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_START_IMAGE_ID, this.instanceIndex),\n waypointStartIcon(svgIconOptions),\n this.mapLibreMap,\n options,\n );\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_STOP_IMAGE_ID, this.instanceIndex),\n waypointIcon(undefined, svgIconOptions),\n this.mapLibreMap,\n options,\n );\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_FINISH_IMAGE_ID, this.instanceIndex),\n waypointFinishIcon(svgIconOptions),\n this.mapLibreMap,\n options,\n );\n }\n\n // Summary bubbles have dedicated sources and contain distance-units dependent text ...\n // ... so we need to re-show or clear them if relevant config parts changed:\n const summaryBubblesVisible = mergedConfig.summaryBubbles?.visible !== false;\n const visibilityChanged = !isEqual(this.config?.summaryBubbles?.visible, mergedConfig.summaryBubbles?.visible);\n const displayUnitsChanged = !isEqual(this.config?.displayUnits, mergedConfig.displayUnits);\n const hasSummaryBubbles = this.sourcesWithLayers.summaryBubbles.shownFeatures.features.length > 0;\n const hasRoutes = this.sourcesWithLayers.mainLines.shownFeatures.features.length > 0;\n\n if (!summaryBubblesVisible && visibilityChanged) {\n this.sourcesWithLayers.summaryBubbles.clear();\n } else if (\n summaryBubblesVisible &&\n ((visibilityChanged && hasRoutes) || (displayUnitsChanged && hasSummaryBubbles))\n ) {\n this.sourcesWithLayers.summaryBubbles.show(\n toDisplayRouteSummaries(this.sourcesWithLayers.mainLines.shownFeatures, mergedConfig.displayUnits),\n );\n }\n\n return mergedConfig;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previouslyShown = Object.entries(this.sourcesWithLayers)\n .map((entry) => ({\n [entry[0]]: entry[1].shownFeatures,\n }))\n .reduce((acc, item) => ({ ...acc, ...item }), {}) as Record<keyof RoutingSourcesWithLayers, any>;\n\n this.initSourcesWithLayers(this.config, true);\n this._applyConfig(this.config);\n\n for (const key of Object.keys(previouslyShown) as (keyof RoutingSourcesWithLayers)[]) {\n this.sourcesWithLayers[key].show(previouslyShown[key]);\n }\n }\n\n private addImageIfNotExisting(\n imageId: string,\n image: string | HTMLImageElement,\n options: Partial<StyleImageMetadata> | undefined,\n ) {\n addOrUpdateImage('if-not-in-sprite', imageId, image, this.mapLibreMap, options);\n }\n\n /**\n * Displays the given routes on the map.\n *\n * @param routes - Route data from Routing API or custom routes.\n * @param options - Optional configuration for route selection and display.\n * @param options.selectedIndex - Index of the route to display as selected (default: 0).\n *\n * @remarks\n * **Waypoints are NOT shown by this method.**\n * Route lines are drawn but start/stop/destination markers are not. To display waypoint\n * markers, call {@link showWaypoints} separately. Alternatively, use\n * {@link PlacesModule} to display waypoints as fully customizable place markers.\n *\n * **Behavior:**\n * - Replaces any previously shown routes\n * - Shows all route-related features: lines, sections, summaries, guidance\n * - First route is selected by default (appears more prominent)\n *\n * **Route Features:**\n * - Main route lines (selected and deselected styles)\n * - Traffic sections with delays\n * - Ferry, tunnel, and toll sections\n * - EV charging stations (for EV routes)\n * - Turn-by-turn instruction lines and arrows\n * - Summary bubbles with distance/time/traffic info\n *\n * @example\n * Show a route (no waypoint markers):\n * ```typescript\n * await routing.showRoutes(response.routes);\n * // Waypoint markers are NOT shown — call showWaypoints() to add them\n * ```\n *\n * @example\n * Show routes and waypoint markers:\n * ```typescript\n * const locations = [[4.9, 52.4], [4.5, 51.9]];\n * const response = await calculateRoute({ locations });\n * await routing.showRoutes(response.routes);\n * await routing.showWaypoints(locations); // Show start/destination markers separately\n * ```\n *\n * @example\n * Show multiple routes with specific selection:\n * ```typescript\n * await routing.showRoutes(response.routes, { selectedIndex: 1 });\n * ```\n */\n async showRoutes(routes: Route | Routes, options?: ShowRoutesOptions) {\n const displayRoutes = toDisplayRoutes(routes, options?.selectedIndex);\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.mainLines.show(displayRoutes);\n this.sourcesWithLayers.vehicleRestricted.show(toDisplayRouteSections(displayRoutes, 'vehicleRestricted'));\n this.sourcesWithLayers.incidents.show(\n toDisplayRouteSections(displayRoutes, 'traffic', toDisplayTrafficSectionProps),\n );\n this.sourcesWithLayers.chargingStops.show(toDisplayChargingStops(displayRoutes, this.config));\n this.sourcesWithLayers.ferries.show(toDisplayRouteSections(displayRoutes, 'ferry'));\n this.sourcesWithLayers.tunnels.show(toDisplayRouteSections(displayRoutes, 'tunnel'));\n this.sourcesWithLayers.tollRoads.show(toDisplayRouteSections(displayRoutes, 'toll'));\n this.sourcesWithLayers.instructionLines.show(toDisplayInstructions(displayRoutes));\n this.sourcesWithLayers.instructionArrows.show(toDisplayInstructionArrows(displayRoutes));\n if (this.config?.summaryBubbles?.visible !== false) {\n this.sourcesWithLayers.summaryBubbles.show(\n toDisplayRouteSummaries(displayRoutes, this.config?.displayUnits),\n );\n } else {\n this.sourcesWithLayers.summaryBubbles.clear();\n }\n for (const handler of this.shownFeaturesHandlers) {\n handler({ routes });\n }\n }\n\n /**\n * Clears any previously shown routes from the map.\n *\n * @remarks\n * - Clears all route-related layers (lines, sections, guidance, summaries)\n * - Does NOT clear waypoints (use {@link clearWaypoints})\n * - Module remains initialized and ready for new routes\n *\n * @example\n * ```typescript\n * await routing.clearRoutes();\n * ```\n */\n async clearRoutes() {\n await this.waitUntilModuleReady();\n for (const key of Object.keys(this.sourcesWithLayers) as (keyof RoutingSourcesWithLayers)[]) {\n if (key !== 'waypoints') {\n this.sourcesWithLayers[key as keyof RoutingSourcesWithLayers].clear();\n }\n }\n }\n\n /**\n * Changes which route appears as selected.\n *\n * @param index - Zero-based index of the route to select.\n *\n * @remarks\n * **Visual Changes:**\n * - Selected route appears more prominent (thicker, brighter)\n * - Previously selected route becomes deselected style\n * - Updates all route-related features (sections, guidance)\n *\n * **Requirements:**\n * - Route must already be displayed via {@link showRoutes}\n * - Index must be within range of displayed routes\n *\n * @example\n * ```typescript\n * // Show multiple routes\n * await routingModule.showRoutes(routes);\n *\n * // User clicks alternative route\n * await routingModule.selectRoute(1);\n *\n * // Switch back to first route\n * await routingModule.selectRoute(0);\n * ```\n */\n async selectRoute(index: number) {\n const updatedRoutes = toDisplayRoutes(this.sourcesWithLayers.mainLines.shownFeatures, index);\n\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.mainLines.show(updatedRoutes);\n // TODO: simply update route style instead of regenerating EV stations again\n this.sourcesWithLayers.chargingStops.show(toDisplayChargingStops(updatedRoutes, this.config));\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.vehicleRestricted);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.incidents);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.ferries);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.tollRoads);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.tunnels);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.instructionLines);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.instructionArrows);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.summaryBubbles);\n }\n\n /**\n * Displays waypoint markers (start, stop, and destination pins) on the map.\n *\n * @param waypoints - An array of planning waypoints or a `Waypoints` FeatureCollection.\n *\n * @remarks\n * Waypoint markers are **not** shown by {@link showRoutes} — this method must be called\n * separately to display them. For more control over marker appearance and behavior,\n * consider using {@link PlacesModule} to render waypoints as place markers instead.\n *\n * @example\n * Show routes and then waypoints:\n * ```typescript\n * const locations = [[4.9041, 52.3676], [4.4777, 51.9244]];\n * const response = await calculateRoute({ locations });\n * await routingModule.showRoutes(response.routes);\n * await routingModule.showWaypoints(locations); // Must be called separately\n * ```\n */\n async showWaypoints(waypoints: PlanningWaypoint[] | Waypoints) {\n const displayWaypoints = Array.isArray(waypoints)\n ? toDisplayWaypoints(waypoints, this.config?.waypoints, this.instanceIndex)\n : // FeatureCollection expected:\n toDisplayWaypoints(waypoints.features as PlanningWaypoint[], this.config?.waypoints, this.instanceIndex);\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.waypoints.show(displayWaypoints);\n for (const handler of this.shownFeaturesHandlers) {\n handler({ waypoints });\n }\n }\n\n /**\n * Clears any previously shown waypoints from the map.\n * If nothing was shown before, nothing happens.\n */\n async clearWaypoints() {\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.waypoints.clear();\n }\n\n /**\n * Returns the currently shown routes and waypoints.\n *\n * @returns An object containing all currently displayed routing data.\n *\n * @remarks\n * Returns the exact data that was passed to the `showRoutes()` and `showWaypoints()` methods.\n *\n * **Returned Data:**\n * - `mainLines`: Main route lines (selected and alternative)\n * - `waypoints`: Route waypoints (start, stops, finish)\n * - `incidents`: Traffic incidents on routes\n * - `ferries`: Ferry sections\n * - `chargingStops`: EV charging stations\n * - `tollRoads`: Toll road sections\n * - `tunnels`: Tunnel sections\n * - `vehicleRestricted`: Vehicle-restricted sections\n * - `instructionLines`: Turn-by-turn instruction lines\n * - `instructionArrows`: Instruction arrow markers\n * - `summaryBubbles`: Route summary popups\n *\n * @example\n * ```typescript\n * const shown = routingModule.getShown();\n * console.log(`Showing ${shown.mainLines.features.length} routes`);\n * console.log(`Showing ${shown.waypoints.features.length} waypoints`);\n * console.log(`Showing ${shown.chargingStops.features.length} charging stops`);\n * ```\n *\n * @example\n * Check if any routes are displayed:\n * ```typescript\n * const shown = routingModule.getShown();\n * if (shown.mainLines.features.length > 0) {\n * console.log('Routes are displayed');\n * } else {\n * console.log('No routes displayed');\n * }\n * ```\n */\n getShown() {\n return {\n mainLines: this.sourcesWithLayers.mainLines.shownFeatures,\n waypoints: this.sourcesWithLayers.waypoints.shownFeatures,\n incidents: this.sourcesWithLayers.incidents.shownFeatures,\n ferries: this.sourcesWithLayers.ferries.shownFeatures,\n chargingStops: this.sourcesWithLayers.chargingStops.shownFeatures,\n tollRoads: this.sourcesWithLayers.tollRoads.shownFeatures,\n tunnels: this.sourcesWithLayers.tunnels.shownFeatures,\n vehicleRestricted: this.sourcesWithLayers.vehicleRestricted.shownFeatures,\n instructionLines: this.sourcesWithLayers.instructionLines.shownFeatures,\n instructionArrows: this.sourcesWithLayers.instructionArrows.shownFeatures,\n summaryBubbles: this.sourcesWithLayers.summaryBubbles.shownFeatures,\n };\n }\n\n /**\n * Unified events interface for the routing module, split into two namespaces:\n *\n * **`events.user`** — user interaction events, keyed by source:\n * ```typescript\n * routing.events.user.mainLines.on('click', (route) => { ... });\n * routing.events.user.waypoints.on('hover', (waypoint) => { ... });\n * ```\n *\n * **`events.module`** — module lifecycle events:\n * ```typescript\n * const unsub = routing.events.module.on('config-change', (config) => { ... });\n * const unsub = routing.events.module.on('shown-features', (features) => {\n * if ('routes' in features) { ... }\n * });\n * unsub();\n * ```\n */\n get events(): {\n user: {\n mainLines: UserEvents<Route<DisplayRouteProps>>;\n waypoints: UserEvents<Waypoint<WaypointDisplayProps>>;\n chargingStops: UserEvents<RouteSection>;\n summaryBubbles: UserEvents<DisplayRouteSummary>;\n incidents: UserEvents<RouteSection<DisplayTrafficSectionProps>>;\n vehicleRestricted: UserEvents<RouteSection>;\n ferries: UserEvents<RouteSection>;\n tollRoads: UserEvents<RouteSection>;\n tunnels: UserEvents<RouteSection>;\n instructionLines: UserEvents<DisplayInstruction>;\n };\n module: ModuleEvents<\n RoutingModuleConfig,\n { routes: Route | Routes } | { waypoints: PlanningWaypoint[] | Waypoints }\n >;\n } {\n return {\n user: {\n mainLines: new UserEvents<Route<DisplayRouteProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.mainLines,\n this.config?.events,\n ),\n waypoints: new UserEvents<Waypoint<WaypointDisplayProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.waypoints,\n this.config?.events,\n ),\n chargingStops: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.chargingStops,\n this.config?.events,\n ),\n summaryBubbles: new UserEvents<DisplayRouteSummary>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.summaryBubbles,\n this.config?.events,\n ),\n incidents: new UserEvents<RouteSection<DisplayTrafficSectionProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.incidents,\n this.config?.events,\n ),\n vehicleRestricted: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.vehicleRestricted,\n this.config?.events,\n ),\n ferries: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.ferries,\n this.config?.events,\n ),\n tollRoads: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.tollRoads,\n this.config?.events,\n ),\n tunnels: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.tunnels,\n this.config?.events,\n ),\n instructionLines: new UserEvents<DisplayInstruction>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.instructionLines,\n this.config?.events,\n ),\n },\n module: new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n };\n }\n\n /**\n * Returns the map style layer under which route lines are rendered.\n * * Useful if you want to render extra layers just above the route ones but not on top of everything else.\n * * It might differ depending on the loaded style/version.\n */\n getLayerToRenderLinesUnder(): string {\n return mapStyleLayerIDs.lowestLabel;\n }\n}\n","import type { StyleSpecification } from 'maplibre-gl';\nimport type { InternalTomTomMapParams, StandardStyle, StandardStyleID, StyleInput, StyleModule } from './types/mapInit';\nimport { styleModules } from './types/mapInit';\n\nexport const DEFAULT_STANDARD_STYLE_ID: StandardStyleID = 'standardLight';\n\nconst standardStyleModulesValues: Record<StandardStyleID, Record<StyleModule, string>> = {\n standardLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_light',\n },\n standardDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_dark',\n },\n drivingLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_light',\n },\n drivingDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_dark',\n },\n monoLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_mono-light',\n },\n monoDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_mono-dark',\n },\n satellite: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_satellite',\n },\n};\n\nconst mapSuffixes: Record<StandardStyleID, string> = {\n standardLight: 'basic_street-light',\n standardDark: 'basic_street-dark',\n drivingLight: 'basic_street-light-driving',\n drivingDark: 'basic_street-dark-driving',\n monoLight: 'basic_mono-light',\n monoDark: 'basic_mono-dark',\n satellite: 'basic_street-satellite',\n};\n\nconst buildStandardStyleUrl = (standardStyle: StandardStyle, baseUrl: string, apiKey: string): string => {\n const standardStyleID = standardStyle.id ?? DEFAULT_STANDARD_STYLE_ID;\n const version = standardStyle.version ?? '0.6.0-0';\n\n const styleURL = new URL(`${baseUrl}/maps/orbis/assets/styles/${version}/style.json`);\n // Param order is asserted by tests and matches the order historically\n // baked into URL_PREFIX: apiVersion, key, map, then modules.\n styleURL.searchParams.set('apiVersion', '1');\n // Proxy mode passes apiKey='' and lets the proxy inject the real key\n // server-side — skip the param so the proxy key never reaches the browser.\n if (apiKey) {\n styleURL.searchParams.set('key', apiKey);\n }\n styleURL.searchParams.set('map', mapSuffixes[standardStyleID]);\n\n for (const module of standardStyle.include ?? styleModules) {\n styleURL.searchParams.append(module, standardStyleModulesValues[standardStyleID][module]);\n }\n\n return styleURL.toString();\n};\n\nconst withApiKey = (givenUrl: string, apiKey: string): string => {\n const url = new URL(givenUrl);\n if (!url.searchParams.has('key')) {\n // In proxy mode the apiKey is intentionally empty; the proxy adds\n // the real key server-side. Don't write an empty `key=` param.\n if (apiKey) {\n url.searchParams.set('key', apiKey);\n }\n } else {\n console.warn(\n 'The style URL is coming with an API key parameter which takes priority. ' +\n 'If you want to use the SDK configured API key, remove the key param from the style URL',\n );\n }\n return url.toString();\n};\n\n/**\n * @ignore\n * @param mapParams The SDK parameters to convert to input renderer style.\n * @return The map style to load into the renderer.\n */\nexport const buildStyleInput = (mapParams: InternalTomTomMapParams): StyleSpecification | string => {\n const style = mapParams.style;\n const baseUrl = mapParams.commonBaseURL;\n const apiKey = mapParams.apiKey;\n\n if (typeof style === 'string') {\n return buildStandardStyleUrl({ id: style }, baseUrl, apiKey);\n } else if (style?.type === 'standard') {\n return buildStandardStyleUrl(style, baseUrl, apiKey);\n } else if (style?.type === 'custom' && style?.url) {\n return withApiKey(style.url, apiKey);\n } else if (style?.type === 'custom' && style?.json) {\n return style.json;\n }\n\n // no style defined, use default\n return buildStandardStyleUrl({ id: DEFAULT_STANDARD_STYLE_ID }, baseUrl, apiKey);\n};\n\n/**\n * Includes the previous standard style parts into the given standard style if the new one didn't define any.\n * * Both new and previous styles must be of \"standard\" type.\n * @ignore\n */\nexport const withPreviousStyleParts = (style: StyleInput, previousStyle?: StyleInput): StyleInput => {\n if (\n previousStyle &&\n typeof previousStyle === 'object' &&\n previousStyle.type === 'standard' &&\n previousStyle.include\n ) {\n if (typeof style === 'string' || (style.type === 'standard' && !style.include)) {\n return {\n type: 'standard',\n id: typeof style === 'string' ? style : style.id,\n include: (previousStyle as StandardStyle).include,\n };\n }\n }\n return style;\n};\n","import type { MapOptions } from 'maplibre-gl';\nimport { transformRequest } from '../shared/mapUtils';\nimport { buildStyleInput } from './styleInputBuilder';\nimport type { InternalTomTomMapParams } from './types/mapInit';\n\n/**\n * @ignore\n * @param tomtomMapParams\n */\nexport const buildMapOptions = (tomtomMapParams: InternalTomTomMapParams): MapOptions => {\n return {\n // defaults (can be overwritten by given options)\n validateStyle: false,\n maxTileCacheZoomLevels: 22,\n cancelPendingTileRequestsWhileZooming: false,\n // given options:\n ...tomtomMapParams.mapLibre,\n // SDK overrides (won't have any effect via given options):\n style: buildStyleInput(tomtomMapParams),\n attributionControl: { compact: false },\n transformRequest: transformRequest(tomtomMapParams),\n };\n};\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\n\n/**\n * @ignore\n */\nexport const isLayerLocalizable = (layer: SymbolLayerSpecification): boolean => {\n const textField = (layer.layout?.['text-field'] ?? '') as string | ExpressionSpecification;\n return textField\n ? // tries to detect layers which have a \"text-field\" that can be localized\n // ex. \"text-field\": \"{name}\" or \"text-field\": [\"get\", \"name\"]\n textField === '{name}' ||\n (textField.length === 2 && textField[1] === 'name') ||\n // tries to detect layers which have \"text-field\" that was localized already\n // ex. \"text-field\": [\"coalesce\", [\"get\", \"name_en\"], [\"get\", \"name\"]]\n (textField.length === 3 &&\n Array.isArray(textField[1]) &&\n typeof textField[1][1] === 'string' &&\n textField[1][1].includes('name_') &&\n Array.isArray(textField[2]) &&\n textField[2].includes('name'))\n : false;\n};\n","import { type BBox, type Language, mergeFromGlobal } from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport { getRTLTextPluginStatus, Map, setRTLTextPlugin, setWorkerCount } from 'maplibre-gl';\nimport { version as maplibreVersion } from 'maplibre-gl/package.json';\nimport type { InternalTomTomMapParams, MapLibreOptions, StyleInput, TomTomMapParams } from './init';\nimport { buildMapOptions } from './init/buildMapOptions';\nimport { buildStyleInput, DEFAULT_STANDARD_STYLE_ID, withPreviousStyleParts } from './init/styleInputBuilder';\nimport {\n EventsProxy,\n filterLayersBySources,\n HILLSHADE_SOURCE_ID,\n LightDark,\n TRAFFIC_FLOW_SOURCE_ID,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n} from './shared';\nimport { isLayerLocalizable } from './shared/localization';\nimport { addPinCategoriesSpriteToStyle, getStyleLightDarkTheme } from './shared/mapUtils';\n\n/**\n * Handler interface for responding to map style changes.\n *\n * @remarks\n * This interface defines callbacks that are invoked when the map style changes via {@link TomTomMap.setStyle}.\n * Use this to perform cleanup or reinitialization of custom map features when styles are switched.\n *\n * **Lifecycle:**\n * 1. `onStyleAboutToChange` - Called before the new style is applied\n * 2. Style change occurs\n * 3. `onStyleChanged` - Called after the new style has been fully loaded\n *\n * **Common Use Cases:**\n * - Saving and restoring custom layers or sources\n * - Reinitializing map modules after style changes\n * - Updating UI components based on the new style\n * - Cleaning up resources tied to the previous style\n *\n * @example\n * ```typescript\n * const styleHandler: StyleChangeHandler = {\n * onStyleAboutToChange: () => {\n * console.log('Style changing - saving state...');\n * // Save custom layer data\n * },\n * onStyleChanged: () => {\n * console.log('Style changed - restoring state...');\n * // Restore custom layers\n * }\n * };\n *\n * const unsubscribe = map.addStyleChangeHandler(styleHandler);\n * // Call unsubscribe() when the handler's owner is torn down.\n * ```\n *\n * @see {@link TomTomMap.addStyleChangeHandler}\n * @see {@link TomTomMap.setStyle}\n *\n * @group Map Style\n */\nexport type StyleChangeHandler = {\n /**\n * Callback invoked immediately before a style change begins.\n *\n * @remarks\n * Use this to perform cleanup or save state before the current style is removed.\n * This method can be synchronous or asynchronous.\n *\n * @returns void or a Promise that resolves when preparation is complete\n */\n onStyleAboutToChange?: () => void | Promise<void>;\n /**\n * Callback invoked after a new style has been fully loaded.\n *\n * @remarks\n * Use this to restore state, reinitialize layers, or perform other setup\n * that depends on the new style being ready. This method can be synchronous or asynchronous.\n *\n * @returns void or a Promise that resolves when reinitialization is complete\n */\n onStyleChanged?: () => void | Promise<void>;\n};\n\n/**\n * Main TomTom Map class for displaying interactive maps in web applications.\n *\n * This is the entry point for rendering TomTom maps. It wraps MapLibre GL JS and provides\n * a simplified, enhanced API for common mapping tasks.\n *\n * @remarks\n * **Key Features:**\n * - Built on MapLibre GL JS for high-performance rendering\n * - Seamless style switching without map reload\n * - Integrated event handling system\n * - Multi-language support with dynamic switching\n * - Compatible with TomTom map modules (traffic, POIs, routing, etc.)\n *\n * **Architecture:**\n * - Exposes the underlying MapLibre Map instance via {@link mapLibreMap}\n * - Manages map lifecycle and style transitions\n * - Coordinates with map modules for data visualization\n *\n * @example\n * Basic map initialization:\n * ```typescript\n * import { TomTomMap } from '@tomtom-international/maps-sdk-js/map';\n *\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: 'standardLight',\n * mapLibre: {\n * container: 'map',\n * center: [4.9041, 52.3676],\n * zoom: 10\n * }\n * });\n * ```\n *\n * @example\n * With modules and configuration:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: {\n * type: 'standard',\n * id: 'standardDark',\n * include: ['trafficFlow', 'trafficIncidents']\n * },\n * language: 'en-US',\n * events: {\n * precisionMode: 'point-then-box',\n * cursorOnHover: 'pointer'\n * },\n * mapLibre: {\n * container: 'map',\n * center: [-74.006, 40.7128],\n * zoom: 12\n * }\n * });\n *\n * // Access MapLibre functionality directly\n * map.mapLibreMap.on('load', () => {\n * console.log('Map loaded');\n * });\n * ```\n *\n * @group Map\n */\nexport class TomTomMap {\n /**\n * Indicates whether the map style has been fully loaded and is ready for interaction.\n *\n * @remarks\n * - `true` when the style is loaded and modules can be safely initialized\n * - `false` during map construction or style changes\n * - Check this before performing style-dependent operations\n *\n * @example\n * ```typescript\n * if (map.mapReady) {\n * // Safe to initialize modules\n * const trafficFlowModule = await TrafficFlowModule.get(map);\n * }\n * ```\n */\n mapReady = false;\n\n /**\n * The underlying MapLibre GL JS Map instance.\n *\n * @remarks\n * **When to Use:**\n * - Access advanced MapLibre functionality not exposed by TomTomMap\n * - Add custom layers, sources, or controls\n * - Listen to MapLibre-specific events\n * - Integrate third-party MapLibre plugins\n *\n * **Important:**\n * - Available immediately after TomTomMap construction\n * - Direct modifications may affect SDK module behavior\n * - Coordinate with SDK modules to avoid conflicts\n *\n * @example\n * Add custom layer:\n * ```typescript\n * map.mapLibreMap.addLayer({\n * id: 'custom-layer',\n * type: 'circle',\n * source: 'my-data',\n * paint: {\n * 'circle-radius': 6,\n * 'circle-color': '#ff0000'\n * }\n * });\n * ```\n *\n * @example\n * Listen to events:\n * ```typescript\n * map.mapLibreMap.on('moveend', () => {\n * console.log('Camera position:', map.mapLibreMap.getCenter());\n * });\n * ```\n *\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/map/ | MapLibre Map Documentation}\n */\n readonly mapLibreMap: Map;\n /**\n * @ignore\n */\n readonly _eventsProxy: EventsProxy;\n /**\n * @ignore\n */\n _params: InternalTomTomMapParams;\n\n styleLightDarkTheme: LightDark;\n\n private readonly styleChangeHandlers: StyleChangeHandler[] = [];\n\n /**\n * Constructs a new TomTom Map instance and attaches it to a DOM element.\n *\n * @param mapParams - Combined TomTom and MapLibre parameters for map initialization.\n * Includes API key, style, events, and MapLibre options like container, center, zoom, etc.\n * See {@link TomTomMapParams} for all available parameters.\n *\n * @remarks\n * **Initialization Process:**\n * 1. Merges `mapParams` with global configuration\n * 2. Creates underlying MapLibre map instance\n * 3. Loads specified style asynchronously\n * 4. Sets `mapReady` to `true` when complete\n *\n * **Configuration Priority:**\n * - Parameters passed here override global configuration\n * - Allows per-map customization while sharing common settings\n *\n * @example\n * Minimal initialization:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * mapLibre: {\n * container: 'map',\n * center: [0, 0],\n * zoom: 2\n * }\n * });\n * ```\n *\n * @example\n * Full configuration:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * },\n * language: 'en-US',\n * events: {\n * precisionMode: 'point-then-box',\n * paddingBoxPx: 10\n * },\n * mapLibre: {\n * container: 'map',\n * center: [-122.4194, 37.7749],\n * zoom: 13,\n * pitch: 45,\n * bearing: -17.6,\n * antialias: true,\n * maxZoom: 18,\n * minZoom: 8\n * }\n * });\n * ```\n *\n * @throws Will log errors if RTL text plugin fails to load (non-blocking)\n *\n * @see {@link MapLibreOptions}\n * @see {@link TomTomMapParams}\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/map/ | MapLibre Map Parameters}\n * @see [Map Quickstart Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/quickstart)\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n * @see [User Interaction Events Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/user-events)\n */\n constructor(mapParams: TomTomMapParams) {\n this._params = mergeFromGlobal(mapParams);\n if (this._params.style === undefined) {\n this._params = { ...this._params, style: DEFAULT_STANDARD_STYLE_ID };\n }\n this.styleLightDarkTheme = getStyleLightDarkTheme(this._params.style);\n this.ensureMapLibreCSSLoaded();\n\n // Set worker count before creating the Map instance.\n // MapLibre defaults to 1 worker (3 on Safari). 4 workers gives smooth\n // tile-by-tile transitions during style changes without starving the main thread.\n setWorkerCount(4);\n\n this.mapLibreMap = new Map(buildMapOptions(this._params));\n // Use `style.load` (fires once after style is fully parsed and all layers are created)\n // rather than `styledata` (fires repeatedly mid-load) — see setStyle for the reasoning.\n this.mapLibreMap.once('style.load', () => {\n this.handleStyleData(false);\n });\n this._eventsProxy = new EventsProxy(this.mapLibreMap, this._params?.events);\n\n this.loadRTLTextPlugin();\n }\n\n private loadRTLTextPlugin(): void {\n // deferred (just in case), lazy loading of the RTL plugin:\n setTimeout(() => {\n if (!['deferred', 'loaded'].includes(getRTLTextPluginStatus())) {\n setRTLTextPlugin(\n 'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js',\n true,\n ).catch((error) => console.error('Something went wrong when setting RTL plugin', error));\n }\n });\n }\n\n /**\n * Dynamically loads the MapLibre CSS stylesheet from CDN.\n */\n private ensureMapLibreCSSLoaded(): void {\n if (typeof document === 'undefined') {\n return;\n }\n // Check if the CSS is already loaded to avoid duplicates:\n const existingLink = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"], style')).some((element) =>\n element.textContent?.includes('.maplibregl-map'),\n );\n if (existingLink) {\n return;\n }\n\n // Create and inject a link tag to load CSS from unpkg CDN\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = `https://unpkg.com/maplibre-gl@${maplibreVersion}/dist/maplibre-gl.css`;\n document.head.appendChild(link);\n }\n\n /**\n * Changes the map style dynamically without reloading the entire map.\n *\n * @param style - The new style to apply. Can be a string ID or a detailed style configuration.\n * @param options - Configuration options for the style change behavior.\n * @param options.keepState - Whether to preserve SDK-rendered items and configurations when changing styles.\n * When `true` (default), maintains traffic layers, routes, markers, and other SDK features.\n * When `false`, performs a clean style switch without preserving previous state.\n *\n * @remarks\n * **Behavior:**\n * - Temporarily sets {@link mapReady} to `false` during the transition\n * - Triggers all registered {@link StyleChangeHandler} callbacks\n * - Resets {@link mapReady} to `true` when the new style is fully loaded\n *\n * **State Preservation (keepState: true):**\n * - Merges style parts from the previous style with the new one\n * - Maintains SDK module layers (traffic, routes, POIs, etc.)\n * - Preserves language settings\n *\n * **Clean Switch (keepState: false):**\n * - Applies the new style without merging previous configuration\n * - Removes all SDK module layers\n * - Useful for complete style resets\n *\n * @example\n * Simple style change:\n * ```typescript\n * // Switch to dark mode\n * map.setStyle('standardDark');\n * ```\n *\n * @example\n * Style change with detailed configuration:\n * ```typescript\n * map.setStyle({\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * });\n * ```\n *\n * @example\n * Clean style switch without state preservation:\n * ```typescript\n * // Complete reset - removes all SDK layers and modules\n * map.setStyle('standardDark', { keepState: false });\n * ```\n *\n * @example\n * With style change handlers:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * console.log('Preparing for style change...');\n * },\n * onStyleChanged: () => {\n * console.log('New style applied!');\n * }\n * });\n *\n * map.setStyle('standardDark');\n * ```\n *\n * @see {@link TomTomMapParams.style} - For setting style during initialization\n * @see {@link StyleChangeHandler} - For handling style change events\n * @see {@link getStyle} - For retrieving the current style\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n */\n setStyle = (style: StyleInput, options: { keepState?: boolean } = { keepState: true }): void => {\n this.mapReady = false;\n\n // Notify all modules that the style is about to change (they mark themselves as not ready).\n // Iterate a snapshot so a handler that unsubscribes itself (or another) during the callback\n // doesn't shift the live array out from under the loop.\n for (const handler of [...this.styleChangeHandlers]) {\n try {\n handler.onStyleAboutToChange?.();\n } catch (e) {\n console.error(e);\n }\n }\n const effectiveStyle = options.keepState ? withPreviousStyleParts(style, this._params.style) : style;\n this._params = { ...this._params, style: effectiveStyle };\n this.styleLightDarkTheme = getStyleLightDarkTheme(effectiveStyle);\n this.mapLibreMap.setStyle(buildStyleInput(this._params), { validate: false });\n // `style.load` fires once after MapLibre finishes applying all diff operations from the\n // new style — at that point every new layer exists in `map.getStyle().layers`, which is\n // what the module-restoration snapshot needs. Using `styledata` here would race because\n // it fires repeatedly during the diff and may run `handleStyleData` before all layers\n // are present.\n this.mapLibreMap.once('style.load', () => {\n // We only handle the style data change if the applied style is still the same as the one we set,\n // to prevent race conditions when handling stale styles applied quickly in succession.\n if (!this.mapReady && isEqual(effectiveStyle, this._params.style)) {\n this.handleStyleData(options.keepState || true);\n }\n });\n };\n\n /**\n * Retrieves the current style configuration of the map.\n *\n * @returns The current {@link StyleInput} configuration, or `undefined` if no style is set.\n *\n * @remarks\n * Returns the style configuration as it was set, not the fully resolved MapLibre style object.\n * Use this to inspect or store the current style configuration for later restoration.\n *\n * **Return Value:**\n * - String ID (e.g., `'standardLight'`) for simple style configurations\n * - Style object with `type`, `id`, and optional `include` properties for detailed configurations\n * - `undefined` if no style has been explicitly set\n *\n * @example\n * ```typescript\n * const currentStyle = map.getStyle();\n * console.log('Current style:', currentStyle);\n *\n * // Save style for later\n * const savedStyle = map.getStyle();\n *\n * // Later, restore it\n * if (savedStyle) {\n * map.setStyle(savedStyle);\n * }\n * ```\n *\n * @example\n * Conditional logic based on current style:\n * ```typescript\n * const style = map.getStyle();\n * if (typeof style === 'string' && style.includes('Dark')) {\n * console.log('Dark mode is active');\n * }\n * ```\n *\n * @see {@link setStyle} - For changing the map style\n * @see {@link StyleInput} - For available style configuration options\n */\n getStyle = (): StyleInput | undefined => {\n return this._params.style;\n };\n\n private _setLanguage(language: Language) {\n this._params = { ...this._params, language };\n const mapLanguage = language?.includes('-') ? language.split('-')[0] : language;\n this.mapLibreMap.getStyle().layers.forEach((layer) => {\n if (layer.type === 'symbol' && isLayerLocalizable(layer)) {\n const textFieldValue = mapLanguage\n ? ['coalesce', ['get', `name_${mapLanguage}`], ['get', 'name']]\n : ['get', 'name'];\n this.mapLibreMap.setLayoutProperty(layer.id, 'text-field', textFieldValue, { validate: false });\n }\n });\n }\n\n /**\n * Changes the language of the map.\n * * You can use this method to change the language at runtime.\n * * To set the language upon initialization, you can better do it via {@link core!TomTomConfig global config}\n * or {@link TomTomMapParams}.\n * @param language The language to be used in map translations.\n *\n * @remarks\n * **Behavior:**\n * - Updates all localizable map labels to the specified language\n * - Falls back to the default label name if the requested language is unavailable\n * - Can be called before or after the map is fully loaded\n * - If called before map is ready, will apply once the style loads\n *\n * **Language Format:**\n * - Simple language codes: `'en'`, `'fr'`, `'de'`, `'ja'`, `'zh'`\n * - Locale-specific codes: `'en-US'`, `'en-GB'`, `'zh-CN'`, `'pt-BR'`\n * - When using locale codes (with `-`), only the language portion is used for labels\n *\n * **Persistence:**\n * - Language setting persists across style changes (when `keepState: true`)\n * - Set during initialization via {@link TomTomMapParams.language} for immediate application\n *\n * @example\n * Change language at runtime:\n * ```typescript\n * // Switch to French\n * map.setLanguage('fr');\n * ```\n *\n * @example\n * Use locale-specific codes:\n * ```typescript\n * // Use Simplified Chinese\n * map.setLanguage('zh-CN');\n *\n * // Use Brazilian Portuguese\n * map.setLanguage('pt-BR');\n * ```\n *\n * @example\n * Language switcher UI:\n * ```typescript\n * const languageSelector = document.getElementById('lang-select');\n * languageSelector.addEventListener('change', (e) => {\n * map.setLanguage(e.target.value);\n * });\n * ```\n *\n * @see {@link TomTomMapParams.language} - For setting language during initialization\n * @see {@link https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes | ISO 639-1 Language Codes}\n */\n setLanguage(language: Language) {\n if (this.mapReady) {\n this._setLanguage(language);\n } else {\n this.mapLibreMap.once('styledata', () => this.setLanguage(language));\n }\n }\n\n /**\n * Retrieves the current visible map area as a GeoJSON bounding box.\n *\n * @returns A {@link https://tools.ietf.org/html/rfc7946#section-5 | GeoJSON BBox} array\n * in the format `[west, south, east, north]` representing the map's current viewport bounds.\n *\n * @remarks\n * **Return Format:**\n * - Array of four numbers: `[minLongitude, minLatitude, maxLongitude, maxLatitude]`\n * - Coordinates are in WGS84 decimal degrees\n * - West/East values range from -180 to 180\n * - South/North values range from -90 to 90\n *\n * @example\n * Get current bounds:\n * ```typescript\n * const bbox = map.getBBox();\n * console.log('Bounds:', bbox);\n * // Output: [-122.5, 37.7, -122.3, 37.8]\n * // [west, south, east, north]\n * ```\n *\n * @example\n * Use bounds for spatial query:\n * ```typescript\n * const bbox = map.getBBox();\n * const results = await searchAPI.searchInBoundingBox({\n * bbox: bbox,\n * query: 'restaurants'\n * });\n * ```\n *\n * @example\n * Save and restore map view:\n * ```typescript\n * // Save current view\n * const savedBounds = map.getBBox();\n * const savedZoom = map.mapLibreMap.getZoom();\n *\n * // Later, restore the view\n * const [west, south, east, north] = savedBounds;\n * map.mapLibreMap.fitBounds([[west, south], [east, north]]);\n * ```\n *\n * @see {@link https://tools.ietf.org/html/rfc7946#section-5 | GeoJSON BBox Specification}\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/geography/#lnglatbounds | MapLibre LngLatBounds}\n */\n getBBox(): BBox {\n return this.mapLibreMap.getBounds().toArray().flat() as BBox;\n }\n\n private handleStyleData(keepState: boolean) {\n // We ensure to make traffic and hillshade hidden by default (even if right after the modules bring it back to visible state)\n // This way we ensure such layers are invisible even of their related SDK modules are not used.\n for (const layer of filterLayersBySources(this.mapLibreMap, [\n TRAFFIC_INCIDENTS_SOURCE_ID,\n TRAFFIC_FLOW_SOURCE_ID,\n HILLSHADE_SOURCE_ID,\n ])) {\n this.mapLibreMap.setLayoutProperty(layer.id, 'visibility', 'none', { validate: false });\n }\n\n // For most use cases we'll need to have pins available (places, routing...) so we add them by default:\n // (subsequent loads for the same sprite should be cached)\n addPinCategoriesSpriteToStyle(this._params, this.mapLibreMap);\n // We restore the language if it was set before:\n this._params.language && this._setLanguage(this._params.language);\n\n this.mapReady = true;\n if (keepState) {\n // Snapshot: a handler may unsubscribe during onStyleChanged (e.g. an overlay tearing down).\n for (const handler of [...this.styleChangeHandlers]) {\n try {\n handler.onStyleChanged?.();\n } catch (e) {\n console.error(e);\n }\n }\n }\n }\n\n /**\n * Registers a handler to be notified when the map style changes.\n *\n * @param handler - A {@link StyleChangeHandler} object with callbacks for style change events.\n *\n * @returns An unsubscribe function that removes this handler. Call it when the\n * handler's owner (e.g. a custom overlay) is torn down — otherwise the handler,\n * and everything it closes over, lives for the lifetime of the map. Mirrors the\n * disposer returned by module event handlers (`module.events.on(...)`).\n *\n * @remarks\n * **When to Use:**\n * - You have custom layers or sources that need to be recreated after style changes\n * - Your application needs to respond to style switches (e.g., light/dark mode transitions)\n * - You need to save and restore state during style changes\n * - Map modules need to reinitialize when styles change\n *\n * **Handler Lifecycle:**\n * 1. `onStyleAboutToChange()` - Called before the style change begins\n * 2. Style change occurs\n * 3. `onStyleChanged()` - Called after the new style has been fully loaded\n *\n * **Multiple Handlers:**\n * - Multiple handlers can be registered and will all be called in registration order\n * - Each handler's errors are caught independently and logged to the console\n * - One failing handler won't prevent others from executing\n *\n * **Important Notes:**\n * - Handlers are only triggered by {@link setStyle} calls, not initial map construction\n * - Only called when `keepState: true` in {@link setStyle} options\n * - Handlers persist for the lifetime of the TomTomMap instance\n *\n * @example\n * Basic usage:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * console.log('Style is changing...');\n * },\n * onStyleChanged: () => {\n * console.log('Style changed successfully!');\n * }\n * });\n *\n * // Later trigger the handlers\n * map.setStyle('standardDark');\n * ```\n *\n * @example\n * Unsubscribe when the owner is torn down:\n * ```typescript\n * const unsubscribe = map.addStyleChangeHandler({\n * onStyleChanged: () => reattachCustomLayers(),\n * });\n *\n * // Later, when the overlay is removed:\n * unsubscribe();\n * ```\n *\n * @example\n * Preserve custom layers across style changes:\n * ```typescript\n * let customLayerData = null;\n *\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * // Save custom layer data before style changes\n * if (map.mapLibreMap.getLayer('my-custom-layer')) {\n * customLayerData = map.mapLibreMap.getSource('my-data')._data;\n * map.mapLibreMap.removeLayer('my-custom-layer');\n * map.mapLibreMap.removeSource('my-data');\n * }\n * },\n * onStyleChanged: () => {\n * // Restore custom layer after new style is loaded\n * if (customLayerData) {\n * map.mapLibreMap.addSource('my-data', {\n * type: 'geojson',\n * data: customLayerData\n * });\n * map.mapLibreMap.addLayer({\n * id: 'my-custom-layer',\n * type: 'circle',\n * source: 'my-data',\n * paint: { 'circle-radius': 6, 'circle-color': '#007cbf' }\n * });\n * }\n * }\n * });\n * ```\n *\n * @example\n * Async handler for external API calls:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: async () => {\n * await saveStateToAPI(map.getStyle());\n * },\n * onStyleChanged: async () => {\n * await loadStateFromAPI();\n * }\n * });\n * ```\n *\n * @example\n * Update UI based on style:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * document.body.classList.add('style-changing');\n * },\n * onStyleChanged: () => {\n * document.body.classList.remove('style-changing');\n * const style = map.getStyle();\n * if (typeof style === 'string' && style.includes('Dark')) {\n * document.body.classList.add('dark-mode');\n * } else {\n * document.body.classList.remove('dark-mode');\n * }\n * }\n * });\n * ```\n *\n * @see {@link StyleChangeHandler} - Handler interface definition\n * @see {@link setStyle} - Method that triggers the handlers\n */\n addStyleChangeHandler(handler: StyleChangeHandler): () => void {\n this.styleChangeHandlers.push(handler);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n const index = this.styleChangeHandlers.indexOf(handler);\n if (index !== -1) this.styleChangeHandlers.splice(index, 1);\n };\n }\n}\n","import type { AreaAnalyticsMetricKey } from '@tomtom-org/maps-sdk/core';\nimport type { BeforeLayerConfig, MapModuleCommonConfig } from '../../shared';\n\n/**\n * A single stop in an analytics color ramp.\n *\n * How `value` is interpreted depends on the surrounding `AreaAnalyticsColorStopsConfig.valueType`.\n * `color` is any valid CSS color string.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorStop = { value: number; color: string };\n\n/**\n * Visualization mode for area analytics data.\n *\n * - `'hexgrid-3d'` — 3D extruded hexagonal cells colored and raised by metric value\n * - `'hexgrid-2d'` — Flat hexagonal cells colored by metric value (no extrusion)\n * - `'square-3d'` — 3D extruded square cells colored and raised by metric value\n * - `'square-2d'` — Flat square cells colored by metric value (no extrusion)\n * - `'heatmap'` — MapLibre density heatmap layer from tile centre points\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsDisplayMode = 'heatmap' | 'hexgrid-3d' | 'hexgrid-2d' | 'square-3d' | 'square-2d';\n\n/**\n * Preset color theme for area analytics visualization.\n *\n * - `'trafficLight'` — Green → amber → red (default, traffic-signal style)\n * - `'heat'` — Blue → orange → red (heat-map style)\n * - `'monochrome'` — Light grey → dark grey (print-friendly)\n * - `'viridis'` — Purple → blue → green → yellow (perceptually uniform, colorblind-safe)\n * - `'plasma'` — Purple → magenta → orange → yellow (vivid, perceptually uniform)\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorTheme = 'trafficLight' | 'heat' | 'monochrome' | 'viridis' | 'plasma';\n\n/**\n * Determines how numeric `value` fields in color stops are interpreted.\n *\n * - `'raw'` — stop values are actual metric values used directly\n * (e.g. congestion: 0–100, speed: 0–120 km/h, travelTime: 0–20 s/km).\n * - `'relativeToPredefinedRangePCT'` — stop values are 0–100 percentages relative to the SDK's predefined\n * metric ranges (congestion 0–100, speed 0–120 km/h, travelTime 0–20 s/km).\n * Useful for consistent coloring regardless of the current dataset.\n * - `'relativeToActualRangePCT'` — stop values are 0–100 percentages relative to the live data\n * range actually present in the loaded tiles (0 % = loaded minimum, 100 % = loaded maximum).\n * Useful for maximum contrast when exact values are unknown.\n *\n * Defaults to `'raw'`.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsValueType = 'raw' | 'relativeToPredefinedRangePCT' | 'relativeToActualRangePCT';\n\n/**\n * Custom color stops configuration with an explicit value-type hint.\n *\n * @example\n * ```typescript\n * // Stops whose values are actual congestion-level values (0–100)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'raw',\n * stops: [\n * { value: 0, color: '#2dc653' },\n * { value: 30, color: '#f5a623' },\n * { value: 100, color: '#e03030' },\n * ],\n * };\n *\n * // Stops as 0–100 % of the predefined metric range (speed: 0–120 km/h)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'relativeToPredefinedRangePCT',\n * stops: [\n * { value: 0, color: '#e03030' }, // 0 km/h\n * { value: 50, color: '#f5a623' }, // 60 km/h\n * { value: 100, color: '#2dc653' }, // 120 km/h\n * ],\n * };\n *\n * // Stops as 0–100 % of the live data range (maximum contrast regardless of absolute values)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'relativeToActualRangePCT',\n * stops: [\n * { value: 0, color: '#2dc653' }, // 0% = loaded minimum\n * { value: 50, color: '#f5a623' }, // 50% = mid\n * { value: 100, color: '#e03030' }, // 100% = loaded maximum\n * ],\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorStopsConfig = {\n /**\n * How stop values are interpreted. Defaults to `'raw'`.\n */\n valueType?: AreaAnalyticsValueType;\n\n /**\n * The color stops array.\n */\n stops: AreaAnalyticsColorStop[];\n};\n\n/**\n * Filter for visible tiles based on a metric's value range.\n * Both `min` and `max` are optional; at least one should be provided to have effect.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsMetricFilter = {\n /** Show only tiles where the metric is ≥ this value. */\n min?: number;\n /** Show only tiles where the metric is ≤ this value. */\n max?: number;\n};\n\n/**\n * Height (extrusion) configuration for a metric.\n *\n * The shape depends on `scaleMode`:\n * - `'predefinedRange'` / `'currentRange'` (default): use `maxHeightMeters` to set a true visual ceiling.\n * - `'raw'`: use `scaleFactor` to multiply the raw metric value directly.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsHeightConfig =\n | {\n /**\n * Normalised scale mode. The metric is mapped to a known range before computing height,\n * so `maxHeightMeters` equals the actual maximum visual height in meters.\n *\n * @defaultValue 'predefinedRange'\n */\n scaleMode?: 'predefinedRange' | 'currentRange';\n\n /**\n * True visual maximum extrusion height in meters.\n * The tallest cell reaches exactly this height.\n *\n * @defaultValue 1000\n */\n maxHeightMeters?: number;\n\n /**\n * Minimum extrusion height in meters.\n *\n * @defaultValue 0\n */\n minHeightMeters?: number;\n }\n | {\n /** Raw scale mode: `scaleFactor` is multiplied directly with the raw metric value. */\n scaleMode: 'raw';\n\n /**\n * Multiplier applied to the raw metric value to obtain extrusion height in meters.\n *\n * @defaultValue 1\n */\n scaleFactor?: number;\n\n /**\n * Minimum extrusion height in meters.\n *\n * @defaultValue 0\n */\n minHeightMeters?: number;\n };\n\n/**\n * Per-metric visualization configuration.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsMetricConfig = {\n /**\n * Color for this metric's visualization.\n *\n * Pass a preset theme name (`AreaAnalyticsColorTheme`) or a custom\n * `AreaAnalyticsColorStopsConfig` object with explicit stop values and a\n * value-type hint. For `speed`, the color order is automatically inverted\n * so that slow speeds render as \"bad\" (red) and fast speeds as \"good\" (green).\n *\n * @defaultValue `'trafficLight'` theme\n */\n color?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig;\n\n /**\n * Extrusion height configuration for this metric.\n */\n height?: AreaAnalyticsHeightConfig;\n\n /**\n * Filter visible tiles to only those within the given value range for this metric.\n */\n filters?: AreaAnalyticsMetricFilter;\n};\n\n/**\n * Per-layer-type positioning configuration for area analytics layers.\n *\n * Controls where each analytics layer type sits in the map's layer stack independently.\n * Each property accepts `'top'` (above all layers) or a well-known style layer ID.\n * Omitted properties fall back to their default positions.\n *\n * @example\n * ```typescript\n * // Move all layers below the lowest label\n * const config: TrafficAreaAnalyticsConfig = {\n * beforeLayerConfig: {\n * heatmap: 'lowestLabel',\n * hexgrid: { flat2D: 'lowestLabel', extrusion3D: 'lowestPlaceLabel' },\n * square: { flat2D: 'lowestLabel', extrusion3D: 'lowestPlaceLabel' },\n * },\n * };\n *\n * // Only reposition the heatmap layer\n * const config: TrafficAreaAnalyticsConfig = {\n * beforeLayerConfig: { heatmap: 'top' },\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsBeforeLayerConfig = {\n /** Layer position for the heatmap layer. Defaults to `'lowestLabel'`. */\n heatmap?: BeforeLayerConfig;\n hexgrid?: {\n /** Layer position for the flat hexgrid fill layer. Defaults to `'lowestLabel'`. */\n flat2D?: BeforeLayerConfig;\n /** Layer position for the extruded hexgrid layer. Defaults to `'lowestPlaceLabel'`. */\n extrusion3D?: BeforeLayerConfig;\n };\n square?: {\n /** Layer position for the flat square fill layer. Defaults to `'lowestLabel'`. */\n flat2D?: BeforeLayerConfig;\n /** Layer position for the extruded square layer. Defaults to `'lowestPlaceLabel'`. */\n extrusion3D?: BeforeLayerConfig;\n };\n};\n\n/**\n * Display configuration for the actual region boundary geometry.\n *\n * When area analytics data is shown, the original region polygon (the area\n * the query was run for) is also rendered. Use this config to control its\n * appearance.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsRegionPolygonConfig = {\n /**\n * Color used for both the fill and outline of the region boundary.\n *\n * @defaultValue '#000000'\n */\n color?: string;\n\n /**\n * Opacity of the region fill.\n *\n * @defaultValue 0\n */\n fillOpacity?: number;\n\n /**\n * Opacity of the region outline.\n *\n * @defaultValue 0.5\n */\n outlineOpacity?: number;\n\n /**\n * Width of the region outline in pixels.\n *\n * @defaultValue 2\n */\n outlineWidth?: number;\n\n /**\n * When `true`, renders the region as an overlay covering everything *outside* the polygon\n * boundary, effectively tinting the area outside the analytics region.\n *\n * Uses a world-minus-polygon donut geometry (same technique as the `GeometriesModule`\n * `'inverted'` theme).\n *\n * @defaultValue false\n */\n inverted?: boolean;\n};\n\n// ── Default values ───────────────────────────────────────────────────\n\n/**\n * Default values for all {@link TrafficAreaAnalyticsConfig} options.\n *\n * Use these as a reference for what the module applies when a property is omitted,\n * or spread them as a base when constructing a config programmatically.\n *\n * @group Traffic Area Analytics\n */\nexport const AREA_ANALYTICS_DEFAULTS = {\n /** @see {@link TrafficAreaAnalyticsConfig.visible} */\n visible: true,\n /** @see {@link TrafficAreaAnalyticsConfig.displayMode} */\n displayMode: 'hexgrid-3d',\n /** @see {@link TrafficAreaAnalyticsConfig.activeMetric} */\n activeMetric: 'congestionLevel',\n /** @see {@link TrafficAreaAnalyticsConfig.metricConfig} */\n metricConfig: {\n congestionLevel: {\n color: {\n valueType: 'raw',\n stops: [\n { value: 0, color: '#2dc653' }, // free flow — green\n { value: 50, color: '#f5a623' }, // moderate — amber\n { value: 100, color: '#e03030' }, // severe — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n speed: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#e03030' }, // slow — red\n { value: 50, color: '#f5a623' }, // medium — amber\n { value: 100, color: '#2dc653' }, // fast — green\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n travelTime: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#2dc653' }, // fast travel — green\n { value: 50, color: '#f5a623' }, // moderate — amber\n { value: 100, color: '#e03030' }, // slow travel — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n freeFlowSpeed: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#e03030' }, // slow — red\n { value: 50, color: '#f5a623' }, // medium — amber\n { value: 100, color: '#2dc653' }, // fast — green\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n // networkLength is the total road length within each tile — per-tile values vary widely\n // and are unpredictable, so relativeToActualRangePCT is used as the default to ensure\n // the visualization always scales to the live data range.\n networkLength: {\n color: {\n valueType: 'relativeToActualRangePCT',\n stops: [\n { value: 0, color: '#2dc653' }, // low coverage — green\n { value: 50, color: '#f5a623' }, // mid coverage — amber\n { value: 100, color: '#e03030' }, // high coverage — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'currentRange' },\n },\n },\n /** @see {@link AreaAnalyticsRegionPolygonConfig} */\n regionPolygon: {\n color: '#000000',\n fillOpacity: 0,\n outlineOpacity: 0.5,\n outlineWidth: 2,\n inverted: false,\n },\n} satisfies TrafficAreaAnalyticsConfig;\n\n// ── Main config type ────────────────────────────────────────────────\n\n/**\n * Configuration for the Traffic Area Analytics visualization module.\n *\n * @remarks\n * The active metric is set via `activeMetric` (defaults to `'congestionLevel'`).\n * Per-metric style settings (color, height, filters) are nested under `metricConfig` keyed by\n * `AreaAnalyticsMetricKey`, making it easy to pre-configure multiple metrics independently and switch\n * between them at runtime via `setMetric()`.\n *\n * @example\n * ```typescript\n * const config: TrafficAreaAnalyticsConfig = {\n * displayMode: 'hexgrid-3d',\n * activeMetric: 'congestionLevel',\n * metricConfig: {\n * congestionLevel: {\n * color: 'heat',\n * height: { maxHeightMeters: 80, scaleMode: 'predefinedRange' },\n * filters: { min: 10 },\n * },\n * speed: {\n * color: {\n * valueType: 'raw',\n * stops: [\n * { value: 0, color: '#e03030' },\n * { value: 60, color: '#f5a623' },\n * { value: 120, color: '#2dc653' },\n * ],\n * },\n * },\n * },\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type TrafficAreaAnalyticsConfig = MapModuleCommonConfig & {\n /**\n * Controls the visibility of the area analytics layers.\n *\n * @defaultValue true\n */\n visible?: boolean;\n\n /**\n * Visualization mode.\n *\n * @defaultValue 'hexgrid-3d'\n */\n displayMode?: AreaAnalyticsDisplayMode;\n\n /**\n * The metric that drives color and extrusion height.\n *\n * @defaultValue 'congestionLevel'\n */\n activeMetric?: AreaAnalyticsMetricKey;\n\n /**\n * Per-metric visualization configuration (color, height, filters).\n *\n * Metrics not listed here fall back to defaults (congestion color theme, metric-dependent height scale).\n */\n metricConfig?: Partial<Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>>;\n\n /**\n * Per-layer-type positioning — controls where each analytics layer type sits in the map layer stack.\n * Omitted layer types fall back to their default positions (`'lowestLabel'` for flat/heatmap, `'lowestPlaceLabel'` for extrusions).\n */\n beforeLayerConfig?: AreaAnalyticsBeforeLayerConfig;\n\n /**\n * Display options for the region polygon boundary drawn alongside the analytics data.\n */\n regionPolygon?: AreaAnalyticsRegionPolygonConfig;\n};\n","import type { AreaAnalyticsMetricKey } from '@tomtom-org/maps-sdk/core';\nimport type {\n ExpressionSpecification,\n FillExtrusionLayerSpecification,\n FillLayerSpecification,\n HeatmapLayerSpecification,\n LineLayerSpecification,\n} from 'maplibre-gl';\nimport type { BeforeLayerConfig, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { mapStyleLayerIDs } from '../../shared';\nimport { isClickEventState } from '../../shared/layers/eventState';\nimport type {\n AreaAnalyticsColorStop,\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsColorTheme,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsRegionPolygonConfig,\n TrafficAreaAnalyticsConfig,\n} from '../types/trafficAreaAnalyticsConfig';\nimport { AREA_ANALYTICS_DEFAULTS } from '../types/trafficAreaAnalyticsConfig';\n\n// ── Internal types ───────────────────────────────────────────────────\n\n/**\n * @ignore\n */\nexport type MetricRange = { min: number; max: number };\n\n// ── Colour theme presets ─────────────────────────────────────────────\n\n/**\n * @ignore\n */\nexport const COLOR_THEMES: Record<AreaAnalyticsColorTheme, string[]> = {\n trafficLight: ['#2dc653', '#f5a623', '#e03030'],\n heat: ['#2196F3', '#FF9800', '#F44336'],\n monochrome: ['#e0e0e0', '#888888', '#1a1a1a'],\n viridis: ['#fde725', '#35b779', '#31688e', '#440154'],\n plasma: ['#f0f921', '#ed7953', '#9c179e', '#0d0887'],\n};\n\n// ── Metric ranges used for colour / height interpolation ─────────────\n\n// Per-tile networkLength values vary widely (they represent road coverage within one tile, not a region\n// total), so the predefined range is a loose upper bound used only as a fallback before data loads.\n// The networkLength default config uses relativeToActualRangePCT to normalize to the live data range.\nconst DEFAULT_METRIC_RANGES: Record<AreaAnalyticsMetricKey, MetricRange> = {\n congestionLevel: { min: 0, max: 100 },\n speed: { min: 0, max: 120 },\n travelTime: { min: 0, max: 20 },\n freeFlowSpeed: { min: 0, max: 120 },\n networkLength: { min: 0, max: 5_000 },\n};\n\n// Speed-related metrics are \"higher = better\", so preset color themes are applied in reverse.\nconst isSpeedMetric = (metric: AreaAnalyticsMetricKey): boolean => metric === 'speed' || metric === 'freeFlowSpeed';\n\n// ── Config resolution helpers ────────────────────────────────────────\n\n/** @ignore */\nexport const getActiveMetric = (config?: TrafficAreaAnalyticsConfig): AreaAnalyticsMetricKey => {\n return config?.activeMetric ?? AREA_ANALYTICS_DEFAULTS.activeMetric;\n};\n\n// Distributes an array of colors into evenly-spaced 0–1 normalized stops.\nconst distributeColors = (colors: string[]): AreaAnalyticsColorStop[] =>\n colors.map((color, i) => ({ value: colors.length === 1 ? 0 : i / (colors.length - 1), color }));\n\n/**\n * Resolves the effective color stops for a given color configuration.\n * @ignore\n */\nexport const resolveColorStops = (\n colorConfig: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n): AreaAnalyticsColorStop[] => {\n if (typeof colorConfig === 'string') return distributeColors(COLOR_THEMES[colorConfig]);\n return colorConfig.stops;\n};\n\nconst resolveBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestLabel;\n};\n\nconst resolveExtrusionBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestPlaceLabel;\n};\n\n// ── Color / height expression builders ──────────────────────────────\n\n// Builds a MapLibre interpolation expression mapping a metric property to a color scale.\nconst buildColorExpression = (\n metric: AreaAnalyticsMetricKey,\n colorConfig?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n computedRange?: MetricRange,\n): ExpressionSpecification => {\n const effectiveConfig = colorConfig ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric].color ?? 'trafficLight';\n const stops = resolveColorStops(effectiveConfig);\n\n let scaled: Array<readonly [number, string]>;\n\n if (typeof effectiveConfig === 'string') {\n // Preset theme: stops have 0-1 normalized values → scale to metric range.\n // Speed-related metrics are inverted (higher = better), so reverse only the colors —\n // values must remain ascending to satisfy MapLibre's interpolate expression requirement.\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n const colors = isSpeedMetric(metric) ? [...stops].reverse().map((s) => s.color) : stops.map((s) => s.color);\n scaled = stops.map(({ value }, i) => [min + value * (max - min), colors[i]] as const);\n } else if (effectiveConfig.valueType === 'relativeToPredefinedRangePCT') {\n // 0-100 values relative to predefined metric range\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n scaled = stops.map(({ value, color }) => [min + (value / 100) * (max - min), color] as const);\n } else if (effectiveConfig.valueType === 'relativeToActualRangePCT') {\n // 0-100 values relative to live data range: 0% = loaded min, 100% = loaded max\n const { min, max } = computedRange ?? DEFAULT_METRIC_RANGES[metric];\n scaled = stops.map(({ value, color }) => [min + (value / 100) * (max - min), color] as const);\n } else {\n // 'raw': stop values are actual data values, used directly\n scaled = stops.map(({ value, color }) => [value, color] as const);\n }\n\n return ['interpolate', ['linear'], ['get', metric], ...scaled.flatMap(([val, color]) => [val, color])];\n};\n\n// Builds a MapLibre heatmap-color expression using the given color config.\nconst buildHeatmapColorExpression = (\n metric: AreaAnalyticsMetricKey,\n colorConfig?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n): ExpressionSpecification => {\n const effectiveConfig = colorConfig ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric].color ?? 'trafficLight';\n const stops = resolveColorStops(effectiveConfig);\n // Map stop values (0-1 for presets; normalize for custom) to density range [0.3, 1.0]\n let densityStops: (number | string)[];\n\n if (typeof effectiveConfig === 'string') {\n // Preset: 0-1 normalized values map directly to density range.\n // Speed-related metrics are inverted (higher = better), so reverse only the colors —\n // values must remain ascending to satisfy MapLibre's interpolate expression requirement.\n const colors = isSpeedMetric(metric) ? [...stops].reverse().map((s) => s.color) : stops.map((s) => s.color);\n densityStops = stops.flatMap(({ value }, i) => [0.3 + value * 0.7, colors[i]]);\n } else if (\n effectiveConfig.valueType === 'relativeToPredefinedRangePCT' ||\n effectiveConfig.valueType === 'relativeToActualRangePCT'\n ) {\n // Both PCT types: 0-100 → normalize to 0-1 then map to density range [0.3, 1.0]\n densityStops = stops.flatMap(({ value, color }) => [0.3 + (value / 100) * 0.7, color]);\n } else {\n // 'raw': cannot easily normalize without data range for heatmap — treat as 0-1 normalized\n // (heatmap density is always 0-1; the first and last stop values anchor the range)\n const minValue = Math.min(...stops.map((s) => s.value));\n const maxValue = Math.max(...stops.map((s) => s.value));\n const range = maxValue - minValue || 1;\n densityStops = stops.flatMap(({ value, color }) => [0.3 + ((value - minValue) / range) * 0.7, color]);\n }\n\n return ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(0,0,0,0)', ...densityStops];\n};\n\n// Builds a MapLibre expression for fill-extrusion height driven by the active metric.\nconst buildHeightExpression = (\n metric: AreaAnalyticsMetricKey,\n heightConfig?: AreaAnalyticsHeightConfig,\n computedRange?: MetricRange,\n): ExpressionSpecification => {\n const defaultHeight = AREA_ANALYTICS_DEFAULTS.metricConfig[metric].height;\n const minHeight = heightConfig?.minHeightMeters ?? defaultHeight.minHeightMeters ?? 0;\n\n if (heightConfig?.scaleMode === 'raw') {\n const scaleFactor = heightConfig.scaleFactor ?? 1;\n if (metric === 'speed') {\n const { max } = DEFAULT_METRIC_RANGES[metric];\n return ['max', minHeight, ['*', ['-', max, ['coalesce', ['get', 'speed'], 0]], scaleFactor]];\n }\n return ['max', minHeight, ['*', ['coalesce', ['get', metric], 0], scaleFactor]];\n }\n\n // 'predefinedRange' or 'currentRange' (default)\n const maxHeight = heightConfig?.maxHeightMeters ?? defaultHeight.maxHeightMeters ?? 1000;\n const scaleMode = heightConfig?.scaleMode ?? defaultHeight.scaleMode;\n const { min, max } =\n scaleMode === 'currentRange' ? (computedRange ?? DEFAULT_METRIC_RANGES[metric]) : DEFAULT_METRIC_RANGES[metric];\n const range = max - min || 1;\n\n if (metric === 'speed') {\n return ['max', minHeight, ['*', ['/', ['-', max, ['coalesce', ['get', 'speed'], 0]], range], maxHeight]];\n }\n return ['max', minHeight, ['*', ['/', ['-', ['coalesce', ['get', metric], 0], min], range], maxHeight]];\n};\n\n// ── Heatmap layer builder ────────────────────────────────────────────\n\n// Expands a preset theme name into per-metric explicit raw stops with correct display ordering.\n// Speed-like metrics are inverted so \"slow\" always maps to the bad end of the ramp.\n// Used by the module when a theme is applied globally across all metrics.\nexport const expandThemeToAllMetrics = (\n theme: AreaAnalyticsColorTheme,\n): Partial<Record<AreaAnalyticsMetricKey, { color: AreaAnalyticsColorStopsConfig }>> => {\n const rawStops = resolveColorStops(theme);\n const toMetricStops = (metric: AreaAnalyticsMetricKey): AreaAnalyticsColorStopsConfig => {\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n const colors = isSpeedMetric(metric)\n ? [...rawStops].reverse().map((s) => s.color)\n : rawStops.map((s) => s.color);\n return {\n valueType: 'raw',\n stops: rawStops.map(({ value }, i) => ({ value: min + value * (max - min), color: colors[i] })),\n };\n };\n return {\n congestionLevel: { color: toMetricStops('congestionLevel') },\n speed: { color: toMetricStops('speed') },\n travelTime: { color: toMetricStops('travelTime') },\n freeFlowSpeed: { color: toMetricStops('freeFlowSpeed') },\n networkLength: { color: toMetricStops('networkLength') },\n };\n};\n\n/**\n * Builds the heatmap layer spec for a given source and config.\n * @ignore\n */\nexport const buildHeatmapLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<HeatmapLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n const { min, max } = computedRange ?? DEFAULT_METRIC_RANGES[metric];\n\n return {\n id: layerId,\n type: 'heatmap',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.heatmap),\n paint: {\n 'heatmap-weight': ['interpolate', ['linear'], ['get', metric], min, 0, max, 1],\n 'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 10, 0.5, 14, 2, 16, 4],\n 'heatmap-color': buildHeatmapColorExpression(metric, metricConfig?.color),\n 'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 3, 12, 35, 14, 80, 16, 100],\n 'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 4, 0.8, 13, 0.8, 17, 0],\n },\n };\n};\n\n// ── Hexgrid layer builders ───────────────────────────────────────────\n\n/**\n * Builds the flat hex fill layer spec.\n * @ignore\n */\nexport const buildHexFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.hexgrid?.flat2D),\n paint: {\n 'fill-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-opacity': 0.6,\n },\n };\n};\n\n/**\n * Builds the extruded hex layer spec.\n * @ignore\n */\nexport const buildHexExtrusionLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill-extrusion',\n beforeID: resolveExtrusionBeforeID(config?.beforeLayerConfig?.hexgrid?.extrusion3D),\n paint: {\n 'fill-extrusion-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-extrusion-height': buildHeightExpression(metric, metricConfig?.height, computedRange),\n 'fill-extrusion-base': 0,\n 'fill-extrusion-opacity': 0.75,\n },\n };\n};\n\n// ── Square grid layer builders ───────────────────────────────────────\n\n/**\n * Builds the flat square fill layer spec.\n * @ignore\n */\nexport const buildSquareFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.square?.flat2D),\n paint: {\n 'fill-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-opacity': 0.6,\n },\n };\n};\n\n/**\n * Builds the extruded square layer spec.\n * @ignore\n */\nexport const buildSquareExtrusionLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill-extrusion',\n beforeID: resolveExtrusionBeforeID(config?.beforeLayerConfig?.square?.extrusion3D),\n paint: {\n 'fill-extrusion-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-extrusion-height': buildHeightExpression(metric, metricConfig?.height, computedRange),\n 'fill-extrusion-base': 0,\n 'fill-extrusion-opacity': 0.75,\n },\n };\n};\n\n// ── Region boundary layer builders ───────────────────────────────────\n\n// Resolves the region display config with defaults applied.\nconst resolveRegionConfig = (region?: AreaAnalyticsRegionPolygonConfig) => ({\n ...AREA_ANALYTICS_DEFAULTS.regionPolygon,\n ...region,\n});\n\n/**\n * Builds the region boundary fill layer spec.\n * @ignore\n */\nexport const buildRegionFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const { color, fillOpacity } = resolveRegionConfig(config?.regionPolygon);\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(),\n paint: {\n 'fill-color': color,\n 'fill-opacity': fillOpacity,\n },\n };\n};\n\n/**\n * Builds the region boundary outline layer spec.\n * @ignore\n */\nexport const buildRegionLineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> => {\n const { color, outlineOpacity, outlineWidth } = resolveRegionConfig(config?.regionPolygon);\n\n return {\n id: layerId,\n type: 'line',\n beforeID: resolveBeforeID(),\n paint: {\n 'line-color': color,\n 'line-opacity': outlineOpacity,\n 'line-width': outlineWidth,\n },\n };\n};\n\n// ── Event-state highlight layer builders ──────────────────────────────\n//\n// These layers render only when a feature has an `eventState` property set\n// (written automatically by the EventsProxy on hover / click). They provide\n// visual feedback on top of the main data layers without modifying them.\n\n// Shared: white polygon footprint outline on hover / click.\nconst buildPolygonOutlineLayerSpec = (\n layerId: string,\n beforeID: string | undefined,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> => ({\n id: layerId,\n type: 'line',\n beforeID,\n filter: ['has', 'eventState'],\n paint: {\n 'line-color': '#ffffff',\n 'line-width': ['case', isClickEventState, 3, 2],\n 'line-opacity': ['case', isClickEventState, 1, 0.8],\n },\n});\n\n/**\n * White outline at the hex cell footprint on hover / click (2D mode).\n * @ignore\n */\nexport const buildHexOutlineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, resolveBeforeID(config?.beforeLayerConfig?.hexgrid?.flat2D));\n\n/**\n * White outline at the square cell footprint on hover / click (2D mode).\n * @ignore\n */\nexport const buildSquareOutlineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, resolveBeforeID(config?.beforeLayerConfig?.square?.flat2D));\n\n// Shared: derives a white semi-transparent fill-extrusion highlight from the given base data layer spec.\n// Inherits beforeID and fill-extrusion-base; raises height by 0.5 m to prevent z-fighting.\n// Raises height by 0.5 m so the highlight prism clears the data layer in the depth buffer.\n// Fill-extrusion uses a strict depth test: same-height overlays are discarded entirely.\nconst buildExtrusionHighlightLayerSpec = (\n base: ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification>,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => ({\n ...base,\n filter: ['has', 'eventState'],\n paint: {\n ...base.paint,\n // fill-extrusion-opacity is not data-driven — use a constant and vary the color instead.\n 'fill-extrusion-color': ['case', isClickEventState, '#ffffff', '#ffffff'],\n 'fill-extrusion-height': ['+', base.paint?.['fill-extrusion-height'] as ExpressionSpecification, 0.5],\n 'fill-extrusion-opacity': 0.6,\n },\n});\n\n/**\n * Semi-transparent white fill-extrusion overlay on hover / click in 3D hex mode.\n * Highlights all visible faces of the extruded prism, approximating a \"mesh\" highlight effect.\n * @ignore\n */\nexport const buildHexExtrusionHighlightLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(buildHexExtrusionLayerSpec(layerId, config, computedRange));\n\n/**\n * Semi-transparent white fill-extrusion overlay on hover / click in 3D square mode.\n * Highlights all visible faces of the extruded prism, approximating a \"mesh\" highlight effect.\n * @ignore\n */\nexport const buildSquareExtrusionHighlightLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(buildSquareExtrusionLayerSpec(layerId, config, computedRange));\n","import type { AreaAnalyticsTileEntry, TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { booleanPointInPolygon, hexGrid, squareGrid } from '@turf/turf';\nimport type { BBox, Feature, FeatureCollection, Point, Polygon } from 'geojson';\nimport type { AreaAnalyticsDisplayProperties } from '../types/trafficAreaAnalyticsFeature';\n\n/**\n * @ignore\n */\nexport const tilesToPointFeatures = (\n analytics: TrafficAreaAnalytics,\n): FeatureCollection<Point, AreaAnalyticsDisplayProperties> => {\n const tiles = analytics.features.flatMap((feature) => feature.properties?.tiledData?.tiles ?? []);\n return {\n type: 'FeatureCollection',\n features: tiles.map((tile, index) => {\n const [lon, lat] = tile.tileCentre;\n const id = `tile-${index}-${lon}-${lat}`;\n\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'Point',\n coordinates: [lon, lat],\n },\n properties: {\n id,\n speed: tile.speed ?? 0,\n freeFlowSpeed: tile.freeFlowSpeed ?? 0,\n congestionLevel: tile.congestionLevel ?? 0,\n travelTime: tile.travelTime ?? 0,\n networkLength: tile.networkLength ?? 0,\n },\n };\n }),\n };\n};\n\ntype CellAggregate = {\n congestionSum: number;\n speedSum: number;\n freeFlowSum: number;\n travelTimeSum: number;\n networkLengthSum: number;\n count: number;\n};\n\nconst buildCellAggregates = (\n tiles: ReadonlyArray<AreaAnalyticsTileEntry>,\n grid: FeatureCollection<Polygon>,\n): Array<CellAggregate | undefined> => {\n const cellAggregates: Array<CellAggregate | undefined> = [];\n\n for (const tile of tiles) {\n for (let index = 0; index < grid.features.length; index++) {\n if (booleanPointInPolygon(tile.tileCentre, grid.features[index])) {\n let cell = cellAggregates[index];\n if (cell === undefined) {\n cell = {\n congestionSum: 0,\n speedSum: 0,\n freeFlowSum: 0,\n travelTimeSum: 0,\n networkLengthSum: 0,\n count: 0,\n };\n cellAggregates[index] = cell;\n }\n cell.congestionSum += tile.congestionLevel ?? 0;\n cell.speedSum += tile.speed ?? 0;\n cell.freeFlowSum += tile.freeFlowSpeed ?? 0;\n cell.travelTimeSum += tile.travelTime ?? 0;\n cell.networkLengthSum += tile.networkLength ?? 0;\n cell.count++;\n break;\n }\n }\n }\n\n return cellAggregates;\n};\n\nconst cellAggregatesToFeatures = (\n cellAggregates: Array<CellAggregate | undefined>,\n grid: FeatureCollection<Polygon>,\n idPrefix: string,\n): Feature<Polygon, AreaAnalyticsDisplayProperties>[] => {\n const features: Feature<Polygon, AreaAnalyticsDisplayProperties>[] = [];\n\n for (let cellIndex = 0; cellIndex < cellAggregates.length; cellIndex++) {\n const cell = cellAggregates[cellIndex];\n if (!cell) continue;\n const { congestionSum, speedSum, freeFlowSum, travelTimeSum, networkLengthSum, count } = cell;\n const id = `${idPrefix}-${cellIndex}`;\n features.push({\n type: 'Feature',\n id,\n geometry: grid.features[cellIndex].geometry,\n properties: {\n id,\n congestionLevel: Math.round(congestionSum / count),\n speed: Math.round(speedSum / count),\n freeFlowSpeed: Math.round(freeFlowSum / count),\n travelTime: Math.round((travelTimeSum / count) * 10) / 10,\n networkLength: Math.round(networkLengthSum / count),\n },\n });\n }\n\n return features;\n};\n\nconst aggregateTilesIntoCells = (\n tiles: ReadonlyArray<AreaAnalyticsTileEntry>,\n grid: FeatureCollection<Polygon>,\n idPrefix: string,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>['features'] =>\n cellAggregatesToFeatures(buildCellAggregates(tiles, grid), grid, idPrefix);\n\ntype BuildGridFn = (bbox: BBox, cellSideKm: number) => FeatureCollection<Polygon>;\n\n// Expand the bbox slightly so tile centres that sit exactly on the bbox edge\n// fall strictly inside a grid cell. booleanPointInPolygon returns false for\n// boundary points, so without this padding those tiles would never be matched.\nconst BBOX_PADDING_DEG = 0.005; // ~350–550 m — enough for hexGrid/squareGrid to generate cells even when all tiles are very close together\n\nconst tilesToGridFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm: number,\n buildGridFn: BuildGridFn,\n idPrefix: string,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> => {\n const allCells: FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>['features'] = [];\n\n for (let featureIndex = 0; featureIndex < analytics.features.length; featureIndex++) {\n const analyticsFeature = analytics.features[featureIndex];\n const tiles = analyticsFeature.properties?.tiledData?.tiles ?? [];\n\n if (tiles.length === 0) continue;\n\n const bbox = bboxFromGeoJSON(analyticsFeature);\n if (!bbox) continue;\n\n const paddedBbox: BBox = [\n bbox[0] - BBOX_PADDING_DEG,\n bbox[1] - BBOX_PADDING_DEG,\n bbox[2] + BBOX_PADDING_DEG,\n bbox[3] + BBOX_PADDING_DEG,\n ];\n const grid = buildGridFn(paddedBbox, cellSideKm);\n allCells.push(...aggregateTilesIntoCells(tiles, grid, `${idPrefix}-${featureIndex}`));\n }\n\n return { type: 'FeatureCollection', features: allCells };\n};\n\n/**\n * Converts area-analytics tile entries into a hexagonal polygon grid\n * using Turf's `hexGrid` and spatial-join aggregation.\n *\n * Tiles that fall within the same hexagon cell are averaged. Empty hexagons\n * (containing no tiles) are excluded from the result.\n *\n * @param analytics - The parsed `TrafficAreaAnalytics` response.\n * @param cellSideKm - Hex cell side length in kilometres (default 0.25).\n * @returns A Polygon FeatureCollection with averaged metric properties.\n *\n * @ignore\n */\nexport const tilesToHexFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm = 0.25,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> =>\n tilesToGridFeatures(analytics, cellSideKm, (bbox, km) => hexGrid(bbox, km, { units: 'kilometers' }), 'hex');\n\n/**\n * Converts area-analytics tile entries into a square polygon grid\n * using Turf's `squareGrid` and spatial-join aggregation.\n *\n * Tiles that fall within the same square cell are averaged. Empty cells\n * (containing no tiles) are excluded from the result.\n *\n * @param analytics - The parsed `TrafficAreaAnalytics` response.\n * @param cellSideKm - Square cell side length in kilometres (default 0.5).\n * @returns A Polygon FeatureCollection with averaged metric properties.\n *\n * @ignore\n */\nexport const tilesToSquareFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm = 0.5,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> =>\n tilesToGridFeatures(analytics, cellSideKm, (bbox, km) => squareGrid(bbox, km, { units: 'kilometers' }), 'square');\n","import type { AreaAnalyticsMetricKey, TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport { mask } from '@turf/turf';\nimport type { FeatureCollection, Point, Polygon } from 'geojson';\nimport { ExpressionSpecification } from 'maplibre-gl';\nimport type { BeforeLayerConfig } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { changeLayerProps, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildHeatmapLayerSpec,\n buildHexExtrusionHighlightLayerSpec,\n buildHexExtrusionLayerSpec,\n buildHexFillLayerSpec,\n buildHexOutlineLayerSpec,\n buildRegionFillLayerSpec,\n buildRegionLineLayerSpec,\n buildSquareExtrusionHighlightLayerSpec,\n buildSquareExtrusionLayerSpec,\n buildSquareFillLayerSpec,\n buildSquareOutlineLayerSpec,\n expandThemeToAllMetrics,\n getActiveMetric,\n} from './layers/areaAnalyticsLayers';\nimport type {\n AreaAnalyticsBeforeLayerConfig,\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsColorTheme,\n AreaAnalyticsDisplayMode,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsMetricConfig,\n AreaAnalyticsMetricFilter,\n TrafficAreaAnalyticsConfig,\n} from './types/trafficAreaAnalyticsConfig';\nimport { AREA_ANALYTICS_DEFAULTS } from './types/trafficAreaAnalyticsConfig';\nimport type { AreaAnalyticsDisplayProperties, AreaAnalyticsTileFeature } from './types/trafficAreaAnalyticsFeature';\nimport { tilesToHexFeatures, tilesToPointFeatures, tilesToSquareFeatures } from './util/areaAnalyticsGeoJSONTiles';\n\n/**\n * Sources and layers managed by this module.\n * @ignore\n */\ntype AreaAnalyticsSourcesWithLayers = {\n heatmap: GeoJSONSourceWithLayers<FeatureCollection<Point, AreaAnalyticsDisplayProperties>>;\n hexgrid: GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>;\n square: GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>;\n region: GeoJSONSourceWithLayers;\n};\n\n/**\n * Traffic Area Analytics visualization module.\n *\n * Renders area-analytics data on the map in one of five modes:\n * - **hexgrid-3d** — 3D extruded hexagonal cells coloured and raised by the active metric (default)\n * - **hexgrid-2d** — Flat hexagonal cells coloured by the active metric\n * - **square-3d** — 3D extruded square cells coloured and raised by the active metric\n * - **square-2d** — Flat square cells coloured by the active metric\n * - **heatmap** — a MapLibre density-heatmap layer built from tile-centre points\n *\n * The actual region boundary is always rendered alongside the analytics cells.\n *\n * @remarks\n * **Quick start:**\n * ```typescript\n * import { TrafficAreaAnalyticsModule } from '@tomtom-org/maps-sdk/map';\n * import { trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';\n *\n * const module = await TrafficAreaAnalyticsModule.get(map, {\n * displayMode: 'hexgrid-3d',\n * metric: { active: 'congestionLevel' },\n * });\n * const analytics = await trafficAreaAnalytics({ ... });\n * await module.show(analytics);\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport class TrafficAreaAnalyticsModule extends AbstractMapModule<\n AreaAnalyticsSourcesWithLayers,\n TrafficAreaAnalyticsConfig\n> {\n // ── Cached layer specs (used by changeLayerProps for config updates) ──\n\n private heatmapLayerSpec!: ReturnType<typeof buildHeatmapLayerSpec>;\n private hexFillLayerSpec!: ReturnType<typeof buildHexFillLayerSpec>;\n private hexExtrusionLayerSpec!: ReturnType<typeof buildHexExtrusionLayerSpec>;\n private hexOutlineLayerSpec!: ReturnType<typeof buildHexOutlineLayerSpec>;\n private hexExtrusionHighlightLayerSpec!: ReturnType<typeof buildHexExtrusionHighlightLayerSpec>;\n private squareFillLayerSpec!: ReturnType<typeof buildSquareFillLayerSpec>;\n private squareExtrusionLayerSpec!: ReturnType<typeof buildSquareExtrusionLayerSpec>;\n private squareOutlineLayerSpec!: ReturnType<typeof buildSquareOutlineLayerSpec>;\n private squareExtrusionHighlightLayerSpec!: ReturnType<typeof buildSquareExtrusionHighlightLayerSpec>;\n private regionFillLayerSpec!: ReturnType<typeof buildRegionFillLayerSpec>;\n private regionLineLayerSpec!: ReturnType<typeof buildRegionLineLayerSpec>;\n\n // Cached for style-change restoration.\n private lastAnalytics: TrafficAreaAnalytics | null = null;\n private readonly shownFeaturesHandlers: ((features: TrafficAreaAnalytics) => void)[] = [];\n\n // ── Factory ──────────────────────────────────────────────────────\n\n /**\n * Creates and initialises a new Traffic Area Analytics module.\n *\n * All configuration properties are optional. When `config` is omitted (or only\n * partially supplied), built-in defaults are applied for every missing field.\n * After `get()` resolves, {@link TrafficAreaAnalyticsModule.getConfig} always returns a\n * fully-populated configuration — there is no need to reference any default\n * values yourself.\n *\n * @param tomtomMap - The TomTomMap instance.\n * @param config - Optional initial configuration. Omit to use all defaults.\n * @returns A promise that resolves with the initialised module.\n *\n * @example\n * ```typescript\n * // No config needed — defaults are applied automatically.\n * const module = await TrafficAreaAnalyticsModule.get(map);\n * await module.show(analytics);\n *\n * // Or override specific properties while keeping everything else at default:\n * const module = await TrafficAreaAnalyticsModule.get(map, {\n * displayMode: 'heatmap',\n * metricConfig: {\n * congestionLevel: { color: 'heat' },\n * },\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: TrafficAreaAnalyticsConfig): Promise<TrafficAreaAnalyticsModule> {\n await waitUntilMapIsReady(tomtomMap);\n // Always pass a config object (even if empty) so _applyConfig fills in all\n // defaults and getConfig() returns a fully-populated result from the start.\n return new TrafficAreaAnalyticsModule(tomtomMap, config ?? {});\n }\n\n private constructor(map: TomTomMap, config?: TrafficAreaAnalyticsConfig) {\n super('geojson', map, config);\n }\n\n // ── AbstractMapModule hooks ──────────────────────────────────────\n\n /** @ignore */\n protected _initSourcesWithLayers(\n config?: TrafficAreaAnalyticsConfig,\n _restore?: boolean,\n ): AreaAnalyticsSourcesWithLayers {\n const index = this.instanceIndex;\n const heatmapSourceId = `area-analytics-heatmap-${index}`;\n const hexgridSourceId = `area-analytics-hexgrid-${index}`;\n const squareSourceId = `area-analytics-square-${index}`;\n const regionSourceId = `area-analytics-region-${index}`;\n\n const heatmapLayerId = `${heatmapSourceId}-layer`;\n const hexFillLayerId = `${hexgridSourceId}-fill`;\n const hexExtrusionLayerId = `${hexgridSourceId}-extrusion`;\n const hexOutlineLayerId = `${hexgridSourceId}-outline`;\n const hexExtrusionHighlightLayerId = `${hexgridSourceId}-extrusion-highlight`;\n const squareFillLayerId = `${squareSourceId}-fill`;\n const squareExtrusionLayerId = `${squareSourceId}-extrusion`;\n const squareOutlineLayerId = `${squareSourceId}-outline`;\n const squareExtrusionHighlightLayerId = `${squareSourceId}-extrusion-highlight`;\n const regionFillLayerId = `${regionSourceId}-fill`;\n const regionLineLayerId = `${regionSourceId}-line`;\n\n // No computed ranges available at init time — pass undefined\n this.heatmapLayerSpec = buildHeatmapLayerSpec(heatmapLayerId, config);\n this.hexFillLayerSpec = buildHexFillLayerSpec(hexFillLayerId, config);\n this.hexExtrusionLayerSpec = buildHexExtrusionLayerSpec(hexExtrusionLayerId, config);\n this.squareFillLayerSpec = buildSquareFillLayerSpec(squareFillLayerId, config);\n this.squareExtrusionLayerSpec = buildSquareExtrusionLayerSpec(squareExtrusionLayerId, config);\n this.regionFillLayerSpec = buildRegionFillLayerSpec(regionFillLayerId, config);\n this.regionLineLayerSpec = buildRegionLineLayerSpec(regionLineLayerId, config);\n\n // Highlight/outline specs — added directly to MapLibre (not through GeoJSONSourceWithLayers)\n // so they are never included in interactiveLayerIDs and won't break queryRenderedFeatures.\n this.hexOutlineLayerSpec = buildHexOutlineLayerSpec(hexOutlineLayerId, config);\n this.hexExtrusionHighlightLayerSpec = buildHexExtrusionHighlightLayerSpec(hexExtrusionHighlightLayerId, config);\n this.squareOutlineLayerSpec = buildSquareOutlineLayerSpec(squareOutlineLayerId, config);\n this.squareExtrusionHighlightLayerSpec = buildSquareExtrusionHighlightLayerSpec(\n squareExtrusionHighlightLayerId,\n config,\n );\n\n // Create sources with only the interactive data layers. Highlight layers share the same\n // MapLibre source but are added separately below to stay out of interactiveLayerIDs.\n const result: AreaAnalyticsSourcesWithLayers = {\n heatmap: new GeoJSONSourceWithLayers<FeatureCollection<Point, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n heatmapSourceId,\n [this.heatmapLayerSpec],\n ),\n hexgrid: new GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n hexgridSourceId,\n [this.hexFillLayerSpec, this.hexExtrusionLayerSpec],\n ),\n square: new GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n squareSourceId,\n [this.squareFillLayerSpec, this.squareExtrusionLayerSpec],\n ),\n region: new GeoJSONSourceWithLayers(this.mapLibreMap, regionSourceId, [\n this.regionFillLayerSpec,\n this.regionLineLayerSpec,\n ]),\n };\n\n // Sources are now in the map — safe to add highlight layers on top.\n this.addHighlightLayersToMap(hexgridSourceId, squareSourceId);\n\n return result;\n }\n\n /** @ignore */\n protected _applyConfig(config: TrafficAreaAnalyticsConfig | undefined): TrafficAreaAnalyticsConfig | undefined {\n if (config === undefined) return;\n\n // Deep-merge the metricConfig record at the per-metric-key level so that\n // passing { metricConfig: { congestionLevel: { color: 'heat' } } } does\n // not wipe out configs for other metrics.\n const mergedMetricConfig: TrafficAreaAnalyticsConfig['metricConfig'] =\n config.metricConfig && this.config?.metricConfig\n ? { ...this.config.metricConfig, ...config.metricConfig }\n : (config.metricConfig ?? this.config?.metricConfig);\n\n // Ensure every metric has an explicit config entry so getConfig() always\n // returns fully-populated stops (callers never need to fall back to defaults).\n const defaultKeys = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const filledMetricConfig = Object.fromEntries(\n defaultKeys.map((metric) => [\n metric,\n mergedMetricConfig?.[metric] ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric],\n ]),\n ) as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>;\n\n const merged: TrafficAreaAnalyticsConfig = {\n activeMetric: AREA_ANALYTICS_DEFAULTS.activeMetric,\n displayMode: AREA_ANALYTICS_DEFAULTS.displayMode,\n ...this.config,\n ...config,\n metricConfig: filledMetricConfig,\n };\n\n this.applyLayerConfig(merged);\n\n if (merged.regionPolygon) {\n this.applyRegionConfig(merged);\n }\n\n if (merged.beforeLayerConfig) {\n this._moveBeforeLayer(merged.beforeLayerConfig, false);\n }\n\n // Set config before applyModeVisibility, which reads `this.config.visible` and\n // `this.config.displayMode`. The parent's applyConfig will overwrite this.config\n // with the same value after we return, and emit config-change exactly once.\n this.config = merged;\n this.applyModeVisibility();\n return merged;\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n const cachedAnalytics = this.lastAnalytics;\n this.initSourcesWithLayers(this.config, true);\n\n if (cachedAnalytics) {\n // Skip _applyConfig — show() will apply metrics + visibility after data is loaded.\n if (this.config?.visible === false) {\n this.setVisible(false);\n }\n void this.show(cachedAnalytics);\n } else if (this.config) {\n this._applyConfig(this.config);\n }\n }\n\n // ── Public API ───────────────────────────────────────────────────\n\n /**\n * Displays area analytics data on the map.\n *\n * Accepts the raw response from `trafficAreaAnalytics()`. Tile data is\n * transformed internally to point features (heatmap), hexagonal polygons\n * (hexgrid modes), and square polygons (square modes).\n * The region boundary geometry is also displayed.\n *\n * @param analytics - The raw `TrafficAreaAnalytics` service response.\n *\n * @example\n * ```typescript\n * const analytics = await trafficAreaAnalytics({ ... });\n * await module.show(analytics);\n * ```\n */\n async show(analytics: TrafficAreaAnalytics): Promise<void> {\n await this.waitUntilModuleReady();\n\n this.lastAnalytics = analytics;\n\n this.sourcesWithLayers.heatmap.show(tilesToPointFeatures(analytics), { automaticVisibility: false });\n this.sourcesWithLayers.hexgrid.show(tilesToHexFeatures(analytics), { automaticVisibility: false });\n this.sourcesWithLayers.square.show(tilesToSquareFeatures(analytics), { automaticVisibility: false });\n\n const inverted = this.config?.regionPolygon?.inverted ?? AREA_ANALYTICS_DEFAULTS.regionPolygon.inverted;\n this.sourcesWithLayers.region.show(this.buildRegionFC(analytics.features, inverted));\n\n this.applyLayerConfig(this.config);\n this.applyModeVisibility();\n for (const handler of this.shownFeaturesHandlers) {\n handler(analytics);\n }\n }\n\n /**\n * Removes all area analytics data from the map.\n */\n async clear(): Promise<void> {\n await this.waitUntilModuleReady();\n this.lastAnalytics = null;\n this.sourcesWithLayers.heatmap.clear();\n this.sourcesWithLayers.hexgrid.clear();\n this.sourcesWithLayers.square.clear();\n this.sourcesWithLayers.region.clear();\n }\n\n /**\n * Switches the visualisation mode.\n *\n * @param mode - One of `'heatmap'`, `'hexgrid-3d'`, `'hexgrid-2d'`, `'square-3d'`, `'square-2d'`.\n */\n setMode(mode: AreaAnalyticsDisplayMode): void {\n if (mode === this.mode) return;\n this.config = { ...this.config, displayMode: mode };\n this.applyModeVisibility();\n this.emitConfigChange();\n }\n\n /**\n * Changes the active metric that drives colour and height.\n *\n * Updates `activeMetric` in the config to the given metric key.\n *\n * @param metric - One of `'congestionLevel'`, `'speed'`, `'travelTime'`, `'freeFlowSpeed'`, or `'networkLength'`.\n */\n setMetric(metric: AreaAnalyticsMetricKey): void {\n if (metric === this.activeMetric) return;\n this.config = { ...this.config, activeMetric: metric };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Sets the color for one or more metrics.\n *\n * Pass a preset theme name or a custom color-stops configuration.\n * Pass `undefined` to clear the color override for the targeted metrics.\n *\n * @param color - A preset theme, an `AreaAnalyticsColorStopsConfig`, or `undefined` to clear.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the color is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Apply a theme to all metrics\n * module.setColor('heat');\n *\n * // Apply a theme to specific metrics only\n * module.setColor('heat', ['speed', 'freeFlowSpeed']);\n *\n * // Apply custom stops to a single metric\n * module.setColor(\n * { valueType: 'raw', stops: [{ value: 0, color: '#2dc653' }, { value: 100, color: '#e03030' }] },\n * ['congestionLevel'],\n * );\n *\n * // Clear color override for all metrics\n * module.setColor(undefined);\n *\n * // Clear color override for a specific metric\n * module.setColor(undefined, ['speed']);\n * ```\n */\n setColor(\n color: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig | undefined,\n metrics?: AreaAnalyticsMetricKey[],\n ): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n if (color === undefined) {\n // Clear color override for targeted metrics.\n for (const metric of targetMetrics) {\n const { color: _c, ...rest } = updatedMetricConfig[metric] ?? {};\n updatedMetricConfig[metric] = rest as AreaAnalyticsMetricConfig;\n }\n } else if (typeof color === 'string') {\n // Theme preset: expand to per-metric explicit stops respecting display ordering\n // (speed-like metrics are inverted so \"slow\" maps to the bad end of the ramp).\n const expanded = expandThemeToAllMetrics(color);\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = {\n ...updatedMetricConfig[metric],\n ...expanded[metric],\n };\n }\n } else {\n // Custom stops: apply same config to all targeted metrics.\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], color };\n }\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Configures extrusion height behaviour for one or more metrics.\n *\n * @param heightConfig - Height configuration.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the height config is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Apply to all metrics\n * module.setHeight({ maxHeightMeters: 100, minHeightMeters: 5 });\n *\n * // Apply only to specific metrics\n * module.setHeight({ scaleMode: 'currentRange', maxHeightMeters: 500 }, ['speed', 'freeFlowSpeed']);\n * ```\n */\n setHeight(heightConfig: AreaAnalyticsHeightConfig, metrics?: AreaAnalyticsMetricKey[]): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], height: heightConfig };\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Filters visible tiles by value range for one or more metrics.\n *\n * @param filter - Filter config. Pass `undefined` to clear.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the filter is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Filter all metrics\n * module.filter({ min: 20, max: 80 });\n *\n * // Filter only a specific metric\n * module.filter({ min: 50 }, ['congestionLevel']);\n *\n * // Clear filter for all metrics\n * module.filter(undefined);\n * ```\n */\n filter(filter?: AreaAnalyticsMetricFilter, metrics?: AreaAnalyticsMetricKey[]): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], filters: filter };\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyFiltersToLayers();\n this.emitConfigChange();\n }\n\n /**\n * Clears any active tile filter for one or more metrics.\n *\n * @param metrics - Optional list of metrics to clear. When omitted, clears filters for **all** metrics.\n */\n clearFilter(metrics?: AreaAnalyticsMetricKey[]): void {\n this.filter(undefined, metrics);\n }\n\n /**\n * Shows or hides all area analytics layers.\n */\n setVisible(visible: boolean): void {\n this.config = { ...this.config, visible };\n this.applyModeVisibility();\n this.emitConfigChange();\n }\n\n /**\n * Whether any area analytics (data) layer is currently visible.\n */\n isVisible(): boolean {\n return (\n this.sourcesWithLayers.heatmap.isAnyLayerVisible() ||\n this.sourcesWithLayers.hexgrid.isAnyLayerVisible() ||\n this.sourcesWithLayers.square.isAnyLayerVisible()\n );\n }\n\n /**\n * Returns the currently displayed data for all visualisation modes.\n */\n getShown() {\n return {\n heatmap: this.sourcesWithLayers.heatmap.shownFeatures,\n hexgrid: this.sourcesWithLayers.hexgrid.shownFeatures,\n square: this.sourcesWithLayers.square.shownFeatures,\n };\n }\n\n /**\n * Repositions analytics layers independently by layer type.\n *\n * Only the properties present in `layerConfig` are repositioned; omitted layer types are left in place.\n *\n * @param layerConfig - Per-layer-type positioning config. Each value is `'top'` or a `MapStyleLayerID`.\n */\n moveBeforeLayer(layerConfig: AreaAnalyticsBeforeLayerConfig): void {\n this._moveBeforeLayer(layerConfig);\n }\n\n private _moveBeforeLayer(layerConfig: AreaAnalyticsBeforeLayerConfig, updateConfig = true): void {\n const toId = (value: BeforeLayerConfig) => (value === 'top' ? undefined : mapStyleLayerIDs[value]);\n\n if (layerConfig.heatmap !== undefined) {\n this.mapLibreMap.moveLayer(this.heatmapLayerSpec.id, toId(layerConfig.heatmap));\n }\n\n if (layerConfig.hexgrid?.flat2D !== undefined) {\n this.mapLibreMap.moveLayer(this.hexFillLayerSpec.id, toId(layerConfig.hexgrid.flat2D));\n }\n\n if (layerConfig.hexgrid?.extrusion3D !== undefined) {\n this.mapLibreMap.moveLayer(this.hexExtrusionLayerSpec.id, toId(layerConfig.hexgrid.extrusion3D));\n }\n\n if (layerConfig.square?.flat2D !== undefined) {\n this.mapLibreMap.moveLayer(this.squareFillLayerSpec.id, toId(layerConfig.square.flat2D));\n }\n\n if (layerConfig.square?.extrusion3D !== undefined) {\n this.mapLibreMap.moveLayer(this.squareExtrusionLayerSpec.id, toId(layerConfig.square.extrusion3D));\n }\n\n if (updateConfig) {\n this.config = { ...this.config, beforeLayerConfig: layerConfig };\n }\n }\n\n /**\n * Event interface for the hexgrid and square layers.\n *\n * Supports feature-interaction events (`hover`, `click`, `contextmenu`, `long-hover`) as well\n * as module lifecycle events (`config-change`, `shown-features`) — all through the same\n * `events.on(type, handler)` surface.\n *\n * @example\n * ```typescript\n * module.events.on('hover', (feature, lngLat) => {\n * console.log(feature.properties.congestionLevel);\n * });\n *\n * const unsub = module.events.on('config-change', (config) => {\n * console.log('Config updated:', config);\n * });\n *\n * module.events.on('shown-features', (data) => {\n * console.log('Analytics data shown:', data);\n * });\n * ```\n */\n get events(): CombinedEvents<AreaAnalyticsTileFeature, TrafficAreaAnalyticsConfig, TrafficAreaAnalytics> {\n return new CombinedEvents(\n new UserEvents(\n this.tomtomMap._eventsProxy,\n [this.sourcesWithLayers.hexgrid, this.sourcesWithLayers.square, this.sourcesWithLayers.heatmap],\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n\n // ── Private helpers ──────────────────────────────────────────────\n\n private get mode(): AreaAnalyticsDisplayMode {\n return this.config?.displayMode ?? AREA_ANALYTICS_DEFAULTS.displayMode;\n }\n\n private get activeMetric(): AreaAnalyticsMetricKey {\n return getActiveMetric(this.config);\n }\n\n private applyModeVisibility(): void {\n if (this.config?.visible === false) {\n this.hideAllDataLayers();\n return;\n }\n\n const mode = this.mode;\n const hasHeatmapData = this.sourcesWithLayers.heatmap.shownFeatures.features.length > 0;\n const hasHexData = this.sourcesWithLayers.hexgrid.shownFeatures.features.length > 0;\n const hasSquareData = this.sourcesWithLayers.square.shownFeatures.features.length > 0;\n const hasRegionData = this.sourcesWithLayers.region.shownFeatures.features.length > 0;\n\n // Data layers (managed by GeoJSONSourceWithLayers)\n this.sourcesWithLayers.heatmap.setLayersVisible(mode === 'heatmap' && hasHeatmapData);\n this.sourcesWithLayers.hexgrid.setLayersVisible(\n mode === 'hexgrid-3d' && hasHexData,\n (layerSpec) => layerSpec.id === this.hexExtrusionLayerSpec.id,\n );\n this.sourcesWithLayers.hexgrid.setLayersVisible(\n mode === 'hexgrid-2d' && hasHexData,\n (layerSpec) => layerSpec.id === this.hexFillLayerSpec.id,\n );\n this.sourcesWithLayers.square.setLayersVisible(\n mode === 'square-3d' && hasSquareData,\n (layerSpec) => layerSpec.id === this.squareExtrusionLayerSpec.id,\n );\n this.sourcesWithLayers.square.setLayersVisible(\n mode === 'square-2d' && hasSquareData,\n (layerSpec) => layerSpec.id === this.squareFillLayerSpec.id,\n );\n this.sourcesWithLayers.region.setLayersVisible(hasRegionData);\n\n // Highlight layers (added directly to MapLibre, not tracked by GeoJSONSourceWithLayers).\n // Outlines are 2D-only — line layers render in the 2D pass and appear behind fill-extrusion prisms.\n // Extrusion highlights are 3D-only.\n this.setHighlightLayerVisible(this.hexOutlineLayerSpec.id, mode === 'hexgrid-2d' && hasHexData);\n this.setHighlightLayerVisible(this.hexExtrusionHighlightLayerSpec.id, mode === 'hexgrid-3d' && hasHexData);\n this.setHighlightLayerVisible(this.squareOutlineLayerSpec.id, mode === 'square-2d' && hasSquareData);\n this.setHighlightLayerVisible(this.squareExtrusionHighlightLayerSpec.id, mode === 'square-3d' && hasSquareData);\n }\n\n private hideAllDataLayers(): void {\n this.sourcesWithLayers.heatmap.setLayersVisible(false);\n this.sourcesWithLayers.hexgrid.setLayersVisible(false);\n this.sourcesWithLayers.square.setLayersVisible(false);\n this.sourcesWithLayers.region.setLayersVisible(false);\n this.setHighlightLayerVisible(this.hexOutlineLayerSpec.id, false);\n this.setHighlightLayerVisible(this.hexExtrusionHighlightLayerSpec.id, false);\n this.setHighlightLayerVisible(this.squareOutlineLayerSpec.id, false);\n this.setHighlightLayerVisible(this.squareExtrusionHighlightLayerSpec.id, false);\n }\n\n private addHighlightLayersToMap(hexgridSourceId: string, squareSourceId: string): void {\n const addHidden = (spec: { id: string; beforeID?: string; [key: string]: unknown }, sourceId: string) => {\n if (!this.mapLibreMap.getLayer(spec.id)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.mapLibreMap.addLayer({ ...spec, source: sourceId } as any, spec.beforeID);\n }\n this.mapLibreMap.setLayoutProperty(spec.id, 'visibility', 'none');\n };\n\n addHidden(this.hexOutlineLayerSpec, hexgridSourceId);\n addHidden(this.hexExtrusionHighlightLayerSpec, hexgridSourceId);\n addHidden(this.squareOutlineLayerSpec, squareSourceId);\n addHidden(this.squareExtrusionHighlightLayerSpec, squareSourceId);\n }\n\n private setHighlightLayerVisible(layerId: string, visible: boolean): void {\n if (this.mapLibreMap.getLayer(layerId)) {\n this.mapLibreMap.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');\n }\n }\n\n private applyLayerConfig(config: TrafficAreaAnalyticsConfig | undefined): void {\n const computedRange = this.lastAnalytics?.properties.ranges?.[this.activeMetric];\n\n const newHeatmapSpec = buildHeatmapLayerSpec(this.heatmapLayerSpec.id, config, computedRange);\n const newHexFillSpec = buildHexFillLayerSpec(this.hexFillLayerSpec.id, config, computedRange);\n const newHexExtrusionSpec = buildHexExtrusionLayerSpec(this.hexExtrusionLayerSpec.id, config, computedRange);\n const newHexExtrusionHighlightSpec = buildHexExtrusionHighlightLayerSpec(\n this.hexExtrusionHighlightLayerSpec.id,\n config,\n computedRange,\n );\n const newSquareFillSpec = buildSquareFillLayerSpec(this.squareFillLayerSpec.id, config, computedRange);\n const newSquareExtrusionSpec = buildSquareExtrusionLayerSpec(\n this.squareExtrusionLayerSpec.id,\n config,\n computedRange,\n );\n const newSquareExtrusionHighlightSpec = buildSquareExtrusionHighlightLayerSpec(\n this.squareExtrusionHighlightLayerSpec.id,\n config,\n computedRange,\n );\n\n changeLayerProps(newHeatmapSpec, this.heatmapLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexFillSpec, this.hexFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexExtrusionSpec, this.hexExtrusionLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexExtrusionHighlightSpec, this.hexExtrusionHighlightLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareFillSpec, this.squareFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionSpec, this.squareExtrusionLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionHighlightSpec, this.squareExtrusionHighlightLayerSpec, this.mapLibreMap);\n\n this.heatmapLayerSpec = newHeatmapSpec;\n this.hexFillLayerSpec = newHexFillSpec;\n this.hexExtrusionLayerSpec = newHexExtrusionSpec;\n this.hexExtrusionHighlightLayerSpec = newHexExtrusionHighlightSpec;\n this.squareFillLayerSpec = newSquareFillSpec;\n this.squareExtrusionLayerSpec = newSquareExtrusionSpec;\n this.squareExtrusionHighlightLayerSpec = newSquareExtrusionHighlightSpec;\n\n this.applyFiltersToLayers();\n }\n\n private applyRegionConfig(config: TrafficAreaAnalyticsConfig | undefined): void {\n const newRegionFillSpec = buildRegionFillLayerSpec(this.regionFillLayerSpec.id, config);\n const newRegionLineSpec = buildRegionLineLayerSpec(this.regionLineLayerSpec.id, config);\n\n changeLayerProps(newRegionFillSpec, this.regionFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newRegionLineSpec, this.regionLineLayerSpec, this.mapLibreMap);\n\n this.regionFillLayerSpec = newRegionFillSpec;\n this.regionLineLayerSpec = newRegionLineSpec;\n\n // Re-apply region geometry in case `inverted` changed after data was already shown.\n if (this.lastAnalytics) {\n const inverted = config?.regionPolygon?.inverted ?? AREA_ANALYTICS_DEFAULTS.regionPolygon.inverted;\n this.sourcesWithLayers.region.show(this.buildRegionFC(this.lastAnalytics.features, inverted));\n }\n }\n\n private applyFiltersToLayers(): void {\n const metric = this.activeMetric;\n const filter = this.config?.metricConfig?.[metric]?.filters;\n\n const layerIds = [\n this.hexFillLayerSpec.id,\n this.hexExtrusionLayerSpec.id,\n this.squareFillLayerSpec.id,\n this.squareExtrusionLayerSpec.id,\n ];\n\n if (!filter || (filter.min === undefined && filter.max === undefined)) {\n for (const layerId of layerIds) {\n this.mapLibreMap.setFilter(layerId, null);\n }\n return;\n }\n\n const conditions: ExpressionSpecification[] = [];\n if (filter.min !== undefined) conditions.push(['>=', ['get', metric], filter.min]);\n if (filter.max !== undefined) conditions.push(['<=', ['get', metric], filter.max]);\n\n const filterExpr: ExpressionSpecification = conditions.length === 1 ? conditions[0] : ['any', ...conditions];\n\n for (const layerId of layerIds) {\n this.mapLibreMap.setFilter(layerId, filterExpr);\n }\n }\n\n private buildRegionFC(features: TrafficAreaAnalytics['features'], inverted: boolean): FeatureCollection {\n return {\n type: 'FeatureCollection',\n features: features.map((feature, i) => {\n const id = `traffic-area-analytics-region-${i}`;\n const geometry = inverted ? mask(feature as any).geometry : feature.geometry;\n return { ...feature, id, geometry, properties: { id } };\n }),\n };\n }\n}\n","import { indexedMagnitudes } from '@tomtom-org/maps-sdk/core';\nimport { isNil } from 'lodash-es';\nimport type {\n ExpressionFilterSpecification,\n FilterSpecification,\n LayerSpecification,\n LegacyFilterSpecification,\n Map,\n} from 'maplibre-gl';\nimport type { MultiSyntaxFilter, ValuesFilter } from '../../shared';\nimport { buildValuesFilter, getMergedAllFilter, getMergedAnyFilter } from '../../shared/mapLibreFilterUtils';\nimport type { TrafficCommonFilter } from '../types/trafficCommonConfig';\nimport type { TrafficFlowFilter, TrafficFlowFilters } from '../types/trafficFlowConfig';\nimport type { DelayFilter, TrafficIncidentsFilter, TrafficIncidentsFilters } from '../types/trafficIncidentsConfig';\nimport { incidentToIconCategoryMapping } from '../util/trafficIncidentMapping';\n\nconst toMultiSyntaxAllFilter = (\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n): MultiSyntaxFilter | null => {\n if (!newSyntaxExpressions.length) {\n return null;\n }\n if (newSyntaxExpressions.length === 1) {\n return {\n expression: newSyntaxExpressions[0] as ExpressionFilterSpecification,\n legacy: legacySyntaxExpressions[0] as LegacyFilterSpecification,\n };\n }\n return {\n expression: ['all', ...newSyntaxExpressions] as ExpressionFilterSpecification,\n legacy: ['all', ...legacySyntaxExpressions] as LegacyFilterSpecification,\n };\n};\n\nconst delayFilterToMapLibre = (delayFilter: DelayFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions = [];\n const legacySyntaxExpressions = [];\n if (delayFilter.mustHaveDelay && delayFilter.minDelayMinutes) {\n // there must be a delay and with the min specified value:\n const delaySeconds = delayFilter.minDelayMinutes * 60;\n newSyntaxExpressions.push(['>=', ['get', 'delay'], delaySeconds]);\n legacySyntaxExpressions.push(['>=', 'delay', delaySeconds]);\n } else if (delayFilter.mustHaveDelay) {\n // just expects a delay of any kind\n newSyntaxExpressions.push(['>', ['get', 'delay'], 0]);\n legacySyntaxExpressions.push(['>', 'delay', 0]);\n } else if (delayFilter.minDelayMinutes) {\n // Min delay expected, but also allows for non-existing delays:\n const delaySeconds = delayFilter.minDelayMinutes * 60;\n newSyntaxExpressions.push([\n 'any',\n ['!', ['has', 'delay']],\n ['==', ['get', 'delay'], 0],\n ['>=', ['get', 'delay'], delaySeconds],\n ]);\n legacySyntaxExpressions.push(['any', ['!has', 'delay'], ['==', 'delay', 0], ['>=', 'delay', delaySeconds]]);\n }\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\nconst addFilter = (\n filter: MultiSyntaxFilter | undefined | null,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n) => {\n if (filter) {\n newSyntaxExpressions.push(filter.expression);\n legacySyntaxExpressions.push(filter.legacy);\n }\n};\n\nconst addValuesFilter = (\n valuesFilter: ValuesFilter<string> | undefined,\n propName: string,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n) => {\n if (valuesFilter) {\n addFilter(buildValuesFilter(propName, valuesFilter), newSyntaxExpressions, legacySyntaxExpressions);\n }\n};\n\nconst addCommonFilterExpressions = (\n sdkFilter: TrafficCommonFilter,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n): void => {\n addValuesFilter(sdkFilter.roadCategories, 'road_category', newSyntaxExpressions, legacySyntaxExpressions);\n addValuesFilter(sdkFilter.roadSubCategories, 'road_subcategory', newSyntaxExpressions, legacySyntaxExpressions);\n};\n\nconst buildMapLibreIncidentsFilter = (sdkFilter: TrafficIncidentsFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions: unknown[] = [];\n const legacySyntaxExpressions: unknown[] = [];\n\n addCommonFilterExpressions(sdkFilter, newSyntaxExpressions, legacySyntaxExpressions);\n\n if (sdkFilter.incidentCategories) {\n const incidentCategoryFilter = buildValuesFilter(\n 'icon_category_0',\n sdkFilter.incidentCategories,\n (value) => incidentToIconCategoryMapping[value],\n );\n addFilter(incidentCategoryFilter, newSyntaxExpressions, legacySyntaxExpressions);\n }\n if (sdkFilter.magnitudes) {\n const magnitudesFilter = buildValuesFilter('magnitude_of_delay', sdkFilter.magnitudes, (magnitude) =>\n indexedMagnitudes.indexOf(magnitude),\n );\n addFilter(magnitudesFilter, newSyntaxExpressions, legacySyntaxExpressions);\n }\n if (sdkFilter.delays) {\n addFilter(delayFilterToMapLibre(sdkFilter.delays), newSyntaxExpressions, legacySyntaxExpressions);\n }\n\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\n/**\n * @ignore\n */\nexport const buildMapLibreIncidentFilters = (incidentFilters: TrafficIncidentsFilters): MultiSyntaxFilter | null => {\n if (!incidentFilters?.any?.length) {\n return null;\n }\n const mapLibreFilters = incidentFilters.any\n .map(buildMapLibreIncidentsFilter)\n .filter((mapLibreFilter) => !isNil(mapLibreFilter));\n return getMergedAnyFilter(mapLibreFilters);\n};\n\nconst buildMapLibreFlowFilter = (sdkFilter: TrafficFlowFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions: unknown[] = [];\n const legacySyntaxExpressions: unknown[] = [];\n\n addCommonFilterExpressions(sdkFilter, newSyntaxExpressions, legacySyntaxExpressions);\n if (sdkFilter.showRoadClosures) {\n const operator = sdkFilter.showRoadClosures === 'only' ? '==' : '!=';\n newSyntaxExpressions.push([operator, ['get', 'road_closure'], true]);\n legacySyntaxExpressions.push([operator, 'road_closure', true]);\n }\n\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\n/**\n * @ignore\n */\nexport const buildMapLibreFlowFilters = (flowFilters: TrafficFlowFilters): MultiSyntaxFilter | null => {\n if (!flowFilters?.any?.length) {\n return null;\n }\n const mapLibreFilters = flowFilters.any\n .map(buildMapLibreFlowFilter)\n .filter((mapLibreFilter) => !isNil(mapLibreFilter));\n return getMergedAnyFilter(mapLibreFilters);\n};\n\n/**\n * @ignore\n * @param filter\n * @param layers\n * @param mapLibreMap\n * @param originalFilters\n */\nexport const applyFilter = (\n filter: MultiSyntaxFilter | undefined,\n layers: LayerSpecification[],\n mapLibreMap: Map,\n originalFilters: Record<string, FilterSpecification | undefined>,\n) => {\n for (const layer of layers) {\n mapLibreMap.setFilter(\n layer.id,\n filter ? getMergedAllFilter(filter, originalFilters[layer.id]) : originalFilters[layer.id],\n );\n }\n};\n","import { isNil, omitBy } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport type { LayerSpecWithSource } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n filterLayersBySources,\n ModuleEvents,\n StyleSourceWithLayers,\n TRAFFIC_FLOW_SOURCE_ID,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyFilter, buildMapLibreFlowFilters } from './filters/trafficFilters';\nimport type { FlowConfig, TrafficFlowFilters } from './types/trafficFlowConfig';\nimport type { TrafficFlowModuleFeature } from './types/trafficFlowFeature';\nimport { trafficFlowMapping } from './util/trafficFlowMapping';\n\n/**\n * IDs of sources and layers for traffic flow module.\n */\ntype TrafficFlowSourcesWithLayers = {\n trafficFlow: StyleSourceWithLayers;\n};\n\n/**\n * Traffic Flow Module for displaying and configuring real-time traffic flow information on the map.\n *\n * This module controls the vector tile traffic flow layers that visualize current\n * traffic speed conditions using color-coded road segments.\n *\n * @remarks\n * **Features:**\n * - Toggle traffic flow visibility on/off\n * - Filter by road categories and types\n * - Color-coded speed visualization (green = free flow, red = congestion)\n * - Real-time traffic data from vector tiles\n * - Filter road closures\n *\n * **Visual Representation:**\n * - Green: Free-flowing traffic\n * - Yellow/Orange: Slow traffic\n * - Red: Heavy congestion\n * - Dark gray: Road closures\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { TrafficFlowModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module (auto-add to style if needed)\n * const trafficFlow = await TrafficFlowModule.get(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * trafficFlow.setVisible(false);\n * trafficFlow.setVisible(true);\n * ```\n *\n * @example\n * Filter by road type:\n * ```typescript\n * // Show only highway traffic\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk']\n * }\n * }]\n * });\n *\n * // Hide local streets\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'all_except',\n * values: ['street']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Show only road closures:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * showRoadClosures: 'only'\n * }]\n * });\n * ```\n *\n * @see [Traffic Flow Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-flow)\n * @see [Traffic Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic)\n *\n * @group Traffic Flow\n */\nexport class TrafficFlowModule extends AbstractMapModule<TrafficFlowSourcesWithLayers, FlowConfig> {\n private originalFilters!: Record<string, FilterSpecification | undefined>;\n\n /**\n * Retrieves a TrafficFlowModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization, visibility, and filters.\n *\n * @returns A promise that resolves to the initialized TrafficFlowModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `ensureAddedToStyle`: Auto-add traffic flow to style if missing\n * - `filters`: Road category and type filters\n *\n * **Style Requirement:**\n * Traffic flow must be included in the map style or added via `ensureAddedToStyle`.\n *\n * @throws Error if traffic flow source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const trafficFlowModule = await TrafficFlowModule.get(map);\n * ```\n *\n * @example\n * Auto-add to style:\n * ```typescript\n * const trafficFlowModule = await TrafficFlowModule.get(map, {\n * visible: true,\n * filters: {\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk', 'primary']\n * }\n * }]\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: FlowConfig): Promise<TrafficFlowModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, TRAFFIC_FLOW_SOURCE_ID, 'trafficFlow');\n return new TrafficFlowModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: FlowConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const flowSource = this.mapLibreMap.getSource(TRAFFIC_FLOW_SOURCE_ID);\n if (!flowSource) {\n throw notInTheStyle(`init ${TrafficFlowModule.name} with source ID ${TRAFFIC_FLOW_SOURCE_ID}`);\n }\n this.originalFilters = {};\n for (const layer of this.getLayers()) {\n this.originalFilters[layer.id] = layer.filter;\n }\n return { trafficFlow: new StyleSourceWithLayers(this.mapLibreMap, flowSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: FlowConfig | undefined) {\n this._setVisible(config?.visible ?? false, false);\n this._filter(config?.filters, false);\n return config;\n }\n\n private getLayers(): LayerSpecWithSource[] {\n return filterLayersBySources(this.tomtomMap.mapLibreMap, [TRAFFIC_FLOW_SOURCE_ID]);\n }\n\n /**\n * Applies filters to traffic flow display.\n *\n * @param filters - Filter configuration for road types, categories, and closures.\n * Pass `undefined` to reset to defaults (show all).\n *\n * @remarks\n * **Filter Options:**\n * - `roadCategories`: Filter by road importance (motorway, trunk, primary, etc.)\n * - `roadSubCategories`: Filter by specific street types\n * - `showRoadClosures`: Show only closures or exclude them\n *\n * **Available Road Categories:**\n * - `motorway`: Major highways\n * - `trunk`: Major roads\n * - `primary`: Primary roads\n * - `secondary`: Secondary roads\n * - `tertiary`: Tertiary roads\n * - `street`: Local streets\n *\n * **Filter Logic:**\n * Uses \"any\" (OR) logic - traffic matching any filter is shown.\n *\n * @example\n * Show only major roads:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk', 'primary']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Hide street-level traffic:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'all_except',\n * values: ['street']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Multiple filter criteria:\n * ```typescript\n * trafficFlow.filter({\n * any: [\n * {\n * roadCategories: { show: 'only', values: ['motorway'] }\n * },\n * {\n * showRoadClosures: 'only'\n * }\n * ]\n * });\n * ```\n *\n * @example\n * Reset filters:\n * ```typescript\n * trafficFlow.filter(undefined);\n * ```\n */\n filter(filters?: TrafficFlowFilters) {\n this._filter(filters);\n }\n\n private _filter(filters: TrafficFlowFilters | undefined, updateConfig = true) {\n if (this.tomtomMap.mapReady) {\n if (filters?.any?.length) {\n const filterExpression = buildMapLibreFlowFilters(filters);\n if (filterExpression) {\n applyFilter(filterExpression, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n } else if (this.config?.filters?.any?.length) {\n applyFilter(undefined, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n }\n\n if (updateConfig) {\n this.config = omitBy({ ...this.config, filters }, isNil);\n this.emitConfigChange();\n }\n }\n\n /**\n * Sets the visibility of traffic flow layers.\n *\n * @param visible - `true` to show traffic flow, `false` to hide it.\n *\n * @example\n * ```typescript\n * trafficFlow.setVisible(true); // Show traffic\n * trafficFlow.setVisible(false); // Hide traffic\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficFlow.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Returns if any layer for traffic flow is visible or not.\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.trafficFlow.isAnyLayerVisible();\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible traffic flow features.\n *\n * @remarks\n * Returns all traffic flow features currently rendered in the visible map area.\n * These represent road segments with real-time traffic speed information.\n *\n * **Feature Properties:**\n * Properties depend on the traffic flow vector tile schema and may include:\n * - Current speed\n * - Free flow speed\n * - Road category\n * - Congestion level\n * - Road closure status\n *\n * @example\n * ```typescript\n * const shown = trafficFlow.getShown();\n * console.log(`Visible flow segments: ${shown.trafficFlow.length}`);\n * shown.trafficFlow.forEach(segment => {\n * console.log('Road segment:', segment.properties);\n * });\n * ```\n *\n * @example\n * Analyze congestion in viewport:\n * ```typescript\n * const shown = trafficFlow.getShown();\n * const congested = shown.trafficFlow.filter(\n * segment => segment.properties.speed < segment.properties.freeFlowSpeed * 0.5\n * );\n * console.log(`Congested segments: ${congested.length}`);\n * ```\n */\n getShown(): { trafficFlow: TrafficFlowModuleFeature[] } {\n return {\n trafficFlow: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.trafficFlow.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(trafficFlowMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficFlowModuleFeature, FlowConfig, never> {\n return new CombinedEvents(\n new UserEvents<TrafficFlowModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.trafficFlow,\n this.config?.events,\n trafficFlowMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","/**\n * Direction chevron sprite for {@link TrafficIncidentOverlayModule}.\n *\n * A single right-pointing chevron, rasterised from SVG and registered via\n * `map.addImage()`. Referenced as a `line-pattern` on a line layer stacked on top\n * of `-inner-solid` — transparent pixels let the magnitude colour show through,\n * opaque strokes paint the direction indicator.\n *\n * Because REST LineStrings arrive in travel order, MapLibre tiles the pattern\n * along that direction automatically; no per-feature rotation needed. A dark halo\n * under the white stroke keeps the chevron legible on all three jam tints\n * (yellow `minor`, orange `moderate`, red `major`).\n *\n * @module\n * @ignore\n */\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { parseSvg } from '../../shared/resources';\n\nexport const INCIDENT_DIRECTION_CHEVRON_IMAGE_ID = 'tt-traffic-incident-direction-chevron';\n\n/**\n * Rasterised chevron, memoised. Returns `undefined` in non-DOM environments\n * (node-only unit tests); callers should skip sprite registration in that case.\n * @ignore\n */\nexport const getIncidentDirectionChevronImage = (): HTMLImageElement | undefined => {\n if (!isDOMImageSupported()) return undefined;\n if (!cachedImg) cachedImg = svgToImg(parseSvg(svgMarkup));\n return cachedImg;\n};\n\nconst svgMarkup = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"18\" viewBox=\"0 0 48 18\">\n<path d=\"M 18 4 L 30 9 L 18 14\" fill=\"none\" stroke=\"rgba(20, 30, 50, 0.5)\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M 18 4 L 30 9 L 18 14\" fill=\"none\" stroke=\"white\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>`;\n\nlet cachedImg: HTMLImageElement | undefined;\n","/**\n * Read per-magnitude incident colours from the loaded MapLibre style. Missing layers fall\n * back silently; layers present with a non-string `line-color` (match/case/etc.) fall back\n * and warn once — the author likely intended an override we can't evaluate here.\n *\n * @module\n * @ignore\n */\n\nimport type { Map as MapLibreMap } from 'maplibre-gl';\n\nconst MAGNITUDES = ['unknown', 'minor', 'moderate', 'major', 'indefinite'] as const;\ntype Magnitude = (typeof MAGNITUDES)[number];\n\n/** @ignore */\nexport type IncidentPalette = {\n outline: Record<Magnitude, string>;\n inner: Record<Magnitude, string>;\n};\n\ntype LayerIdPair = { outline: string; inner: string };\n\n// Layer IDs we pull colours from. Update when the loaded style renames them.\nconst LAYER_IDS: Record<Magnitude, LayerIdPair> = {\n unknown: { outline: 'TrafficIncidents - No delay outline', inner: 'TrafficIncidents - No delay pattern' },\n minor: { outline: 'TrafficIncidents - Minor jam outline', inner: 'TrafficIncidents - Minor jam' },\n moderate: { outline: 'TrafficIncidents - Moderate jam outline', inner: 'TrafficIncidents - Moderate jam' },\n major: { outline: 'TrafficIncidents - Major jam outline', inner: 'TrafficIncidents - Major jam' },\n indefinite: { outline: 'TrafficIncidents - Closed road outline', inner: 'TrafficIncidents - Closed road pattern' },\n};\n\n/** @ignore */\nexport const FALLBACK_INCIDENT_PALETTE: IncidentPalette = {\n outline: {\n unknown: 'hsl(198, 20%, 54%)',\n minor: 'hsl(45, 85%, 36%)',\n moderate: 'hsl(9, 87%, 31%)',\n major: 'hsl(0, 100%, 17%)',\n indefinite: 'hsl(0, 100%, 34%)',\n },\n inner: {\n unknown: 'hsl(198, 20%, 90%)',\n minor: 'hsl(45, 100%, 51%)',\n moderate: 'hsl(9, 97%, 51%)',\n major: 'hsl(0, 100%, 34%)',\n indefinite: 'hsl(197, 28%, 95%)',\n },\n};\n\n/** @ignore */\nexport const readIncidentPalette = (map: Pick<MapLibreMap, 'getStyle'>): IncidentPalette => {\n const layers = map.getStyle()?.layers ?? [];\n const byId = new Map<string, unknown>();\n for (const layer of layers) {\n byId.set(layer.id, layer);\n }\n\n const palette: IncidentPalette = {\n outline: { ...FALLBACK_INCIDENT_PALETTE.outline },\n inner: { ...FALLBACK_INCIDENT_PALETTE.inner },\n };\n const nonLiteralLayerIds: string[] = [];\n\n for (const magnitude of MAGNITUDES) {\n const ids = LAYER_IDS[magnitude];\n const outline = readLineColor(byId.get(ids.outline), ids.outline, nonLiteralLayerIds);\n const inner = readLineColor(byId.get(ids.inner), ids.inner, nonLiteralLayerIds);\n if (outline) palette.outline[magnitude] = outline;\n if (inner) palette.inner[magnitude] = inner;\n }\n\n if (nonLiteralLayerIds.length > 0) {\n console.warn(\n '[TrafficIncidentOverlayModule] style layers have non-literal `line-color` ' +\n '(match/case/etc.); falling back to default colour for: ' +\n nonLiteralLayerIds.join(', '),\n );\n }\n\n return palette;\n};\n\nconst readLineColor = (layer: unknown, layerId: string, nonLiteralSink: string[]): string | undefined => {\n if (!layer || typeof layer !== 'object') return undefined;\n const paint = (layer as { paint?: unknown }).paint;\n if (!paint || typeof paint !== 'object') return undefined;\n const color = (paint as { 'line-color'?: unknown })['line-color'];\n if (typeof color === 'string') return color;\n if (color !== undefined) nonLiteralSink.push(layerId);\n return undefined;\n};\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport { INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from '../util/incidentDirectionChevron';\nimport { FALLBACK_INCIDENT_PALETTE, type IncidentPalette } from '../util/readIncidentPalette';\n\n/**\n * Incident rendering for {@link TrafficIncidentOverlayModule}. All road-class-driven geometry\n * is dropped: REST features carry no `road_category`, `road_subcategory`, `display_class`, or\n * `point_type`, so per-road-class widths and offsets would be uniform approximations\n * pretending to be precise. Incidents render as a uniform stripe centred on the road —\n * callers wanting full per-road-class styling can use the vector-tile\n * `TrafficIncidentsModule` instead.\n *\n * Per-magnitude colours come from a palette passed in (see\n * {@link ../util/readIncidentPalette}). Each factory bakes `beforeID` into every layer spec.\n *\n * @module incidentDetailsLayers\n * @ignore\n */\n\ntype LineLayerWithBefore = Omit<LineLayerSpecification, 'source'> & { beforeID?: string };\ntype SymbolLayerWithBefore = Omit<SymbolLayerSpecification, 'source'> & { beforeID?: string };\n\n// --- Line layers ---------------------------------------------------------------------------\n\n// Zoom interpolations collapsed from canonical's `primary` road-class path. Canonical ramps\n// widths up by road class (motorway > trunk > primary > ... > street); we don't have road\n// class, so every incident renders at the primary-road width.\nconst INNER_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 0,\n 9,\n 0.96,\n 13,\n 2.1,\n 19,\n 10.8,\n];\nconst OUTLINE_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 0.75,\n 9,\n 1.96,\n 13,\n 3.6,\n 19,\n 13.8,\n];\n\n// Default case uses `major` so an unexpected magnitude is loud, not silent.\nconst buildColorExpr = (colors: IncidentPalette['outline']): ExpressionSpecification => [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n colors.unknown,\n 'minor',\n colors.minor,\n 'moderate',\n colors.moderate,\n 'major',\n colors.major,\n 'indefinite',\n colors.indefinite,\n colors.major,\n];\n\n// Pattern sprites for magnitudes with no solid colour (no-delay stripe, closed-road stripe).\nconst INNER_PATTERN: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n 'traffic-incidents-no_delay-pattern',\n 'indefinite',\n 'traffic-incidents-road_closed-pattern',\n '',\n];\n\n// Focus treatment: pop focused features, leave everything else as-is.\n// - `focused=null` (no focus active): every feature renders at full strength.\n// - `focused=true`: wider stripe + outline painted beneath (when focus styling is enabled).\n// - `focused=false`: identical to `focused=null` — unfocused features are not dimmed.\n//\n// Defaults are applied via `resolveFocusStyle` and can be overridden / disabled via the\n// module's `focus` config.\nconst DEFAULT_FOCUS_OUTLINE_COLOR = '#000';\nconst DEFAULT_FOCUS_WIDTH_SCALE = 1.6;\n\ntype ResolvedFocus = { outlineColor: string; widthScale: number };\n\n/**\n * Resolve the user's `focus` config into an internal shape:\n * `false` → `null` (no halo layer, no width pop, no feature-state expressions on width)\n * `undefined` → defaults\n * object → defaults merged with overrides\n * @ignore\n */\nexport const resolveFocusStyle = (\n focus: false | { outlineColor?: string; widthScale?: number } | undefined,\n): ResolvedFocus | null => {\n if (focus === false) return null;\n return {\n outlineColor: focus?.outlineColor ?? DEFAULT_FOCUS_OUTLINE_COLOR,\n widthScale: focus?.widthScale ?? DEFAULT_FOCUS_WIDTH_SCALE,\n };\n};\n\n// Halo spec — a sharp outline painted behind the magnitude outline so it reads as a\n// border around the focused stripe. Width tracks the focused outline plus a small constant\n// offset; no blur, so the edge is crisp.\nconst HALO_BLUR = 0;\nconst HALO_OPACITY = 1;\nconst HALO_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 2.2,\n 9,\n 4.6,\n 13,\n 8.3,\n 19,\n 26,\n];\n\n// Halo opacity: full on focused features, zero elsewhere (MapLibre can't filter on feature-state\n// at the filter level for line layers, so we hide via paint instead).\nconst haloOpacity: ExpressionSpecification = ['case', ['==', ['feature-state', 'focused'], true], HALO_OPACITY, 0];\n\n// MapLibre allows at most ONE zoom-based `interpolate`/`step` in the entire expression tree for\n// a given paint property. A `case` branching between two `interpolate` expressions counts as\n// two, which is illegal. The legal pattern is a single outer `interpolate` whose *stop values*\n// (not the zoom axis) contain the feature-state case — per-feature values inside stop outputs\n// are evaluated at render time and are not themselves zoom-based sub-expressions.\n//\n// focusWidth rewrites ['interpolate', curve, ['zoom'], z0, v0, z1, v1, ...]\n// into ['interpolate', curve, ['zoom'], z0, case(v0*s|v0), z1, case(v1*s|v1), ...]\n// `scale === null` returns the original expression unmodified — used when the focus\n// visual is disabled, so widths don't carry a feature-state expression at all.\nconst focusWidth = (interp: ExpressionSpecification, scale: number | null): ExpressionSpecification => {\n if (scale === null) return interp;\n const [op, curve, input, ...stops] = interp as unknown as [\n string,\n ExpressionSpecification,\n ExpressionSpecification,\n ...(number | ExpressionSpecification)[],\n ];\n const focusedStops = stops.map((v, i) =>\n i % 2 === 1\n ? ([\n 'case',\n ['==', ['feature-state', 'focused'], true],\n (v as number) * scale,\n v,\n ] as ExpressionSpecification)\n : v,\n );\n return [op, curve, input, ...focusedStops] as unknown as ExpressionSpecification;\n};\n\nconst SOLID_FILTER: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['minor', 'moderate', 'major']],\n];\nconst PATTERN_FILTER: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['unknown', 'indefinite']],\n];\n\n/**\n * 4–5 line layers covering all five delay magnitudes plus an optional focus halo:\n * - focus-halo (only emitted when `focus !== null`; visible only on feature-state.focused=true)\n * - outline (all magnitudes, colour via match)\n * - inner solid colour (minor/moderate/major)\n * - inner direction chevron (minor/moderate/major — white chevron pattern on top of solid,\n * stretches with line width so it's visible at all zooms where the stripe itself is visible)\n * - inner pattern (unknown → no-delay stripes, indefinite → road-closed stripes)\n *\n * Replaces canonical's 10 per-magnitude layer pairs; uses match-on-magnitude expressions\n * inside paint rather than duplicating layers. The chevron layer supersedes the canonical\n * per-feature `roads-arrow` symbol (which only activates at z15+ and drops out on short\n * LineStrings because sprite spacing is absolute-pixel). `beforeID` is applied to every layer.\n *\n * `focus === null` (caller passed `focus: false` in the module config) drops the halo layer\n * entirely and leaves widths free of any feature-state expression — `setFocus()` still\n * writes `feature-state.focused`, but no layer reads it.\n * @ignore\n */\nexport const buildCanonicalIncidentLineLayers = (\n sourceId: string,\n beforeID: string | undefined,\n palette: IncidentPalette = FALLBACK_INCIDENT_PALETTE,\n focus: ResolvedFocus | null = resolveFocusStyle(undefined),\n): LineLayerWithBefore[] => {\n const widthScale = focus?.widthScale ?? null;\n const layers: LineLayerWithBefore[] = [];\n if (focus !== null) {\n layers.push({\n // Outline halo. Painted FIRST so it sits beneath the magnitude outline and\n // shows as a border around it; invisible for non-focused features via\n // `line-opacity: 0`.\n id: `${sourceId}-focus-halo`,\n type: 'line',\n minzoom: 6,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': focus.outlineColor,\n 'line-width': HALO_WIDTH,\n 'line-blur': HALO_BLUR,\n 'line-opacity': haloOpacity,\n },\n beforeID,\n });\n }\n layers.push(\n {\n id: `${sourceId}-outline`,\n type: 'line',\n minzoom: 6,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': buildColorExpr(palette.outline),\n 'line-width': focusWidth(OUTLINE_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n id: `${sourceId}-inner-solid`,\n type: 'line',\n minzoom: 6,\n filter: SOLID_FILTER,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': buildColorExpr(palette.inner),\n 'line-width': focusWidth(INNER_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n // `line-cap: butt` so the rounded end-caps of `-inner-solid` aren't re-stamped by the\n // pattern (which would bleed the chevron into the cap).\n id: `${sourceId}-inner-chevron`,\n type: 'line',\n minzoom: 12,\n filter: SOLID_FILTER,\n layout: { 'line-cap': 'butt', 'line-join': 'round' },\n paint: {\n 'line-pattern': INCIDENT_DIRECTION_CHEVRON_IMAGE_ID,\n 'line-width': focusWidth(INNER_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n id: `${sourceId}-inner-pattern`,\n type: 'line',\n minzoom: 6,\n filter: PATTERN_FILTER,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: { 'line-pattern': INNER_PATTERN, 'line-width': focusWidth(INNER_WIDTH, widthScale) },\n beforeID,\n },\n );\n return layers;\n};\n\n// --- Symbol layers ------------------------------------------------------------------------\n\nconst ICON_SIZE: ExpressionSpecification = ['step', ['zoom'], 0.7, 14, 0.8, 16, 0.9, 18, 1];\nconst JAM_TEXT_SIZE: ExpressionSpecification = ['step', ['zoom'], 12.6, 14, 14.4, 16, 16.2, 18, 18];\n\n// Non-jam, non-closed-road incidents map category → sprite. `''` suppresses rendering for\n// categories with no canonical sprite (jam — handled by jam-marker; animals-on-road,\n// narrow-lanes — canonical omits them).\nconst INCIDENT_MARKER_ICON: ExpressionSpecification = [\n 'match',\n ['get', 'category'],\n 'other',\n 'traffic-incidents-info',\n 'accident',\n 'traffic-incidents-accident',\n 'fog',\n 'traffic-incidents-fog',\n 'danger',\n 'traffic-incidents-roadworks',\n 'rain',\n 'traffic-incidents-rain',\n 'frost',\n 'traffic-incidents-frost',\n 'lane-closed',\n 'traffic-incidents-lane_closed',\n 'roadworks',\n 'traffic-incidents-roadworks',\n 'wind',\n 'traffic-incidents-wind',\n 'flooding',\n 'traffic-incidents-flooding',\n 'broken-down-vehicle',\n 'traffic-incidents-broken_down_vehicle',\n '',\n];\n\n// Canonical declutter: hide short-delay incidents until the user zooms in further. Hides\n// indefinite (road closed) — those are drawn by the closed-road-marker layer.\nconst INCIDENT_MARKER_FILTER: ExpressionSpecification = [\n 'all',\n ['!=', ['get', 'magnitudeOfDelay'], 'indefinite'],\n [\n 'any',\n ['!', ['has', 'delayInSeconds']],\n [\n 'step',\n ['zoom'],\n ['>', ['get', 'delayInSeconds'], 900],\n 14,\n ['>', ['get', 'delayInSeconds'], 600],\n 15,\n ['>', ['get', 'delayInSeconds'], 300],\n 16,\n true,\n ],\n ],\n];\n\nconst JAM_MARKER_FILTER: ExpressionSpecification = [\n 'all',\n ['==', ['get', 'category'], 'jam'],\n ['!=', ['get', 'magnitudeOfDelay'], 'unknown'],\n ['!=', ['get', 'magnitudeOfDelay'], 'indefinite'],\n [\n 'step',\n ['zoom'],\n ['>', ['get', 'delayInSeconds'], 900],\n 13,\n ['>', ['get', 'delayInSeconds'], 600],\n 14,\n ['>', ['get', 'delayInSeconds'], 300],\n 15,\n true,\n ],\n];\n\nconst CLOSED_ROAD_FILTER: ExpressionSpecification = ['==', ['get', 'magnitudeOfDelay'], 'indefinite'];\n\n// Jam sprite: `traffic-jam-<magnitude>-<size>`. Size suffix picks a sprite wide enough to fit\n// the delay text rendered alongside (~<10 min, <1 hr, ≥1 hr). Simplified from canonical's\n// ~25-line size selector — the visual outcome is the same.\nconst JAM_ICON: ExpressionSpecification = [\n 'concat',\n 'traffic-jam-',\n ['get', 'magnitudeOfDelay'],\n [\n 'case',\n ['<', ['get', 'delayInSeconds'], 600],\n '-small',\n ['<', ['get', 'delayInSeconds'], 3600],\n '-medium',\n '-large',\n ],\n];\n\n// Canonical delay formatting: \"1 min\" / \"X min\" / \"Y hr Z min\" (drops \" 0 min\" on exact hours).\nconst JAM_TEXT: ExpressionSpecification = [\n 'case',\n ['<=', ['get', 'delayInSeconds'], 60],\n '1 min',\n ['>=', ['get', 'delayInSeconds'], 3600],\n [\n 'concat',\n ['to-string', ['floor', ['/', ['to-number', ['get', 'delayInSeconds']], 3600]]],\n ' hr ',\n [\n 'case',\n ['==', ['%', ['round', ['/', ['to-number', ['get', 'delayInSeconds']], 60]], 60], 0],\n '',\n [\n 'concat',\n ['to-string', ['round', ['%', ['/', ['to-number', ['get', 'delayInSeconds']], 60], 60]]],\n ' min',\n ],\n ],\n ],\n ['concat', ['round', ['/', ['get', 'delayInSeconds'], 60]], ' min'],\n];\n\n// Worst-delay incidents sort highest (top). Canonical adds a magnitude tiebreaker (index 0-4);\n// dropped because delayInSeconds dominates by orders of magnitude.\nconst SORT_KEY: ExpressionSpecification = ['case', ['has', 'delayInSeconds'], ['*', -1, ['get', 'delayInSeconds']], 0];\n\n/**\n * 3 symbol layers in z-order: incident marker, jam marker (with delay text), closed-road\n * marker. Canonical splits jam into a pure icon (z12+) and an icon+delay-text layer (z13+);\n * stock MapLibre ignores the `tomtom:transition` hint that would hide one behind the other,\n * so we render a single jam-with-text layer from z12. Direction is rendered by the\n * `-inner-chevron` line layer, not a per-feature symbol.\n * @ignore\n */\nexport const buildCanonicalIncidentSymbolLayers = (\n sourceId: string,\n beforeID: string | undefined,\n): SymbolLayerWithBefore[] => [\n {\n id: `${sourceId}-incident-marker`,\n type: 'symbol',\n minzoom: 13,\n filter: INCIDENT_MARKER_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-anchor': 'bottom-right',\n 'icon-offset': [-1, 3],\n 'icon-image': INCIDENT_MARKER_ICON,\n 'icon-size': ICON_SIZE,\n 'symbol-sort-key': SORT_KEY,\n },\n beforeID,\n },\n {\n id: `${sourceId}-jam-marker`,\n type: 'symbol',\n minzoom: 12,\n filter: JAM_MARKER_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'icon-anchor': 'bottom-left',\n 'icon-offset': [2, 3],\n 'icon-image': JAM_ICON,\n 'icon-size': ICON_SIZE,\n 'icon-padding': 0,\n 'text-anchor': 'bottom-left',\n 'text-field': JAM_TEXT,\n 'text-font': ['NotoSans-CondensedBold'],\n 'text-offset': [2.7, -0.75],\n 'text-size': JAM_TEXT_SIZE,\n 'symbol-sort-key': SORT_KEY,\n },\n paint: { 'text-color': 'hsl(210, 30%, 14%)' },\n beforeID,\n },\n {\n id: `${sourceId}-closed-road-marker`,\n type: 'symbol',\n minzoom: 12,\n filter: CLOSED_ROAD_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-anchor': 'bottom-right',\n 'icon-offset': [-1, 3],\n 'icon-image': 'traffic-incidents-road_closed',\n 'icon-size': ICON_SIZE,\n },\n beforeID,\n },\n];\n","import type { TrafficIncident, TrafficIncidentDetails } from '@tomtom-org/maps-sdk/core';\nimport type { BeforeLayerConfig } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { addOrUpdateImage, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildCanonicalIncidentLineLayers,\n buildCanonicalIncidentSymbolLayers,\n resolveFocusStyle,\n} from './layers/incidentDetailsLayers';\nimport type { TrafficIncidentOverlayConfig } from './types/trafficIncidentOverlayConfig';\nimport { getIncidentDirectionChevronImage, INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from './util/incidentDirectionChevron';\nimport { readIncidentPalette } from './util/readIncidentPalette';\n\ntype Sources = {\n incidents: GeoJSONSourceWithLayers<TrafficIncidentDetails>;\n};\n\n// Resolve a caller-supplied `beforeLayerConfig` value into a concrete MapLibre layer-ID.\n// Undefined (no config) places incidents below `lowestLabel` so labels stay readable on top;\n// `'top'` passes undefined to MapLibre so layers go to the end of the stack. Mirrors\n// `resolveBeforeID` in `traffic/layers/areaAnalyticsLayers.ts` for consistency.\nconst resolveBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestLabel;\n};\n\n/**\n * Renders traffic incidents returned by `trafficIncidentDetails()` on the map.\n *\n * Use this module when you fetch incidents via the Incident Details service and want\n * to display them as a developer-controlled overlay. {@link TrafficIncidentsModule} is\n * the vector-tile alternative — hidden in the default style, so the overlay renders\n * on its own without collision; if you've enabled it elsewhere in your app, hide it\n * again before using the overlay to avoid double-rendering.\n *\n * ## Rendering approach\n *\n * 7–8 layers — 4 to 5 line (optional focus-halo, outline, solid inner, direction chevron,\n * pattern inner) and 3 symbol (incident marker, jam marker with delay text, closed-road\n * marker). Per-magnitude styling uses match expressions on `magnitudeOfDelay` inside a\n * single layer instead of one layer per magnitude. Colours are read from the loaded style\n * at init (see {@link ./util/readIncidentPalette}), falling back to built-in defaults when\n * absent. Direction is rendered as a tiled `line-pattern` (chevron sprite registered at\n * init) instead of the vector-tile module's per-feature `roads-arrow` symbol — the\n * pattern is continuous, so it reads on short jams and at planning zooms (z12+) where\n * the per-feature symbol only activates at z15+.\n *\n * ## Focus\n *\n * {@link setFocus} writes MapLibre feature-state `focused=true|false` on each rendered\n * incident. The default visual treatment widens the focused subset and paints a black\n * outline beneath it; unfocused incidents are unchanged. Override or disable the visual\n * treatment via the `focus` config — `focus: false` keeps `setFocus` writing feature-state\n * but emits no halo layer and no width pop, leaving callers free to drive their own\n * styling off `feature-state.focused`.\n *\n * ## API limitations\n *\n * The Incident Details REST API returns a feature list, not a rendering-ready tile.\n * Several tags that the vector-tile pipeline injects per-feature are absent:\n *\n * - **Road classification** (`road_category`, `road_subcategory`) — the vector-tile\n * pipeline scales line width and offset by road class (motorway wider than street,\n * stripe placed alongside the road on one carriageway). Without it, per-road-class\n * geometry becomes a guess: we render every incident as a uniform stripe centred on\n * the road, no offset.\n * - **Traffic-direction side** (`left_hand_traffic`) — only meaningful when paired with\n * road-class-driven offset; irrelevant here.\n * - **Declutter priority** (`display_class`) — the vector-tile pipeline hides\n * low-priority incidents at low zooms; we show everything above the layer minzoom.\n * - **Endpoint restriction** (`point_type`) — the vector-tile pipeline limits jam and\n * closed-road icons to line endpoints; we render on every matching feature.\n * - **Secondary cause** (`icon_category_1`) — the vector-tile pipeline layers a cause\n * icon (e.g. accident) over the jam icon; we show only the primary category.\n *\n * Callers wanting full per-road-class visual fidelity should use\n * {@link TrafficIncidentsModule} instead.\n *\n * ## Known limitations\n *\n * - **Overlapping incidents** — when multiple incidents share the same point or stack\n * along the same road segment, MapLibre's symbol collision culling keeps only the\n * highest-sort-key feature. Culled features are also invisible to\n * `queryRenderedFeatures`, so hover and click cannot reach them. If you need to\n * surface multiple incidents at one location, query the source data directly\n * (the `FeatureCollection` you passed to {@link show}) rather than relying on\n * render-time hit-testing.\n *\n * @example\n * Fetch and render incidents for Amsterdam:\n * ```typescript\n * import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\n * import { TomTomMap, TrafficIncidentOverlayModule } from '@tomtom-org/maps-sdk/map';\n * import { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n *\n * TomTomConfig.instance.put({ apiKey: '<API_KEY>' });\n *\n * const map = new TomTomMap({ mapLibre: { container: 'map', center: [4.9, 52.37], zoom: 13 } });\n *\n * const overlay = await TrafficIncidentOverlayModule.get(map);\n * const result = await trafficIncidentDetails({\n * bbox: [4.85, 52.34, 4.95, 52.40],\n * timeValidityFilter: ['present'],\n * });\n * await overlay.show(result);\n *\n * // Highlight a subset.\n * overlay.setFocus([result.features[0].properties.id as string]);\n * ```\n *\n * @see {@link TrafficIncidentsModule} — the vector-tile alternative. Hidden in the\n * default style; if you've enabled it explicitly elsewhere in your app, hide it again\n * before using the overlay to avoid double-rendering.\n * @see [Traffic Incident Overlay Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-incident-overlay)\n * @see [Traffic Incident Details Service](https://docs.tomtom.com/maps-sdk-js/guides/services/traffic/traffic-incident-details)\n *\n * @group Traffic\n */\nexport class TrafficIncidentOverlayModule extends AbstractMapModule<Sources, TrafficIncidentOverlayConfig> {\n private lastResult: TrafficIncidentDetails | null = null;\n private lastFeatureIds: string[] = [];\n private lastFocusIds: readonly string[] | null = null;\n private readonly shownFeaturesHandlers: ((result: TrafficIncidentDetails) => void)[] = [];\n\n static async get(\n tomtomMap: TomTomMap,\n config?: TrafficIncidentOverlayConfig,\n ): Promise<TrafficIncidentOverlayModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new TrafficIncidentOverlayModule(tomtomMap, config ?? {});\n }\n\n private constructor(map: TomTomMap, config?: TrafficIncidentOverlayConfig) {\n super('geojson', map, config);\n }\n\n /** @ignore */\n protected _initSourcesWithLayers(config?: TrafficIncidentOverlayConfig, _restore?: boolean): Sources {\n const sourceId = `traffic-incident-overlay-${this.instanceIndex}`;\n\n // Skipped in non-DOM envs — the image getter returns undefined there.\n const chevronImg = getIncidentDirectionChevronImage();\n if (chevronImg) {\n void addOrUpdateImage(\n 'if-not-in-sprite',\n INCIDENT_DIRECTION_CHEVRON_IMAGE_ID,\n chevronImg,\n this.mapLibreMap,\n );\n }\n\n // Layers are inserted below `lowestLabel` by default so incidents sit under map\n // labels (roads, places, POIs) rather than obscuring them — matches the routing,\n // analytics, and geometries modules. Callers can override via `beforeLayerConfig`.\n const beforeID = resolveBeforeID(config?.beforeLayerConfig);\n const palette = readIncidentPalette(this.mapLibreMap);\n const focus = resolveFocusStyle(config?.focus);\n const layerSpecs = [\n ...buildCanonicalIncidentLineLayers(sourceId, beforeID, palette, focus),\n ...buildCanonicalIncidentSymbolLayers(sourceId, beforeID),\n ];\n\n return {\n incidents: new GeoJSONSourceWithLayers<TrafficIncidentDetails>(this.mapLibreMap, sourceId, layerSpecs),\n };\n }\n\n /** @ignore */\n protected _applyConfig(config: TrafficIncidentOverlayConfig | undefined): TrafficIncidentOverlayConfig | undefined {\n if (config?.visible === false) {\n this.sourcesWithLayers.incidents.setLayersVisible(false);\n } else {\n this.sourcesWithLayers.incidents.setLayersVisible(true);\n }\n this.applyBeforeLayer(config?.beforeLayerConfig);\n return config;\n }\n\n private applyBeforeLayer(beforeLayerConfig?: BeforeLayerConfig): void {\n const beforeId = resolveBeforeID(beforeLayerConfig);\n for (const lid of this.sourcesWithLayers.incidents.sourceAndLayerIDs.layerIDs) {\n this.mapLibreMap.moveLayer(lid, beforeId);\n }\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n this.initSourcesWithLayers(this.config, true);\n if (this.lastResult) {\n this.sourcesWithLayers.incidents.show(this.lastResult);\n }\n this._applyConfig(this.config);\n if (this.lastFocusIds !== null && this.lastFocusIds.length > 0) {\n this.setFocus(this.lastFocusIds);\n }\n }\n\n /**\n * Render the service result. Replaces any previously shown snapshot.\n */\n async show(result: TrafficIncidentDetails): Promise<void> {\n await this.waitUntilModuleReady();\n this.clearFeatureState();\n this.lastFocusIds = null;\n this.lastResult = result;\n this.lastFeatureIds = result.features\n .map((f) => f.properties.id)\n .filter((id): id is string => typeof id === 'string');\n this.sourcesWithLayers.incidents.show(result);\n for (const handler of this.shownFeaturesHandlers) {\n handler(result);\n }\n }\n\n /**\n * Remove all rendered incidents (source kept, data emptied).\n */\n async clear(): Promise<void> {\n await this.waitUntilModuleReady();\n this.clearFeatureState();\n this.lastResult = null;\n this.lastFeatureIds = [];\n this.lastFocusIds = null;\n this.sourcesWithLayers.incidents.clear();\n }\n\n /**\n * Show or hide all incident layers.\n */\n setVisible(visible: boolean): void {\n this.config = { ...this.config, visible };\n this.sourcesWithLayers.incidents.setLayersVisible(visible);\n this.emitConfigChange();\n }\n\n /**\n * Whether any incident layer is currently visible.\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.incidents.isAnyLayerVisible();\n }\n\n /**\n * Move incident layers before the given map-style layer, or to the top.\n */\n moveBeforeLayer(beforeLayerConfig: BeforeLayerConfig): void {\n this.config = { ...this.config, beforeLayerConfig };\n this.applyBeforeLayer(beforeLayerConfig);\n this.emitConfigChange();\n }\n\n /**\n * Returns the currently shown incidents — the exact data that was passed to {@link show}.\n *\n * Reads the module's client-side cache (`shownFeatures`), mirroring the other GeoJSON\n * modules (`PlacesModule`, `TrafficAreaAnalyticsModule`, `GeometriesModule`). It does\n * **not** hit-test the viewport: the proxy already scopes and de-duplicates the\n * `allEventFeatures` it dispatches to click handlers, so no caller needs viewport-level\n * de-duplication here.\n */\n getShown(): { incidents: TrafficIncidentDetails } {\n return { incidents: this.sourcesWithLayers.incidents.shownFeatures };\n }\n\n get events(): CombinedEvents<TrafficIncident, TrafficIncidentOverlayConfig, TrafficIncidentDetails> {\n return new CombinedEvents(\n new UserEvents<TrafficIncident>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.incidents,\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n\n /**\n * Mark a subset of currently-rendered incidents as \"focused\" via MapLibre\n * feature-state. `null` clears the state. Paint expressions in\n * `incidentDetailsLayers.ts` read `['feature-state', 'focused']` and pop the\n * focused subset (wider stripe + black outline). Unfocused features are\n * unchanged — the focus treatment adds emphasis, it does not dim the rest.\n *\n * No effect if called before `show()`.\n */\n setFocus(ids: readonly string[] | null): void {\n if (ids === null || ids.length === 0) {\n this.lastFocusIds = null;\n this.clearFeatureState();\n return;\n }\n if (this.lastFeatureIds.length === 0) return;\n this.lastFocusIds = ids;\n const focusSet = new Set<string>(ids);\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featId of this.lastFeatureIds) {\n this.mapLibreMap.setFeatureState({ source: sourceId, id: featId }, { focused: focusSet.has(featId) });\n }\n }\n\n private clearFeatureState(): void {\n if (this.lastFeatureIds.length === 0) return;\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featId of this.lastFeatureIds) {\n this.mapLibreMap.removeFeatureState({ source: sourceId, id: featId });\n }\n }\n}\n","import { isEmpty, isNil, omitBy } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport type { LayerSpecWithSource } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n filterLayersBySources,\n ModuleEvents,\n StyleSourceWithLayers,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyFilter, buildMapLibreIncidentFilters } from './filters/trafficFilters';\nimport type { IncidentsConfig, TrafficIncidentsFilters } from './types/trafficIncidentsConfig';\nimport { TrafficIncidentsModuleFeature } from './types/trafficIncidentsFeature';\nimport { trafficIncidentMapping } from './util/trafficIncidentMapping';\n\n/**\n * IDs of sources and layers for traffic incidents module.\n */\ntype TrafficIncidentsSourcesWithLayers = {\n trafficIncidents: StyleSourceWithLayers;\n};\n\n/**\n * Traffic Incidents Module for displaying and configuring real-time traffic incidents on the map.\n *\n * This module controls the vector tile traffic incidents layers that show traffic\n * events like accidents, road closures, construction, and hazards.\n *\n * @remarks\n * **Features:**\n * - Toggle incidents visibility on/off\n * - Separate control for incident icons\n * - Filter by incident type (accident, construction, etc.)\n * - Filter by severity/delay magnitude\n * - Filter by road categories\n * - Icon and line/polygon visualization\n *\n * **Incident Types:**\n * - Accidents\n * - Road closures\n * - Construction/road works\n * - Weather conditions (fog, ice, rain, etc.)\n * - Lane closures\n * - Traffic jams\n * - Broken down vehicles\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { TrafficIncidentsModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module (auto-add to style if needed)\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * trafficIncidentsModule.setVisible(false);\n * trafficIncidentsModule.setVisible(true);\n *\n * // Control icons separately\n * trafficIncidentsModule.setIconsVisible(false);\n * ```\n *\n * @example\n * Filter by incident type:\n * ```typescript\n * // Show only accidents and road closures\n * trafficIncidentsModule.filter({\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed']\n * }\n * }]\n * });\n *\n * // Hide construction\n * trafficIncidentsModule.filter({\n * any: [{\n * incidentCategories: {\n * show: 'all_except',\n * values: ['roadworks']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter by severity:\n * ```typescript\n * // Show only major delays\n * incidents.filter({\n * any: [{\n * magnitudes: {\n * show: 'only',\n * values: ['major']\n * }\n * }]\n * });\n *\n * // Show incidents with at least 10 minutes delay\n * incidents.filter({\n * any: [{\n * delays: {\n * mustHaveDelay: true,\n * minDelayMinutes: 10\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter icons separately from incident areas:\n * ```typescript\n * // Show all incidents but only major icons\n * incidents.filter(\n * {\n * any: [{}] // Show all incidents\n * },\n * {\n * any: [{\n * magnitudes: { show: 'only', values: ['major'] }\n * }]\n * }\n * );\n * ```\n *\n * @see [Traffic Incidents Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-incidents)\n * @see [Traffic Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic)\n * @see {@link TrafficIncidentOverlayModule} — for rendering incidents fetched via the\n * `trafficIncidentDetails()` REST service as a developer-controlled overlay (use the\n * overlay when you need typed incident objects in app code; this vector-tile module\n * remains the recommended default for visual fidelity).\n *\n * @group Traffic Incidents\n */\nexport class TrafficIncidentsModule extends AbstractMapModule<TrafficIncidentsSourcesWithLayers, IncidentsConfig> {\n private originalFilters!: Record<string, FilterSpecification | undefined>;\n\n /**\n * Retrieves a TrafficIncidentsModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization, visibility, and filters.\n *\n * @returns A promise that resolves to the initialized TrafficIncidentsModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state for all incidents\n * - `icons.visible`: Initial visibility for incident icons\n * - `ensureAddedToStyle`: Auto-add traffic incidents to style if missing\n * - `filters`: Incident type, severity, and delay filters\n * - `icons.filters`: Separate filters for icons\n *\n * @throws Error if traffic incidents source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map);\n * ```\n *\n * @example\n * With configuration:\n * ```typescript\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map, {\n * visible: true,\n * icons: { visible: true },\n * filters: {\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed', 'jam']\n * }\n * }]\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: IncidentsConfig): Promise<TrafficIncidentsModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, TRAFFIC_INCIDENTS_SOURCE_ID, 'trafficIncidents');\n return new TrafficIncidentsModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: IncidentsConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const incidentsSource = this.mapLibreMap.getSource(TRAFFIC_INCIDENTS_SOURCE_ID);\n if (!incidentsSource) {\n throw notInTheStyle(`init ${TrafficIncidentsModule.name} with source ID ${TRAFFIC_INCIDENTS_SOURCE_ID}`);\n }\n this.originalFilters = {};\n for (const layer of this.getLayers()) {\n this.originalFilters[layer.id] = layer.filter;\n }\n return { trafficIncidents: new StyleSourceWithLayers(this.mapLibreMap, incidentsSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: IncidentsConfig | undefined) {\n // We do not update config in setVisible since it could override icons visibility setting:\n this._setVisible(config?.visible ?? false, { updateConfig: false });\n if (!isNil(config?.icons?.visible)) {\n this._setIconsVisible(config.icons.visible, false);\n }\n this._filter(config?.filters, config?.icons?.filters, false);\n return config;\n }\n\n /**\n * Applies filters to traffic incidents display.\n *\n * @param incidentFilters - Filter for incident areas/lines. Pass `undefined` to reset.\n * @param iconFilters - Optional separate filter for incident icons. Pass `undefined` to reset.\n *\n * @remarks\n * **Filter Options:**\n * - `incidentCategories`: Filter by incident type\n * - `magnitudes`: Filter by delay severity (minor/moderate/major/unknown)\n * - `delays`: Filter by delay duration\n * - `roadCategories`: Filter by road importance\n * - `roadSubCategories`: Filter by specific road types\n *\n * **Available Incident Categories:**\n * - `accident`, `road-closed`, `lane-closed`\n * - `roadworks` (construction)\n * - `jam` (traffic jam)\n * - `fog`, `rain`, `frost`, `wind`, `flooding`\n * - `danger`, `animals-on-road`, `narrow-lanes`\n * - `broken-down-vehicle`\n * - `other`\n *\n * **Delay Magnitudes:**\n * - `minor`: Small delays\n * - `moderate`: Moderate delays\n * - `major`: Significant delays\n * - `unknown`: Unknown or no delay info\n *\n * @example\n * Filter by type:\n * ```typescript\n * incidents.filter({\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter by severity and delay:\n * ```typescript\n * incidents.filter({\n * any: [{\n * magnitudes: { show: 'only', values: ['major', 'moderate'] },\n * delays: {\n * mustHaveDelay: true,\n * minDelayMinutes: 5\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Different filters for icons and areas:\n * ```typescript\n * // Show all incidents on roads\n * const incidentFilter = {\n * any: [{\n * roadCategories: { show: 'only', values: ['motorway', 'trunk'] }\n * }]\n * };\n *\n * // But only show icons for major incidents\n * const iconFilter = {\n * any: [{\n * magnitudes: { show: 'only', values: ['major'] }\n * }]\n * };\n *\n * incidents.filter(incidentFilter, iconFilter);\n * ```\n */\n filter(incidentFilters?: TrafficIncidentsFilters, iconFilters?: TrafficIncidentsFilters) {\n this._filter(incidentFilters, iconFilters);\n }\n\n private _filter(\n incidentFilters: TrafficIncidentsFilters | undefined,\n iconFilters: TrafficIncidentsFilters | undefined,\n updateConfig = true,\n ) {\n if (this.tomtomMap.mapReady) {\n if (incidentFilters?.any?.length) {\n const incidentFilterExpression = buildMapLibreIncidentFilters(incidentFilters);\n if (incidentFilterExpression) {\n const layers = iconFilters ? this.getNonSymbolLayers() : this.getLayers();\n applyFilter(incidentFilterExpression, layers, this.mapLibreMap, this.originalFilters);\n }\n } else if (this.config?.filters?.any?.length) {\n applyFilter(undefined, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n if (iconFilters?.any?.length) {\n const iconFilterExpression = buildMapLibreIncidentFilters(iconFilters);\n if (iconFilterExpression) {\n applyFilter(iconFilterExpression, this.getSymbolLayers(), this.mapLibreMap, this.originalFilters);\n }\n }\n }\n\n // else: default incidents visibility has been set already if necessary\n if (updateConfig) {\n this.config = omitBy(\n {\n ...this.config,\n filters: incidentFilters,\n icons: { ...this.config?.icons, filters: iconFilters },\n },\n isNil,\n );\n this.emitConfigChange();\n }\n }\n\n private getLayers(): LayerSpecWithSource[] {\n return filterLayersBySources(this.tomtomMap.mapLibreMap, [TRAFFIC_INCIDENTS_SOURCE_ID]);\n }\n\n private getSymbolLayers(): LayerSpecWithSource[] {\n return this.getLayers().filter((layer) => layer.type === 'symbol');\n }\n\n private getNonSymbolLayers(): LayerSpecWithSource[] {\n return this.getLayers().filter((layer) => layer.type != 'symbol');\n }\n\n /**\n * Sets the visibility of incident icon layers.\n *\n * @param visible - `true` to show icons, `false` to hide them.\n *\n * @remarks\n * This controls only the icon/symbol layers, not the incident area polygons or lines.\n *\n * @example\n * ```typescript\n * // Hide icons but keep incident areas visible\n * incidents.setIconsVisible(false);\n *\n * // Show icons\n * incidents.setIconsVisible(true);\n * ```\n */\n setIconsVisible(visible: boolean): void {\n this._setIconsVisible(visible);\n }\n\n private _setIconsVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficIncidents.setLayersVisible(\n visible,\n (layerSpec) => layerSpec.type === 'symbol',\n );\n }\n if (updateConfig) {\n this.config = { ...this.config, icons: { ...this.config?.icons, visible } };\n this.emitConfigChange();\n }\n }\n\n /**\n * Sets the visibility of all traffic incident layers.\n *\n * @param visible - `true` to show incidents, `false` to hide them.\n *\n * @remarks\n * This controls all incident layers including icons, lines, and polygons.\n *\n * @example\n * ```typescript\n * incidents.setVisible(false); // Hide all incidents\n * incidents.setVisible(true); // Show all incidents\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, options?: { updateConfig: boolean }): void {\n const updateConfig = options?.updateConfig ?? true;\n if (updateConfig) {\n // setting all traffic visible also nullifies the icons visible setting\n delete this.config?.icons?.visible;\n // we remove empty values from config to avoid confusion (in case icons part is just empty after deleting visible)\n this.config = { ...omitBy({ ...this.config }, isEmpty), visible };\n this.emitConfigChange();\n }\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficIncidents.setLayersVisible(visible);\n }\n }\n\n /**\n * Checks if any traffic incident layers are currently visible.\n *\n * @returns `true` if any incident layer is visible, `false` if all are hidden.\n *\n * @example\n * ```typescript\n * if (incidents.isVisible()) {\n * console.log('Incidents are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.trafficIncidents.isAnyLayerVisible();\n }\n\n /**\n * Checks if any incident icon layers are currently visible.\n *\n * @returns `true` if any icon layer is visible, `false` if all icons are hidden.\n *\n * @example\n * ```typescript\n * if (incidents.anyIconLayersVisible()) {\n * console.log('Incident icons are shown');\n * }\n * ```\n */\n anyIconLayersVisible(): boolean {\n return this.sourcesWithLayers.trafficIncidents?.isAnyLayerVisible((layerSpec) => layerSpec.type === 'symbol');\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible traffic incident features.\n *\n * @remarks\n * Returns all incident features currently rendered in the visible map area,\n * including both incident areas/lines and icon markers.\n *\n * **Feature Properties:**\n * Properties depend on the traffic incidents vector tile schema and may include:\n * - Incident type/category\n * - Severity/magnitude\n * - Delay information\n * - Road affected\n * - Description\n *\n * @example\n * ```typescript\n * const shown = incidents.getShown();\n * console.log(`Visible incidents: ${shown.trafficIncidents.length}`);\n * shown.trafficIncidents.forEach(incident => {\n * console.log('Incident:', incident.properties);\n * });\n * ```\n *\n * @example\n * Filter and analyze visible incidents:\n * ```typescript\n * const shown = incidents.getShown();\n * const accidents = shown.trafficIncidents.filter(\n * incident => incident.properties.category === 'accident'\n * );\n * console.log(`Accidents in view: ${accidents.length}`);\n * ```\n */\n getShown(): { trafficIncidents: TrafficIncidentsModuleFeature[] } {\n return {\n trafficIncidents: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.trafficIncidents.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(trafficIncidentMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficIncidentsModuleFeature, IncidentsConfig, never> {\n return new CombinedEvents(\n new UserEvents<TrafficIncidentsModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.trafficIncidents,\n this.config?.events,\n trafficIncidentMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { FilterShowMode, ValuesFilter } from '../../shared';\n\n/**\n * Available road hierarchy category identifiers.\n *\n * @remarks\n * These categories represent different levels in the road network hierarchy,\n * from major highways to local streets.\n *\n * @group Traffic\n */\nexport const roadCategories = [\n 'motorway',\n 'motorway_link',\n 'trunk',\n 'trunk_link',\n 'primary',\n 'primary_link',\n 'secondary',\n 'secondary_link',\n 'tertiary',\n 'tertiary_link',\n 'street',\n 'service',\n 'track',\n] as const;\n\n/**\n * Road hierarchy category type.\n *\n * @remarks\n * Classifies roads by their importance and capacity in the transportation network.\n * Used for filtering traffic data display based on road significance.\n *\n * Road hierarchy (from highest to lowest):\n * - `motorway` / `motorway_link` - High-capacity highways with restricted access\n * - `trunk` / `trunk_link` - Major inter-city roads\n * - `primary` / `primary_link` - Primary through routes\n * - `secondary` / `secondary_link` - Secondary through routes\n * - `tertiary` / `tertiary_link` - Connecting roads\n * - `street` - Local streets (see {@link StreetRoadSubCategory})\n * - `service` - Service roads (see {@link ServiceRoadSubCategory})\n * - `track` - Unpaved or agricultural tracks\n *\n * @group Traffic\n */\nexport type RoadCategory = (typeof roadCategories)[number];\n\n/**\n * Available street road sub-category identifiers.\n *\n * @remarks\n * Provides finer granularity for classifying local streets.\n *\n * @group Traffic\n */\nexport const streetRoadSubCategories = ['unclassified', 'residential', 'living_street'] as const;\n\n/**\n * Street road sub-category type.\n *\n * @remarks\n * Further classifies `street` roads into specific sub-types.\n *\n * Sub-categories:\n * - `unclassified` - Unclassified local roads\n * - `residential` - Roads in residential areas\n * - `living_street` - Pedestrian-priority shared streets\n *\n * @group Traffic\n */\nexport type StreetRoadSubCategory = (typeof streetRoadSubCategories)[number];\n\n/**\n * Available service road sub-category identifiers.\n *\n * @remarks\n * Provides finer granularity for classifying service roads.\n *\n * @group Traffic\n */\nexport const serviceRoadSubCategories = ['parking', 'driveway', 'alley'] as const;\n\n/**\n * Service road sub-category type.\n *\n * @remarks\n * Further classifies `service` roads into specific sub-types.\n *\n * Sub-categories:\n * - `parking` - Roads within parking areas\n * - `driveway` - Private access roads\n * - `alley` - Narrow back-access lanes\n *\n * @group Traffic\n */\nexport type ServiceRoadSubCategory = (typeof serviceRoadSubCategories)[number];\n\n/**\n * Road sub-category type.\n *\n * @remarks\n * Combines street and service sub-categories. Only `street` and `service` road categories have sub-categories.\n *\n * @group Traffic\n */\nexport type RoadSubCategory = StreetRoadSubCategory | ServiceRoadSubCategory;\n\n/**\n * Common filter configuration shared between traffic incidents and flow visualization.\n *\n * @remarks\n * Provides road category filtering capabilities used by both incident and flow modules.\n *\n * @group Traffic\n */\nexport type TrafficCommonFilter = {\n /**\n * Filters traffic data by road hierarchy categories.\n *\n * @remarks\n * Controls which road types display traffic information.\n * Use the `mode` field to specify whether to show or hide the selected categories.\n *\n * @example\n * ```ts\n * // Show only motorways and trunk roads\n * roadCategories: { show: 'only', values: ['motorway', 'trunk'] }\n * ```\n */\n roadCategories?: ValuesFilter<RoadCategory>;\n\n /**\n * Filters traffic data by road sub-categories.\n *\n * @remarks\n * Provides finer-grained control for street and service roads.\n * Applies to sub-categories of {@link StreetRoadSubCategory} and {@link ServiceRoadSubCategory}.\n *\n * @example\n * ```ts\n * // Hide minor local streets\n * roadSubCategories: { show: 'all_except', values: ['living_street'] }\n * ```\n */\n roadSubCategories?: ValuesFilter<RoadSubCategory>;\n};\n\nexport type { FilterShowMode };\n","import type { BBox } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport type { Map } from 'maplibre-gl';\nimport type { TomTomMap } from '../TomTomMap';\nimport type {\n CalculateFittingBBoxOptions,\n CalculateMapBoundsOptions,\n CalculateMapCenterOptions,\n} from './types/paddedBounds';\n\n// (Originally mostly AI-generated)\n\ntype BoundsPX = {\n left: number;\n top: number;\n right: number;\n bottom: number;\n};\n\nconst getRelativeElementBounds = (elementRect: DOMRect, containerRect: DOMRect): BoundsPX => ({\n left: elementRect.left - containerRect.left,\n top: elementRect.top - containerRect.top,\n right: elementRect.right - containerRect.left,\n bottom: elementRect.bottom - containerRect.top,\n});\n\nconst isElementOutsideContainer = (bounds: BoundsPX, containerWidth: number, containerHeight: number): boolean =>\n bounds.right <= 0 || bounds.left >= containerWidth || bounds.bottom <= 0 || bounds.top >= containerHeight;\n\n// Adjusts the current visible bounds to account for a horizontal bar element (that spans the whole width)\nconst adjustForHorizontalBar = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerHeight: number,\n padding: number,\n): void => {\n const distanceFromTop = bounds.top;\n const distanceFromBottom = containerHeight - bounds.bottom;\n if (distanceFromTop <= distanceFromBottom) {\n currentVisible.top = Math.max(currentVisible.top, bounds.bottom + padding);\n } else {\n currentVisible.bottom = Math.min(currentVisible.bottom, bounds.top - padding);\n }\n};\n\n// Adjusts the current visible bounds to account for a vertical bar element (that spans the whole height)\nconst adjustForVerticalBar = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerWidth: number,\n padding: number,\n): void => {\n const distanceFromLeft = bounds.left;\n const distanceFromRight = containerWidth - bounds.right;\n if (distanceFromLeft <= distanceFromRight) {\n currentVisible.left = Math.max(currentVisible.left, bounds.right + padding);\n } else {\n currentVisible.right = Math.min(currentVisible.right, bounds.left - padding);\n }\n};\n\n// Adjusts the current visible bounds to account for a floating element (that does not span full width or height)\nconst adjustForFloatingElement = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerWidth: number,\n containerHeight: number,\n padding: number,\n): void => {\n const distanceFromLeft = bounds.left;\n const distanceFromRight = containerWidth - bounds.right;\n const distanceFromTop = bounds.top;\n const distanceFromBottom = containerHeight - bounds.bottom;\n const minDistance = Math.min(distanceFromLeft, distanceFromRight, distanceFromTop, distanceFromBottom);\n\n if (minDistance === distanceFromLeft) {\n currentVisible.left = Math.max(currentVisible.left, bounds.right + padding);\n } else if (minDistance === distanceFromRight) {\n currentVisible.right = Math.min(currentVisible.right, bounds.left - padding);\n } else if (minDistance === distanceFromTop) {\n currentVisible.top = Math.max(currentVisible.top, bounds.bottom + padding);\n } else {\n currentVisible.bottom = Math.min(currentVisible.bottom, bounds.top - padding);\n }\n};\n\n/**\n * Clamps the element bounds to the container boundaries.\n * This handles cases where UI elements extend beyond the visible container\n * (e.g., a sidebar that goes past the screen edge).\n */\nconst clampBoundsToContainer = (bounds: BoundsPX, containerWidth: number, containerHeight: number): BoundsPX => ({\n left: Math.max(0, bounds.left),\n top: Math.max(0, bounds.top),\n right: Math.min(containerWidth, bounds.right),\n bottom: Math.min(containerHeight, bounds.bottom),\n});\n\nconst adjustVisibleBoundsForElement = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n mapWidthPX: number,\n mapHeightPX: number,\n padding: number,\n): void => {\n // Clamp bounds to container to handle elements that extend beyond the screen\n const clampedBounds = clampBoundsToContainer(bounds, mapWidthPX, mapHeightPX);\n\n // Determine if the element touches or extends beyond the full width or height of the container\n // An element \"spans full width\" if it touches both left and right edges (or extends beyond)\n const spansFullWidth = bounds.left <= 0 && bounds.right >= mapWidthPX;\n const spansFullHeight = bounds.top <= 0 && bounds.bottom >= mapHeightPX;\n\n // For elements spanning full width (horizontal bars), only consider vertical adjustment\n if (spansFullWidth && !spansFullHeight) {\n adjustForHorizontalBar(clampedBounds, currentVisible, mapHeightPX, padding);\n return;\n }\n\n // For elements spanning full height (vertical bars), only consider horizontal adjustment\n if (spansFullHeight && !spansFullWidth) {\n adjustForVerticalBar(clampedBounds, currentVisible, mapWidthPX, padding);\n return;\n }\n\n // Check if element touches/extends beyond an edge (makes it act like a bar on that side)\n const touchesLeft = bounds.left <= 0;\n const touchesRight = bounds.right >= mapWidthPX;\n const touchesTop = bounds.top <= 0;\n const touchesBottom = bounds.bottom >= mapHeightPX;\n\n // If element touches opposite edges in one dimension but not the other,\n // treat it as a bar in that dimension\n if ((touchesLeft || touchesRight) && !touchesTop && !touchesBottom) {\n // Element extends horizontally but not vertically - treat as horizontal bar\n adjustForHorizontalBar(clampedBounds, currentVisible, mapHeightPX, padding);\n return;\n }\n if ((touchesTop || touchesBottom) && !touchesLeft && !touchesRight) {\n // Element extends vertically but not horizontally - treat as vertical bar\n adjustForVerticalBar(clampedBounds, currentVisible, mapWidthPX, padding);\n return;\n }\n\n // For corner elements (touching edges in both dimensions) or floating elements\n // determine the best adjustment based on which edge the element is closest to\n adjustForFloatingElement(clampedBounds, currentVisible, mapWidthPX, mapHeightPX, padding);\n};\n\n/**\n * Resolves a surrounding element reference to an HTMLElement.\n * If the reference is a string, it's treated as a DOM selector.\n * @param ref - Either an HTMLElement or a DOM selector string\n * @returns The resolved HTMLElement, or null if not found\n */\nconst toElement = (ref: HTMLElement | string): HTMLElement | null => {\n if (typeof ref === 'string') {\n return document.querySelector(ref);\n }\n return ref;\n};\n\n/**\n * Resolves a map reference to a MapLibre Map instance.\n * If the reference is a TomTomMap, it extracts the underlying mapLibreMap.\n * @param map - Either a TomTomMap or a MapLibre Map instance\n * @returns The MapLibre Map instance\n */\nconst getMapLibreMap = (map: TomTomMap | Map): Map => {\n if ('mapLibreMap' in map) {\n return map.mapLibreMap;\n }\n return map;\n};\n\ntype VisibleAreaResult = {\n visibleAreaBounds: BoundsPX;\n mapWidthPX: number;\n mapHeightPX: number;\n} | null;\n\n/**\n * Calculates the visible area bounds in pixel coordinates after accounting for\n * surrounding UI elements and padding.\n */\nconst calculateVisibleAreaBounds = (\n map: TomTomMap | Map,\n surroundingElements: (HTMLElement | string)[],\n paddingPX: number,\n): VisibleAreaResult => {\n const containerRect = getMapLibreMap(map).getContainer().getBoundingClientRect();\n const { width: mapWidthPX, height: mapHeightPX } = containerRect;\n\n // Initialize visible bounds with padding applied to all edges (will be mutated)\n const visibleAreaBounds: BoundsPX = {\n left: paddingPX,\n top: paddingPX,\n right: mapWidthPX - paddingPX,\n bottom: mapHeightPX - paddingPX,\n };\n\n for (const elementRef of surroundingElements) {\n const element = toElement(elementRef);\n if (!element) {\n continue;\n }\n const elementRect = element.getBoundingClientRect();\n const elementBounds = getRelativeElementBounds(elementRect, containerRect);\n\n if (isElementOutsideContainer(elementBounds, mapWidthPX, mapHeightPX)) {\n continue;\n }\n\n adjustVisibleBoundsForElement(elementBounds, visibleAreaBounds, mapWidthPX, mapHeightPX, paddingPX);\n }\n\n if (visibleAreaBounds.left >= visibleAreaBounds.right || visibleAreaBounds.top >= visibleAreaBounds.bottom) {\n return null;\n }\n\n return { visibleAreaBounds, mapWidthPX, mapHeightPX };\n};\n\n/**\n * Calculates the bounding box in lng-lat coordinates of the visible map area\n * that does not overlap with the given UI HTML elements.\n *\n * @remarks\n * This is useful for determining the area of the map that is not obscured by UI components.\n *\n * @param options - The options for calculating map bounds\n * @returns The padded bounding box as [west, south, east, north], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculatePaddedBBox = (options: CalculateMapBoundsOptions): BBox | null => {\n const { map, surroundingElements, paddingPX = 0 } = options;\n\n const result = calculateVisibleAreaBounds(map, surroundingElements, paddingPX);\n if (!result) {\n return null;\n }\n\n const { visibleAreaBounds } = result;\n const mapLibreMap = getMapLibreMap(map);\n const sw = mapLibreMap.unproject([visibleAreaBounds.left, visibleAreaBounds.bottom]);\n const ne = mapLibreMap.unproject([visibleAreaBounds.right, visibleAreaBounds.top]);\n\n return [sw.lng, sw.lat, ne.lng, ne.lat];\n};\n\n/**\n * Calculates the center point in lng-lat coordinates of the visible map area\n * that does not overlap with the given UI HTML elements.\n *\n * @remarks\n * * This is useful to offset the map center in a way that it looks harmonious with surrounding UI components.\n * * It's equivalent to the center of calculatePaddedBBox.\n *\n * @param options - The options for calculating map center\n * @returns The center as [lng, lat], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculatePaddedCenter = (options: CalculateMapCenterOptions): Position | null => {\n const { map, surroundingElements } = options;\n const bbox = calculatePaddedBBox({ map, surroundingElements });\n if (!bbox) {\n return null;\n }\n const [west, south, east, north] = bbox;\n return [(west + east) / 2, (south + north) / 2];\n};\n\n/**\n * Calculates an expanded bounding box that, when the map is zoomed to it, ensures that the given to-be-contained bounding box\n * is visible within the area not obscured by surrounding UI elements, including optional padding.\n * * In other words, calculates a bounding box that ensure the to-be-contained bbox fits within the visible area of the map.\n *\n * @remarks\n * This is useful when you have a specific geographic area (containedBBox) that you want to be fully visible\n * in the unobscured portion of the map (the area not covered by UI components).\n * The function returns a larger bounding box that accounts for the space taken by UI elements.\n *\n * @param options - The options for calculating expanded bounds\n * @returns The expanded bounding box as [west, south, east, north], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculateFittingBBox = (options: CalculateFittingBBoxOptions): BBox | null => {\n const { map, toBeContainedBBox, surroundingElements, paddingPX = 0 } = options;\n\n const result = calculateVisibleAreaBounds(map, surroundingElements, paddingPX);\n if (!result) {\n return null;\n }\n\n const { visibleAreaBounds, mapWidthPX, mapHeightPX } = result;\n\n // Calculate the ratios of how much the visible area is offset from the full container\n // These represent what fraction of the full map the visible area occupies\n const leftRatio = visibleAreaBounds.left / mapWidthPX;\n const rightRatio = visibleAreaBounds.right / mapWidthPX;\n const topRatio = visibleAreaBounds.top / mapHeightPX;\n const bottomRatio = visibleAreaBounds.bottom / mapHeightPX;\n\n // Calculate the width and height ratios of the visible area\n const widthRatio = rightRatio - leftRatio;\n const heightRatio = bottomRatio - topRatio;\n\n // Target bbox coordinates\n const [west, south, east, north] = toBeContainedBBox;\n const targetWidth = east - west;\n const targetHeight = north - south;\n\n // Calculate the full map extent needed so that when cropped by the visible area,\n // the target bbox fits exactly\n const fullWidth = targetWidth / widthRatio;\n const fullHeight = targetHeight / heightRatio;\n\n // Calculate the fitting expanded bounds\n // The visible area starts at leftRatio of the full width, so we need to extend west\n const expandedWest = west - leftRatio * fullWidth;\n const expandedEast = expandedWest + fullWidth;\n\n // For latitude, remember that in pixel coordinates, top is smaller Y but higher latitude\n // topRatio represents the fraction from top, which corresponds to north\n const expandedNorth = north + topRatio * fullHeight;\n const expandedSouth = expandedNorth - fullHeight;\n\n return [expandedWest, expandedSouth, expandedEast, expandedNorth];\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"map.es.js","names":[],"sources":["../src/traffic/util/trafficFlowMapping.ts","../src/traffic/util/trafficIncidentMapping.ts","../src/shared/errorMessages.ts","../src/shared/imageUtils.ts","../src/shared/resources/index.ts","../src/shared/assertionUtils.ts","../src/shared/featureId.ts","../src/shared/TomTomMapSource.ts","../src/shared/SourceWithLayers.ts","../src/shared/mapUtils.ts","../src/shared/AbstractMapModule.ts","../src/shared/CombinedEvents.ts","../src/shared/AbstractEventProxy.ts","../src/shared/eventUtils.ts","../src/shared/EventsProxy.ts","../src/shared/layers/layerIDs.ts","../src/shared/layers/sourcesIDs.ts","../src/shared/ModuleEvents.ts","../src/shared/UserEvents.ts","../src/pois/layers/poisLayers.ts","../src/pois/util/poiCategoryMapping.ts","../src/shared/layers/commonLayerProps.ts","../src/shared/layers/symbolLayers.ts","../src/shared/layers/utils.ts","../src/places/layers/clusterLayers.ts","../src/places/layers/connectionsLayers.ts","../src/places/utils/customIconScales.ts","../src/places/utils/evAvailabilityHelpers.ts","../src/places/utils/textOffsetCalculator.ts","../src/places/utils/themeAdaptation.ts","../src/places/utils/layerConfiguration.ts","../src/shared/layers/eventState.ts","../src/places/utils/layerSpecBuilders.ts","../src/places/layers/placesLayers.ts","../src/places/resources/index.ts","../src/places/utils/prepareConnectionsForDisplay.ts","../src/places/utils/toPinImageID.ts","../src/places/utils/preparePlacesForDisplay.ts","../src/places/PlacesModule.ts","../src/shared/mapLibreFilterUtils.ts","../src/pois/types/poiCategoryGroups.ts","../src/pois/util/poisMapping.ts","../src/pois/POIsModule.ts","../src/base/layerGroups.ts","../src/base/BaseMapModule.ts","../src/base/types/baseMapModuleConfig.ts","../src/custom/CustomGeoJSONModule.ts","../src/geometry/layers/constants.ts","../src/geometry/layers/geometryLayers.ts","../src/geometry/layers/colorPalettes.ts","../src/geometry/prepareGeometryForDisplay.ts","../src/geometry/GeometriesModule.ts","../src/geometry/prepareReachableRangesForDisplay.ts","../src/geometry/geometryConfigs.ts","../src/geometry/types/geometryTheme.ts","../src/hillshade/HillshadeModule.ts","../src/init/types/mapInit.ts","../src/utils/colorUtils.ts","../src/routing/layers/shared.ts","../src/routing/layers/chargingStopLayers.ts","../src/routing/layers/guidanceLayers.ts","../src/routing/layers/routeFerrySectionLayers.ts","../src/routing/layers/routeMainLineLayers.ts","../src/routing/layers/routeTollRoadLayers.ts","../src/traffic/util/trafficIncidentStyle.ts","../src/routing/layers/routeTrafficSectionLayers.ts","../src/routing/layers/routeTunnelSectionLayers.ts","../src/routing/layers/routeVehicleRestrictedLayers.ts","../src/routing/layers/summaryBubbleLayers.ts","../src/routing/types/waypointDisplayProps.ts","../src/routing/layers/waypointLayers.ts","../src/routing/layers/routingLayers.ts","../src/routing/resources/index.ts","../src/routing/util/config.ts","../src/routing/util/displayChargingStops.ts","../src/routing/util/displayTrafficSectionProps.ts","../src/routing/util/displayWaypoints.ts","../src/routing/util/guidance.ts","../src/routing/util/routeSections.ts","../src/routing/util/routeSelection.ts","../src/routing/util/routes.ts","../src/routing/RoutingModule.ts","../src/init/styleInputBuilder.ts","../src/init/buildMapOptions.ts","../src/shared/localization.ts","../src/TomTomMap.ts","../src/traffic/types/trafficAreaAnalyticsConfig.ts","../src/traffic/layers/areaAnalyticsLayers.ts","../src/traffic/util/areaAnalyticsGeoJSONTiles.ts","../src/traffic/TrafficAreaAnalyticsModule.ts","../src/traffic/filters/trafficFilters.ts","../src/traffic/TrafficFlowModule.ts","../src/traffic/util/incidentDirectionChevron.ts","../src/traffic/util/readIncidentPalette.ts","../src/traffic/layers/incidentDetailsLayers.ts","../src/traffic/TrafficIncidentOverlayModule.ts","../src/traffic/TrafficIncidentsModule.ts","../src/traffic/types/trafficCommonConfig.ts","../src/utils/paddedBounds.ts"],"sourcesContent":["import type { LineString } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { TrafficFlowModuleFeature } from '../types/trafficFlowFeature';\n\n/**\n * @ignore\n */\nexport const FLOW_TAGS = [\n 'road_category',\n 'road_subcategory',\n 'relative_speed',\n 'left_hand_traffic',\n 'road_closure',\n 'absolute_speed',\n 'part_of_two_way_road',\n 'openlr',\n 'display_class',\n] as const;\n\n/**\n * @ignore\n */\nexport const trafficFlowMapping = (feature: MapGeoJSONFeature): TrafficFlowModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry as LineString,\n properties: {\n roadCategory: properties?.road_category,\n leftHandTraffic: Boolean(properties?.left_hand_traffic),\n roadClosure: Boolean(properties?.road_closure),\n relativeSpeed: properties?.relative_speed,\n ...(properties?.road_subcategory && { roadSubcategory: properties.road_subcategory }),\n ...(properties?.absolute_speed && { absoluteSpeed: properties.absolute_speed }),\n ...(properties?.part_of_two_way_road && { partOfTwoWayRoad: Boolean(properties.part_of_two_way_road) }),\n ...(properties?.openlr && { openlr: properties.openlr }),\n ...(properties?.display_class && { displayClass: properties.display_class }),\n },\n };\n};\n","import {\n generateId,\n iconToTrafficIncidentCategory,\n indexedMagnitudes,\n type TrafficIncidentCategory,\n} from '@tomtom-org/maps-sdk/core';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { TrafficIncidentsModuleFeature } from '../types/trafficIncidentsFeature';\n\n/**\n * @ignore\n */\nexport const INCIDENT_TAGS = [\n 'icon_category',\n 'left_hand_traffic',\n 'magnitude_of_delay',\n 'road_category',\n 'road_subcategory',\n 'id',\n 'description',\n 'delay',\n 'part_of_two_way_road',\n 'start_time',\n 'end_time',\n 'probability_of_occurrence',\n 'number_of_reports',\n 'last_report_time',\n 'average_speed_kmph',\n 'openlr',\n 'time_validity',\n 'display_class',\n] as const;\n\n/**\n * @ignore\n */\nexport const incidentToIconCategoryMapping: Record<TrafficIncidentCategory, number> = {\n other: 0,\n accident: 1,\n fog: 2,\n danger: 3,\n rain: 4,\n frost: 5,\n jam: 6,\n 'lane-closed': 7,\n 'road-closed': 8,\n roadworks: 9,\n wind: 10,\n flooding: 11,\n 'animals-on-road': 12,\n 'narrow-lanes': 13,\n 'broken-down-vehicle': 14,\n} as const;\n\n/**\n * @ignore\n */\nexport const trafficIncidentMapping = (feature: MapGeoJSONFeature): TrafficIncidentsModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry,\n properties: {\n id: properties.id ?? generateId(), // generateID is a failsafe but properties.id should always be present in the vector tile data\n description: properties?.description_0 ?? '',\n category: iconToTrafficIncidentCategory(properties?.icon_category_0),\n magnitudeOfDelay: indexedMagnitudes[properties?.magnitude_of_delay],\n roadCategory: properties?.road_category,\n roadSubcategory: properties?.road_subcategory,\n leftHandTraffic: Boolean(properties?.left_hand_traffic),\n ...(properties?.delay && { delayInSeconds: properties.delay }),\n ...(properties?.part_of_two_way_road && { partOfTwoWayRoad: Boolean(properties.part_of_two_way_road) }),\n ...(properties?.start_time && { startTime: new Date(properties.start_time) }),\n ...(properties?.end_time && { endTime: new Date(properties.end_time) }),\n ...(properties?.probability_of_occurrence && {\n probabilityOfOccurrence: properties.probability_of_occurrence,\n }),\n ...(properties?.number_of_reports && { numberOfReports: properties.number_of_reports }),\n ...(properties?.last_report_time && { lastReportTime: new Date(properties.last_report_time) }),\n ...(properties?.average_speed_kmph && { averageSpeedKmph: properties.average_speed_kmph }),\n ...(properties?.openlr && { openlr: properties.openlr }),\n ...(properties?.time_validity && { timeValidity: properties.time_validity }),\n ...(properties?.display_class && { displayClass: properties.display_class }),\n ...(properties?.point_type && { pointType: properties.point_type }),\n },\n };\n};\n","import type { StyleModule } from '../init';\n\n/**\n * @ignore\n */\nexport const notInTheStyle = (actionText: string): Error =>\n new Error(`Trying to ${actionText} while it is not in the map style. Did you exclude it when loading the map?`);\n\n/**\n * @ignore\n */\nexport const cannotAddStyleModuleToCustomStyle = (styleModule: StyleModule): Error =>\n new Error(`Trying to add style module ${styleModule} to the custom style!`);\n","/**\n * @ignore\n */\nexport const isDOMImageSupported = (): boolean =>\n typeof document != 'undefined' && typeof DOMParser != 'undefined' && typeof btoa != 'undefined';\n\n/**\n * @ignore\n */\nexport const svgToImg = (svgDomElement: SVGElement): HTMLImageElement => {\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const img = document.createElement('img');\n img.src = `data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgDomElement))}`;\n return img;\n};\n","import { SVGIconStyleOptions } from '../types';\nimport pinSvgRaw from './pin.svg?raw';\n\n/**\n * Helper function to parse SVG string (typically imported from ...svg?raw) to SVGElement\n * @ignore\n */\nexport const parseSvg = (svgString: string): SVGElement =>\n new DOMParser().parseFromString(svgString, 'image/svg+xml').documentElement as unknown as SVGElement;\n\n// Applies `fillColor` / `outlineColor` / `outlineOpacity` overrides to an SVG\n// that follows the `#background` / `#outline` id convention.\nconst applySvgStyleOptions = (element: SVGElement, options: SVGIconStyleOptions | undefined): SVGElement => {\n if (options?.fillColor) {\n element.querySelector('#background')?.setAttribute('fill', options.fillColor);\n }\n if (options?.outlineColor) {\n element.querySelector('#outline')?.setAttribute('fill', options.outlineColor);\n }\n if (options?.outlineOpacity !== undefined) {\n element.querySelector('#outline')?.setAttribute('fill-opacity', options.outlineOpacity.toString());\n }\n return element;\n};\n\n/**\n * @ignore\n */\nexport const pinSvg = (options: SVGIconStyleOptions | undefined): SVGElement =>\n applySvgStyleOptions(parseSvg(pinSvgRaw), options);\n","import { isNil } from 'lodash-es';\n\n/**\n * Throws an error if the given value is not defined.\n * @ignore\n */\nexport function assertDefined<T>(value: T | null | undefined): asserts value is T {\n if (isNil(value)) {\n throw new Error(`${value} must not be null/undefined. Something went wrong with its initialization.`);\n }\n}\n\n/**\n * Returns the given value as its defined type, or throws an error if it isn't defined.\n * @ignore\n * @throws\n */\nexport const asDefined = <T>(value: T | null | undefined): T => {\n assertDefined(value);\n return value;\n};\n","/**\n * Class-independent feature-id recovery and lookup, shared by the event subsystem and the\n * source/layer caches. Operate on the minimal {@link RenderedRef} shape, so they accept\n * MapLibre `MapGeoJSONFeature`s, plain GeoJSON `Feature`s, and cached originals alike.\n */\n\n/** Minimal shape needed to look a feature up by id. @ignore */\nexport type RenderedRef = { id?: string | number; properties?: { id?: unknown } | null };\n\n/** A feature together with its index in the array it was found in. @ignore */\nexport type IndexedFeature<F> = { feature: F; index: number };\n\n/**\n * Recover the SDK-assigned id of a feature, preferring `properties.id` over the top-level `id`.\n *\n * The SDK always stamps the real id into `properties.id` and uses `promoteId: 'id'`, so MapLibre\n * mirrors it onto the top-level `id` — the two are normally identical. The exception is **clustered**\n * GeoJSON sources: clustering disables `promoteId`, and MapLibre's supercluster then assigns its own\n * **synthetic** numeric top-level `id` (0, 1, 2, …) to rendered features. Preferring the top-level\n * `id` there would return that synthetic value and miss the cache, whereas `properties.id` is still\n * the real id. So read `properties.id` first, falling back to the top-level `id` (e.g. vector-tile\n * features that only carry a top-level id). Returns `undefined` when neither is set.\n * @ignore\n */\nexport const renderedRefId = (rendered: RenderedRef): string | number | undefined => {\n const propId = (rendered.properties as { id?: unknown } | null)?.id;\n // `properties.id` is `unknown`: trust it as the ref id only when it's actually a string/number,\n // else fall back to the (already-typed) top-level id. Keeps the declared return type honest.\n return typeof propId === 'string' || typeof propId === 'number' ? propId : rendered.id;\n};\n\n/**\n * Find the first feature whose {@link renderedRefId} equals `id`, with its index, or\n * `undefined` (also for a nullish `id`).\n * @ignore\n */\nexport const findFeatureByRefId = <F extends RenderedRef>(\n features: readonly F[],\n id: string | number | undefined,\n): IndexedFeature<F> | undefined => {\n if (id === undefined || id === null) return undefined;\n for (let index = 0; index < features.length; index++) {\n if (renderedRefId(features[index]) === id) return { feature: features[index], index };\n }\n return undefined;\n};\n","import type { Map, Source, SourceSpecification } from 'maplibre-gl';\n\n/**\n * Contains a source relevant for TomTom Maps SDK JS.\n * * The source might already be initialized from the map style, or it might be initialized here.\n * @ignore\n */\nexport class TomTomMapSource<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> {\n constructor(\n readonly id: string,\n readonly spec?: SOURCE_SPEC,\n public runtimeSource?: RUNTIME_SOURCE,\n ) {}\n\n ensureAddedToMap(map: Map) {\n if (!this.runtimeSource) {\n if (!map.getSource(this.id) && this.spec) {\n map.addSource(this.id, this.spec);\n }\n this.runtimeSource = map.getSource(this.id) as RUNTIME_SOURCE;\n }\n }\n}\n","import type { FeatureCollection } from 'geojson';\nimport type {\n GeoJSONSource,\n GeoJSONSourceSpecification,\n LayerSpecification,\n Map,\n Source,\n SourceSpecification,\n} from 'maplibre-gl';\nimport { asDefined } from './assertionUtils';\nimport { findFeatureByRefId, type IndexedFeature } from './featureId';\nimport { TomTomMapSource } from './TomTomMapSource';\nimport type {\n ByIdOrIndex,\n CleanEventStateOptions,\n CleanEventStatesOptions,\n LayerSpecFilter,\n LayerSpecWithSource,\n PutEventStateOptions,\n SourceWithLayerIDs,\n SourceWithLayers,\n ToBeAddedLayerSpec,\n ToBeAddedLayerSpecWithoutSource,\n} from './types';\n\n/**\n * Contains a source and the layers to render its data.\n * @ignore\n */\nexport abstract class AbstractSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n LAYER_SPEC extends LayerSpecification = LayerSpecification,\n> {\n readonly _layerSpecs: LAYER_SPEC[];\n private _sourceAndLayerIDs!: SourceWithLayerIDs;\n\n constructor(\n readonly map: Map,\n readonly source: TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>,\n layerSpecs: LAYER_SPEC[],\n ) {\n this._layerSpecs = layerSpecs;\n this._updateSourceAndLayerIDs();\n }\n\n isAnyLayerVisible(filter?: LayerSpecFilter): boolean {\n return this.getLayerSpecs(filter).some((layer) => this.isLayerVisible(layer));\n }\n\n areAllLayersVisible(filter?: LayerSpecFilter): boolean {\n return this.getLayerSpecs(filter).every((layer) => this.isLayerVisible(layer));\n }\n\n private getLayerSpecs(filter?: LayerSpecFilter) {\n return filter ? this._layerSpecs.filter(filter) : this._layerSpecs;\n }\n\n _updateSourceAndLayerIDs(): void {\n this._sourceAndLayerIDs = { sourceID: this.source.id, layerIDs: this._layerSpecs.map((layer) => layer.id) };\n }\n\n private isLayerVisible(layer: LayerSpecification): boolean {\n // If the layer is no longer attached to the map (e.g. an out-of-band style swap removed\n // it before this snapshot got refreshed), `getLayoutProperty` returns `undefined` and\n // also fires a MapLibre error. Treat a missing layer as hidden, mirroring\n // `setLayersVisible` which skips missing layers silently.\n if (!this.map.getLayer(layer.id)) return false;\n return this.map.getLayoutProperty(layer.id, 'visibility') !== 'none';\n }\n\n setLayersVisible(visible: boolean, filter?: LayerSpecFilter): void {\n for (const layerSpec of this.getLayerSpecs(filter)) {\n // The layer may have been removed by an out-of-band style swap\n // (e.g. `setStyle()` between calls) — `setLayoutProperty` on a\n // missing id throws inside MapLibre. Skip silently; the next\n // `show()` will re-add the layer via `ensureAddedToMap`.\n if (!this.map.getLayer(layerSpec.id)) continue;\n this.map.setLayoutProperty(layerSpec.id, 'visibility', visible ? 'visible' : 'none', { validate: false });\n }\n }\n\n get sourceAndLayerIDs(): SourceWithLayerIDs {\n return this._sourceAndLayerIDs;\n }\n\n equalSourceAndLayerIDs(other: SourceWithLayers): boolean {\n return (\n this.sourceAndLayerIDs.sourceID === other.sourceAndLayerIDs.sourceID &&\n this.sourceAndLayerIDs.layerIDs.length === other.sourceAndLayerIDs.layerIDs.length &&\n this.sourceAndLayerIDs.layerIDs.every((id, index) => id === other.sourceAndLayerIDs.layerIDs[index])\n );\n }\n}\n\n/**\n * @ignore\n * @param loadedMap\n * @param sourceIDs\n */\nexport const filterLayersBySources = (loadedMap: Map, sourceIDs: string[]): LayerSpecWithSource[] =>\n loadedMap\n .getStyle()\n .layers.filter((layer) => sourceIDs.includes((layer as LayerSpecWithSource)?.source)) as LayerSpecWithSource[];\n\n/**\n * Source with layers which are coming from the downloaded TT map style.\n * * Examples are parts of the base map, traffic, pois, and hillshade.\n * @ignore\n */\nexport class StyleSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> extends AbstractSourceWithLayers<SourceSpecification, RUNTIME_SOURCE> {\n constructor(map: Map, runtimeSource: RUNTIME_SOURCE, filter?: LayerSpecFilter) {\n let layers = filterLayersBySources(map, [runtimeSource.id]);\n if (filter) {\n layers = layers.filter(filter);\n }\n super(\n map,\n new TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>(\n runtimeSource.id,\n map.getStyle().sources[runtimeSource.id] as SOURCE_SPEC,\n runtimeSource,\n ),\n layers,\n );\n }\n}\n\n/**\n * Source with layers which originally is not in the map style, but it's to be added after the map is initialized.\n * @ignore\n */\nexport class AddedSourceWithLayers<\n SOURCE_SPEC extends SourceSpecification = SourceSpecification,\n RUNTIME_SOURCE extends Source = Source,\n> extends AbstractSourceWithLayers<SOURCE_SPEC, RUNTIME_SOURCE, ToBeAddedLayerSpec> {\n constructor(map: Map, sourceId: string, sourceSpec: SOURCE_SPEC, layerSpecs: ToBeAddedLayerSpecWithoutSource[]) {\n super(\n map,\n new TomTomMapSource<SOURCE_SPEC, RUNTIME_SOURCE>(sourceId, sourceSpec),\n // We ensure the source ID is assigned to the layers:\n layerSpecs.map((layerSpec) => ({ ...layerSpec, source: sourceId }) as ToBeAddedLayerSpec),\n );\n }\n\n private ensureLayersAddedToMap(): void {\n for (const layerSpec of this._layerSpecs) {\n if (!this.map.getLayer(layerSpec.id)) {\n this.map.addLayer(layerSpec, layerSpec.beforeID);\n }\n }\n }\n\n ensureAddedToMapWithVisibility(visible: boolean, addLayersToMap: boolean): void {\n this.source.ensureAddedToMap(this.map);\n if (addLayersToMap) {\n this.ensureLayersAddedToMap();\n this.setLayersVisible(visible);\n }\n }\n}\n\nconst emptyFeatureCollection: FeatureCollection = { type: 'FeatureCollection', features: [] };\n\n/**\n * Options for the {@link GeoJSONSourceWithLayers.show} method.\n * @ignore\n */\nexport type ShowOptions = {\n /**\n * When `true` (default), layer visibility is automatically set based on whether\n * the feature collection is non-empty. Pass `false` from state-update calls\n * (e.g. `putEventState`, `cleanEventState`) to avoid unintentionally hiding or\n * showing layers when only feature state changes.\n *\n * @defaultValue true\n */\n automaticVisibility?: boolean;\n};\n\n/**\n * Subset of {@link GeoJSONSourceSpecification} fields that {@link GeoJSONSourceWithLayers}\n * can be configured with at construction time. Forwarded verbatim to MapLibre.\n * @ignore\n */\nexport type GeoJSONSourceClusterOptions = Pick<\n GeoJSONSourceSpecification,\n 'cluster' | 'clusterRadius' | 'clusterMaxZoom' | 'clusterMinPoints' | 'clusterProperties'\n>;\n\n/**\n * @ignore\n */\nexport class GeoJSONSourceWithLayers<T extends FeatureCollection = FeatureCollection> extends AddedSourceWithLayers<\n GeoJSONSourceSpecification,\n GeoJSONSource\n> {\n shownFeatures: T = emptyFeatureCollection as T;\n\n constructor(\n map: Map,\n sourceId: string,\n layerSpecs: ToBeAddedLayerSpecWithoutSource[],\n addLayersToMap = true,\n clusterOptions?: GeoJSONSourceClusterOptions,\n ) {\n // MapLibre does not reuse the given feature ID. Either we generate it on the fly or use the one from properties via promotedId value.\n // We must generate \"id\" property based on the feature id on the fly on \"prepareForDisplay\" functions.\n // `promoteId` is incompatible with `cluster: true` (MapLibre validates cluster sources without it),\n // so we omit it whenever clustering is enabled.\n const sourceSpec: GeoJSONSourceSpecification = clusterOptions?.cluster\n ? { type: 'geojson', data: emptyFeatureCollection, ...clusterOptions }\n : { type: 'geojson', data: emptyFeatureCollection, promoteId: 'id', ...(clusterOptions ?? {}) };\n super(map, sourceId, sourceSpec, layerSpecs);\n this.ensureAddedToMapWithVisibility(false, addLayersToMap);\n }\n\n show(featureCollection: T, { automaticVisibility = true }: ShowOptions = {}): void {\n this.shownFeatures = featureCollection;\n asDefined(this.source.runtimeSource).setData(featureCollection);\n if (automaticVisibility) {\n this.setLayersVisible(!!featureCollection.features.length);\n }\n }\n\n /**\n * Canonical id-based lookup into `shownFeatures.features` via {@link findFeatureByRefId}\n * (first match wins on duplicate ids). Returns `undefined` for a nullish or absent id.\n */\n findById(id: string | number | undefined): IndexedFeature<T['features'][number]> | undefined {\n return findFeatureByRefId(this.shownFeatures.features, id);\n }\n\n clear(): void {\n this.show(emptyFeatureCollection as T);\n }\n\n private findFeature(options: ByIdOrIndex) {\n if ('index' in options) return this.shownFeatures.features[options.index];\n if ('id' in options) return this.findById(options.id)?.feature;\n return undefined;\n }\n\n putEventState(options: PutEventStateOptions) {\n const mode = options.mode ?? 'put';\n if (mode === 'put') {\n for (const feature of this.shownFeatures.features) {\n if (feature.properties?.eventState === options.state) {\n delete feature.properties.eventState;\n }\n }\n }\n\n const feature = this.findFeature(options);\n if (feature) {\n feature.properties = { ...feature.properties, eventState: options.state };\n }\n\n if (options.show !== false) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n\n cleanEventState(options: CleanEventStateOptions) {\n const feature = this.findFeature(options);\n\n if (feature?.properties?.eventState) {\n delete feature?.properties?.eventState;\n if (options?.show !== false) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n }\n\n cleanEventStates(options?: CleanEventStatesOptions) {\n let changed = false;\n for (const feature of this.shownFeatures.features) {\n if (!options?.states || options.states.includes(feature.properties?.eventState)) {\n delete feature.properties?.eventState;\n changed = true;\n }\n }\n\n if (options?.show !== false && changed) {\n this.show(this.shownFeatures, { automaticVisibility: false });\n }\n }\n}\n","import type { GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport { DEFAULT_COMMON_BASE_URL, generateTomTomHeaders, isProxyCredentialsMode } from '@tomtom-org/maps-sdk/core';\nimport type {\n FilterSpecification,\n Map,\n MapGeoJSONFeature,\n RequestParameters,\n ResourceType,\n StyleImageMetadata,\n} from 'maplibre-gl';\nimport { InternalTomTomMapParams, StandardStyle, StandardStyleID, StyleInput, StyleModule } from '../init';\nimport type { TomTomMap } from '../TomTomMap';\nimport { FLOW_TAGS } from '../traffic/util/trafficFlowMapping';\nimport { INCIDENT_TAGS } from '../traffic/util/trafficIncidentMapping';\nimport { cannotAddStyleModuleToCustomStyle } from './errorMessages';\nimport { svgToImg } from './imageUtils';\nimport { parseSvg } from './resources';\nimport { AbstractSourceWithLayers, filterLayersBySources } from './SourceWithLayers';\nimport type { LightDark, ToBeAddedLayerSpec, ToBeAddedLayerSpecWithoutSource } from './types';\n\n/**\n * Wait until the map is ready.\n * @param tomtomMap The TomTomMap instance.\n * @returns {Promise<boolean>} Returns a Promise<boolean>\n */\nexport const waitUntilMapIsReady = async (tomtomMap: TomTomMap): Promise<void> => {\n if (!tomtomMap.mapReady) {\n await tomtomMap.mapLibreMap.once('styledata');\n // Recursively waiting for map to be ready (in case of style changes quickly in succession):\n await waitUntilMapIsReady(tomtomMap);\n }\n};\n\n/**\n * Wait until the source is ready.\n * @param tomtomMap The TomTomMap instance.\n * @param sourceId we want to check for.\n * @returns {Promise<boolean>} Returns a Promise<boolean>\n */\nexport const waitUntilSourceIsLoaded = async (tomtomMap: TomTomMap, sourceId: string): Promise<void> => {\n if (!tomtomMap.mapLibreMap.getSource(sourceId) || !tomtomMap.mapLibreMap.isSourceLoaded(sourceId)) {\n await tomtomMap.mapLibreMap.once('sourcedata');\n }\n};\n\nconst isTomTomHostname = (hostname: string): boolean =>\n hostname === 'api.tomtom.com' || hostname.endsWith('.api.tomtom.com');\n\n/**\n * Vector tiles are served from prefixed-subdomain CDN hosts\n * (a./b./c./d.api.tomtom.com) for browser-parallelism. Rewrite all of those\n * plus the bare `api.tomtom.com` host to the proxy. In demo-BFF mode\n * (`isDemoBffMode`) drop any inbound `key` param too: TomTom's edge bakes\n * the request's key into tile URLs returned in the style JSON, and the\n * proxy injects its own server-side — leaving the client-visible key would\n * just leak it to the browser console.\n *\n * Returns the original URL unchanged when it isn't a TomTom host or when\n * we're not in proxy mode.\n */\nconst rewriteForProxy = (url: string, baseURL: string, isProxyMode: boolean, isDemoBffMode: boolean): string => {\n if (!isProxyMode) return url;\n try {\n const parsed = new URL(url);\n if (!isTomTomHostname(parsed.hostname)) return url;\n if (isDemoBffMode) {\n parsed.searchParams.delete('key');\n }\n return baseURL + parsed.pathname + parsed.search + parsed.hash;\n } catch {\n return url;\n }\n};\n\n/**\n * For traffic-incident / traffic-flow tile endpoints, override the\n * server-side `tags` filter to the SDK's tracked-incident set so MapLibre\n * gets the data the SDK actually knows how to render.\n */\nconst injectTrafficTags = (url: URL): void => {\n if (url.pathname.includes('incidents')) {\n url.searchParams.set('tags', INCIDENT_TAGS.join(','));\n } else if (url.pathname.includes('flow')) {\n url.searchParams.set('tags', FLOW_TAGS.join(','));\n }\n};\n\ndeclare global {\n /**\n * Installed by the demo-BFF sandpack bootstrap. Resolves once the proxy\n * session cookie is valid and not about to expire, re-minting it first\n * when needed. See ensureFreshSession in proxyBootstrap.ts. Declared as a\n * global var (not a Window member) so it can be read off `globalThis`,\n * which also works on the main thread where window === globalThis.\n */\n var __DEMO_BFF_ENSURE_SESSION__: (() => Promise<void>) | undefined;\n}\n\n/**\n * Gate a request on a fresh demo-BFF session when the bootstrap installed\n * its hook. MapLibre awaits transformRequest results on the MAIN thread\n * before handing the request to its tile worker — which makes this the one\n * place that can hold back worker-fetched tiles until the session cookie is\n * renewed (the workers' own `fetch` is unreachable from here; they rely on\n * the browser attaching whatever cookie exists when the request starts).\n * Without the hook (direct mode, non-sandpack consumers) this stays fully\n * synchronous. A failed renewal lets the request proceed (it will 401, same\n * as without the gate) rather than wedging the map.\n * @ignore\n */\nconst gateOnDemoBffSession = (result: RequestParameters): RequestParameters | Promise<RequestParameters> => {\n const ensureSession = globalThis.__DEMO_BFF_ENSURE_SESSION__;\n if (typeof ensureSession !== 'function') return result;\n return ensureSession().then(\n () => result,\n () => result,\n );\n};\n\n/**\n * Inject TomTom custom headers (and, when a proxy `commonBaseURL` is\n * configured, rewrite tile URLs + attach credentials) on requests issued\n * by MapLibre.\n *\n * In \"proxy mode\" (commonBaseURL points away from api.tomtom.com), tile\n * URLs baked into the style JSON still arrive here as `api.tomtom.com/...`\n * because MapLibre fetches them directly. We rewrite them to flow through\n * the configured base URL, and — in demo-BFF mode — set\n * `credentials: 'include'` so the session cookie travels with each tile.\n *\n * @ignore\n * @param params Global SDK Map configuration\n */\nexport const transformRequest = (params: Partial<GlobalConfig>) => {\n const baseURL = params.commonBaseURL ?? DEFAULT_COMMON_BASE_URL;\n // commonBaseURL points at something other than TomTom — could be a\n // demo-BFF or a customer's own customServiceBaseURL. We always rewrite\n // tile hostnames to flow through it.\n const isProxyMode = baseURL !== DEFAULT_COMMON_BASE_URL;\n // Demo-BFF-style mode: the proxy injects the key server-side (no apiKey), so\n // we strip key= and attach `credentials: 'include'`. customServiceBaseURL\n // keeps its apiKey and is excluded (its backend may serve CORS as `*`).\n const isDemoBffMode = isProxyCredentialsMode(params);\n\n return (url: string, resourceType?: ResourceType): RequestParameters | Promise<RequestParameters> => {\n const rewrittenUrl = rewriteForProxy(url, baseURL, isProxyMode, isDemoBffMode);\n const isProxyUrl = isProxyMode && rewrittenUrl.startsWith(baseURL);\n const useCredentials = isDemoBffMode && isProxyUrl;\n\n // Hostname-based TomTom detection — a substring check like\n // `url.includes('tomtom.com')` would also match lookalikes\n // (`tomtom.com.evil.example`) or a `tomtom.com` in the query string,\n // and then run TomTom header/tag injection on unintended hosts. Match\n // the registrable domain (tomtom.com or any *.tomtom.com subdomain) by\n // hostname — broader than the api-only `isTomTomHostname` used for the\n // rewrite, since headers apply to all genuine TomTom hosts. The\n // try/catch guards against invalid/relative URLs.\n let isTomTomUrl = false;\n try {\n const host = new URL(url).hostname;\n isTomTomUrl = host === 'tomtom.com' || host.endsWith('.tomtom.com');\n } catch {\n isTomTomUrl = false;\n }\n\n if (!isTomTomUrl && !isProxyUrl) {\n return { url: rewrittenUrl };\n }\n\n if (resourceType === 'Image') {\n return useCredentials\n ? gateOnDemoBffSession({ url: rewrittenUrl, credentials: 'include' })\n : { url: rewrittenUrl };\n }\n\n const parsedUrl = new URL(rewrittenUrl);\n injectTrafficTags(parsedUrl);\n\n const result: RequestParameters = {\n url: parsedUrl.toString(),\n headers: { ...generateTomTomHeaders(params) },\n };\n if (useCredentials) {\n result.credentials = 'include';\n return gateOnDemoBffSession(result);\n }\n return result;\n };\n};\n\n/**\n * Compares two MapLibre features by ID.\n * @ignore\n */\nexport const areBothDefinedAndEqual = (\n featureA: MapGeoJSONFeature | undefined,\n featureB: MapGeoJSONFeature | undefined,\n): boolean => !!featureA && !!featureB && featureA.id === featureB.id;\n\ntype LayerProps = {\n id: string;\n layout?: any;\n paint?: any;\n minzoom?: number;\n maxzoom?: number;\n filter?: FilterSpecification;\n};\n\n/**\n * Applies the layout and paint properties from newLayoutPaint\n * while unsetting (setting as undefined) the ones from previousSpec which no longer exist in newLayoutPaint.\n * * This allows for a quick change of a layer visuals without removing-re-adding the layer.\n * @ignore\n * @param newLayerProps The new layer from which to apply layout/pain props.\n * @param prevLayerProps The previous layer to ensure layout/paint props are removed.\n * @param map\n */\nexport const changeLayerProps = (newLayerProps: LayerProps, prevLayerProps: LayerProps, map: Map) => {\n const layerId = newLayerProps.id;\n if (newLayerProps.maxzoom !== prevLayerProps.maxzoom || newLayerProps.minzoom !== prevLayerProps.minzoom) {\n map.setLayerZoomRange(\n layerId,\n newLayerProps.minzoom ?? map.getMinZoom(),\n newLayerProps.maxzoom ?? map.getMaxZoom(),\n );\n }\n map.setFilter(layerId, newLayerProps.filter, { validate: false });\n for (const property of Object.keys(prevLayerProps.layout ?? [])) {\n if (!newLayerProps.layout?.[property]) {\n map.setLayoutProperty(layerId, property, undefined, { validate: false });\n }\n }\n for (const property of Object.keys(prevLayerProps.paint ?? [])) {\n if (!newLayerProps.paint?.[property]) {\n map.setPaintProperty(layerId, property, undefined, { validate: false });\n }\n }\n for (const [property, value] of Object.entries(newLayerProps.paint ?? [])) {\n map.setPaintProperty(layerId, property, value, { validate: false });\n }\n\n for (const [property, value] of Object.entries(newLayerProps.layout ?? [])) {\n map.setLayoutProperty(layerId, property, value, { validate: false });\n }\n};\n\n/**\n * Applies the layer properties from each layer of newLayoutPaints\n * while unsetting (setting as undefined) the ones from the corresponding layer from prevLayoutPaints\n * which no longer exist in the new one.\n * * The two layer inputs are expected to be parallel arrays.\n * * This allows for quick changes of layer visuals without removing-re-adding the layers.\n * @ignore\n */\nexport const changeLayersProps = (newLayerProps: LayerProps[], prevLayerProps: LayerProps[], map: Map) => {\n newLayerProps.forEach((layoutPaint, index) => changeLayerProps(layoutPaint, prevLayerProps[index], map));\n};\n\n/**\n * Handles new layer specs for the provided source. It will remove layers no longer present,\n * update existing layers and add new one if needed to the source.\n * Adding layers to the map needs to be done correctly, so after calling this method, you should call addLayersInCorrectOrder.\n * If ID of layer to be added already is present on map, MapLibre will through exception.\n * @param newLayersSpecs new layer specification for provided source.\n * @param oldLayersSpecs current layer specification for provided source.\n * @param sourceWithLayers provided source that contains layers.\n * @param map provided map libre map object.\n * @ignore\n */\nexport const updateLayersAndSource = (\n newLayersSpecs: ToBeAddedLayerSpecWithoutSource[],\n oldLayersSpecs: ToBeAddedLayerSpecWithoutSource[],\n sourceWithLayers: AbstractSourceWithLayers,\n map: Map,\n): void => {\n // map layers by id in object for easier access, reduces number of loops\n const newLayersMap: Record<string, ToBeAddedLayerSpecWithoutSource> = newLayersSpecs.reduce(\n (acc, cur) => ({ ...acc, [cur.id]: cur }),\n {},\n );\n const oldLayersMap: Record<string, ToBeAddedLayerSpecWithoutSource> = oldLayersSpecs.reduce(\n (acc, cur) => ({ ...acc, [cur.id]: cur }),\n {},\n );\n\n // we need to store layers in four arrays, layers to add ID, layers to remove ID and layers to update\n const layersToAdd: string[] = [];\n const layersToRemove: string[] = [];\n const newLayersToUpdate: ToBeAddedLayerSpecWithoutSource[] = [];\n const oldLayersToUpdate: ToBeAddedLayerSpecWithoutSource[] = [];\n Object.keys(newLayersMap).forEach((key) => {\n if (oldLayersMap[key]) {\n newLayersToUpdate.push(newLayersMap[key]);\n oldLayersToUpdate.push(oldLayersMap[key]);\n } else {\n layersToAdd.push(key);\n }\n });\n Object.keys(oldLayersMap).forEach((key) => {\n if (!newLayersMap[key]) {\n layersToRemove.push(key);\n }\n });\n\n // remove the old layers no longer present in new layer specification\n const layerSpecs: ToBeAddedLayerSpec[] = sourceWithLayers._layerSpecs;\n layersToRemove.forEach((layerId) => {\n map.removeLayer(layerId);\n for (let i = 0; i < layerSpecs.length; i++) {\n if (layerSpecs[i].id === layerId) {\n layerSpecs.splice(i, 1);\n break;\n }\n }\n });\n // add new layers\n layersToAdd.forEach((layerId) => {\n // add layer spec and map\n const toBeAddedLayerSpec: ToBeAddedLayerSpec = {\n ...newLayersMap[layerId],\n source: sourceWithLayers.source.id,\n } as ToBeAddedLayerSpec;\n layerSpecs.push(toBeAddedLayerSpec);\n });\n sourceWithLayers._updateSourceAndLayerIDs();\n // update existing layers\n changeLayersProps(newLayersToUpdate, oldLayersToUpdate, map);\n};\n\n/**\n * Adds the given layers to the map ensuring they respect their \"beforeID\" properties.\n * * We need to make sure that layers are added in the correct Z order because one layer may depend on another layer.\n * @param layersToAdd\n * @param map MapLibre map\n * @ignore\n */\nexport const addLayers = (layersToAdd: ToBeAddedLayerSpec[], map: Map): void => {\n const layerIdsAlreadyOnMap = new Set<string>();\n const addLayer = (layer: ToBeAddedLayerSpec): void => {\n // we can safely add this layer\n if (!map.getLayer(layer.id)) {\n map.addLayer({ ...layer, layout: { ...layer.layout, visibility: 'none' } }, layer.beforeID);\n }\n layerIdsAlreadyOnMap.add(layer.id);\n };\n\n const mapIdDependency: Record<string, ToBeAddedLayerSpec[]> = {};\n\n layersToAdd.forEach((layer) => {\n if (layer.beforeID) {\n if (layerIdsAlreadyOnMap.has(layer.beforeID) || map.getLayer(layer.beforeID)) {\n layerIdsAlreadyOnMap.add(layer.beforeID);\n addLayer(layer);\n } else if (mapIdDependency[layer.beforeID]) {\n // we cannot add this layer yet\n mapIdDependency[layer.beforeID].push(layer);\n } else {\n mapIdDependency[layer.beforeID] = [layer];\n }\n } else {\n addLayer(layer);\n }\n });\n\n // try to process the rest of layers\n while (Object.keys(mapIdDependency).length > 0) {\n const idsWeCanProcess = Object.keys(mapIdDependency).filter((id) => layerIdsAlreadyOnMap.has(id));\n if (!idsWeCanProcess.length) {\n console.error(\n `Some layers cannot be added. Check for non-existing layers, or circular beforeID dependencies for the following: ${JSON.stringify(Object.keys(mapIdDependency))}`,\n );\n return;\n }\n idsWeCanProcess.forEach((id) => {\n mapIdDependency[id].forEach((layer) => addLayer(layer));\n delete mapIdDependency[id];\n });\n }\n};\n\n/**\n * Adding a style-based module to the given style input, if possible.\n * * This results in a style input which will include such style-based module (e.g. include traffic layers).\n * @param style which we want to update.\n * @param styleModule module we want to add.\n * @ignore\n */\nexport const updateStyleWithModule = (style: StyleInput | undefined, styleModule: StyleModule): StyleInput => {\n switch (typeof style) {\n case 'undefined':\n return { type: 'standard', include: [styleModule] };\n case 'string':\n // this is a standard style\n return { type: 'standard', id: style, include: [styleModule] };\n default:\n if (style.type === 'standard') {\n if (style.include) {\n return { ...style, include: [...style.include, styleModule] };\n }\n return { ...style, include: [styleModule] };\n }\n throw cannotAddStyleModuleToCustomStyle(styleModule);\n }\n};\n\n/**\n * Checks if the source is missing and try to add it to the map by reloading its style.\n * @param map the TomTom map instance.\n * @param sourceId id of the source.\n * @param styleModule style module of the source.\n * @ignore\n */\nexport const ensureAddedToStyle = async (map: TomTomMap, sourceId: string, styleModule: StyleModule): Promise<void> => {\n if (!map.mapLibreMap.getSource(sourceId)) {\n const mapLibreMap = map.mapLibreMap;\n if (!mapLibreMap.isStyleLoaded()) {\n // we let the map settle before changing its style again, so the previous style/data load goes smoother:\n await mapLibreMap.once('idle');\n }\n map.setStyle(updateStyleWithModule(map.getStyle(), styleModule));\n await waitUntilSourceIsLoaded(map, sourceId);\n\n await mapLibreMap.once('styledata');\n // we're loading a bunch of style layers to the map, and we hide them all by default:\n // see TomTomMap.handleStyleData for similar logic\n for (const layer of filterLayersBySources(mapLibreMap, [sourceId])) {\n mapLibreMap.setLayoutProperty(layer.id, 'visibility', 'none', { validate: false });\n }\n // Since we just changed the style visibility, we ensure to wait until the style data is changed before returning to prevent race conditions:\n await mapLibreMap.once('styledata');\n }\n};\n\n/**\n * Sets the given image on the map (loading it if necessary), either adding or updating it.\n * @ignore\n */\nexport const addOrUpdateImage = async (\n mode: 'if-not-in-sprite' | 'add-or-update',\n imageId: string,\n imageToLoad: string | HTMLImageElement,\n map: Map,\n options?: Partial<StyleImageMetadata>,\n) => {\n // defensive check (should not happen but we cannot let it crash):\n if (!imageToLoad) {\n console.warn(`addOrUpdateImage called with empty image for ID ${imageId}`);\n return;\n }\n\n // Helper function to add or update the image\n const addOrUpdateToMap = (imgElement: HTMLImageElement | ImageData | ImageBitmap) => {\n const imageExists = map.hasImage(imageId);\n if (imageExists && mode == 'add-or-update') {\n map.updateImage(imageId, imgElement);\n } else if (!imageExists) {\n map.addImage(imageId, imgElement, options);\n }\n };\n\n const ensureImageLoaded = (imgElement: HTMLImageElement) => {\n // An image is successfully loaded if it's complete AND has valid dimensions\n // (naturalWidth > 0 ensures the image didn't fail to load)\n if (imgElement.complete) {\n if (imgElement.naturalWidth > 0) {\n addOrUpdateToMap(imgElement);\n } else {\n // Image is complete but failed to load\n console.warn(`Failed to load image for ID ${imageId}`);\n }\n } else {\n imgElement.onload = () => addOrUpdateToMap(imgElement);\n imgElement.onerror = () => console.warn(`Failed to load image for ID ${imageId}`);\n }\n };\n\n if (typeof imageToLoad === 'string') {\n if (imageToLoad.includes('<svg')) {\n // Supporting raw SVGs:\n const imgElement = svgToImg(parseSvg(imageToLoad));\n ensureImageLoaded(imgElement);\n } else {\n // Expecting image URL, so the image needs to be downloaded first:\n addOrUpdateToMap((await map.loadImage(imageToLoad)).data);\n }\n } else {\n // Expecting HTMLImageElement, wait for it to be loaded\n ensureImageLoaded(imageToLoad);\n }\n};\n\n/**\n * Returns the light/dark theme for a known standard style.\n * @param standardStyleID\n */\nconst getStandardStyleTheme = (standardStyleID: StandardStyleID): LightDark => {\n switch (standardStyleID) {\n case 'standardDark':\n case 'drivingDark':\n case 'monoDark':\n case 'satellite':\n return 'dark';\n default:\n return 'light';\n }\n};\n\n/**\n * Returns the light/dark theme for a given style input.\n * * Unknown standard styles and custom styles are considered as 'light' theme.\n * @param styleInput The style input to check. If not provided, 'light' is returned.\n * @ignore\n */\nexport const getStyleLightDarkTheme = (styleInput?: StyleInput): LightDark => {\n if (typeof styleInput === 'string') {\n return getStandardStyleTheme(styleInput);\n }\n const standardStyle = styleInput as StandardStyle;\n if (standardStyle?.id) {\n return getStandardStyleTheme(standardStyle.id);\n }\n return 'light';\n};\n\n/**\n * Adds the large POI sprite to the map style.\n * @ignore\n */\nexport const addPinCategoriesSpriteToStyle = async (mapParams: InternalTomTomMapParams, mapLibreMap: Map) => {\n const params = new URLSearchParams();\n // Proxy deployments leave apiKey empty and let the proxy inject the real\n // key server-side. Skip the param entirely rather than emitting `key=`.\n if (mapParams.apiKey) {\n params.set('key', mapParams.apiKey);\n }\n params.set('poi', `poi_${getStyleLightDarkTheme(mapParams.style)}`);\n params.set('apiVersion', '1');\n params.set('apiChannel', 'preview');\n mapLibreMap.setSprite(`${mapParams.commonBaseURL}/maps/orbis/assets/sprites/2.*/sprite?${params}`, {\n validate: false,\n });\n};\n","import type { Map } from 'maplibre-gl';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { EventsProxy } from './EventsProxy';\nimport { waitUntilMapIsReady } from './mapUtils';\nimport type { MapModuleCommonConfig, SourcesWithLayers, SourceWithLayerIDs } from './types';\n\n/**\n * Whether a map module is based on a map style or on added GeoJSON data.\n */\ntype MapModuleSource = 'style' | 'geojson';\n\n/**\n * Base class for all Maps SDK map modules.\n *\n * This abstract class provides the foundation for creating map modules that can display\n * and manage various types of data on a TomTom map. It handles module lifecycle management,\n * including initialization, configuration updates, and automatic restoration after map style changes.\n *\n * @remarks\n * All map modules extend this class to ensure consistent behavior across the SDK.\n * The class manages the module's sources, layers, and configuration, automatically\n * handling map style changes by restoring the module's state when needed.\n *\n * @typeParam SOURCES_WITH_LAYERS - The type defining the sources and layers used by this module\n * @typeParam CFG - The configuration type for this module, or undefined if no configuration is needed. When defined, must extend MapModuleCommonConfig.\n *\n * @example\n * ```typescript\n * class CustomModule extends AbstractMapModule<MySourcesWithLayers, MyConfig> {\n * constructor(tomtomMap: TomTomMap, config?: MyConfig) {\n * super('geojson', tomtomMap, config);\n * }\n * // Implement abstract methods...\n * }\n * ```\n *\n * @group Shared\n */\nexport abstract class AbstractMapModule<\n SOURCES_WITH_LAYERS extends SourcesWithLayers,\n CFG extends MapModuleCommonConfig | undefined = undefined,\n> {\n private readonly sourceType: MapModuleSource;\n /**\n * @ignore\n */\n protected readonly tomtomMap: TomTomMap;\n /**\n * @ignore\n */\n protected readonly eventsProxy: EventsProxy;\n /**\n * @ignore\n */\n protected readonly mapLibreMap: Map;\n /**\n * @ignore\n */\n protected sourcesWithLayers!: SOURCES_WITH_LAYERS;\n /**\n * @ignore\n */\n protected _sourceAndLayerIDs!: Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs>;\n /**\n * @ignore\n */\n protected config?: CFG;\n /**\n * @ignore\n */\n protected _initializing = true;\n\n /**\n * Handlers registered via events.on('config-change', …).\n * Stored here so the array reference stays stable across multiple events getter calls.\n * @ignore\n */\n protected readonly configChangeHandlers: ((config: CFG | undefined) => void)[] = [];\n\n /**\n * Monotonically increasing index unique to this instance within its concrete module\n * class. Auto-assigned in the constructor based on `new.target`, so each subclass\n * has its own counter without per-subclass boilerplate. Use as a suffix on\n * auto-generated source and layer IDs to keep them stable across style changes and\n * unique across module instances.\n * @ignore\n */\n protected readonly instanceIndex: number;\n\n /**\n * Per-concrete-class instance counter. WeakMap-keyed so unloaded classes can be GC'd.\n * @ignore\n */\n private static readonly instanceCounters = new WeakMap<Function, number>();\n\n /**\n * Indicates that this module is currently adding its sources and layers to the map, so during this time it might not function properly.\n * @see waitUntilModuleReady\n * @private\n */\n private moduleReady = false;\n\n /**\n * Builds this module based on a given Maps SDK map.\n * @param tomtomMap The map. It may or may not be initialized at this stage,\n * but the module ensures to initialize itself once it is.\n * @param sourceType Whether the module is based on a map style or on added GeoJSON data.\n * @param config Optional configuration to initialize directly as soon as the map is ready.\n */\n protected constructor(sourceType: MapModuleSource, tomtomMap: TomTomMap, config?: CFG) {\n const concreteClass = new.target;\n const previousIndex = AbstractMapModule.instanceCounters.get(concreteClass) ?? -1;\n this.instanceIndex = previousIndex + 1;\n AbstractMapModule.instanceCounters.set(concreteClass, this.instanceIndex);\n\n this.sourceType = sourceType;\n this.tomtomMap = tomtomMap;\n this.eventsProxy = tomtomMap._eventsProxy;\n this.mapLibreMap = tomtomMap.mapLibreMap;\n // TODO: we need to find a cleaner separation between initSourcesWithLayers and applyConfig for most modules to prevent double work, particularly with adding layers\n this.initSourcesWithLayers(config);\n this.applyConfig(config);\n this.tomtomMap.addStyleChangeHandler({\n onStyleAboutToChange: () => {\n this.moduleReady = false;\n },\n onStyleChanged: () => this.restoreDataAndConfig(),\n });\n this._initializing = false;\n }\n\n /**\n * Initializes the sources with layers of this module.\n * @param config The optional configuration for the module.\n * @param restore Whether we are restoring an existing module after the map style got reloaded.\n * @protected\n * @ignore\n */\n protected initSourcesWithLayers(config?: CFG, restore?: boolean): void {\n this.moduleReady = false;\n this.sourcesWithLayers = this._initSourcesWithLayers(config, restore);\n this._sourceAndLayerIDs = Object.fromEntries(\n Object.entries(this.sourcesWithLayers).map(([name, sourceWithLayers]) => [\n name,\n sourceWithLayers.sourceAndLayerIDs,\n ]),\n ) as Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs>;\n if (restore) {\n this.eventsProxy.updateIfRegistered(this.sourcesWithLayers);\n }\n // Only if the map is still ready, we consider that the module is ready.\n // Otherwise, we assume there's a quick style change in progress and expect that'll trigger the module to restore itself again.\n if (this.tomtomMap.mapReady) {\n this.moduleReady = true;\n }\n }\n\n /**\n * Initializes the sources with layers for the specific module.\n * @protected\n * @ignore\n */\n protected abstract _initSourcesWithLayers(config?: CFG, restore?: boolean): SOURCES_WITH_LAYERS;\n\n protected async waitUntilModuleReady(): Promise<void> {\n await waitUntilMapIsReady(this.tomtomMap);\n if (!this.moduleReady) {\n await new Promise<void>((resolve) => {\n const interval = setInterval(() => {\n if (this.tomtomMap.mapReady && this.moduleReady) {\n clearInterval(interval);\n resolve();\n }\n }, 200);\n });\n }\n }\n\n /**\n * Applies a configuration to this module.\n *\n * This method updates the module's behavior and appearance based on the provided configuration.\n * The configuration is stored internally and will be automatically reapplied if the map style changes.\n *\n * @param config - The configuration object to apply to the module. Pass `undefined` to reset\n * the configuration to default values.\n *\n * @remarks\n * When a configuration is applied, the module updates its visual representation and behavior\n * accordingly. The configuration persists across map style changes, ensuring consistent\n * module behavior even when the map's base style is modified.\n *\n * @example\n * ```typescript\n * // Apply a new configuration\n * myModule.applyConfig({ visible: true, opacity: 0.8 });\n *\n * // Reset to default configuration\n * myModule.applyConfig(undefined);\n * ```\n *\n * @see {@link resetConfig} for a convenience method to reset configuration\n * @see {@link getConfig} to retrieve the current configuration\n */\n applyConfig(config: CFG | undefined) {\n this.config = this._applyConfig(config);\n this.emitConfigChange();\n }\n\n /**\n * Emits a `config-change` event to all registered handlers.\n * Skipped during module initialisation to avoid spurious events.\n * @ignore\n */\n protected emitConfigChange(): void {\n if (this._initializing) return;\n\n for (const handler of this.configChangeHandlers) {\n handler(this.config);\n }\n }\n\n /**\n * Internal implementation to apply config for the specific module.\n * @param config The config to apply. this.config contains the previous configuration (if any).\n * Once the method returns config, it will be assigned to this.config.\n * @protected\n * @ignore\n */\n protected abstract _applyConfig(config: CFG | undefined): CFG | undefined;\n\n /**\n * Resets the configuration of this module to its default values.\n *\n * This is a convenience method that clears any previously applied configuration\n * and restores the module to its initial state. This is equivalent to calling\n * `applyConfig(undefined)`.\n *\n * @remarks\n * After calling this method, the module will behave as if no configuration was ever applied.\n * Any custom settings, styling, or behavior modifications will be removed and replaced\n * with default values.\n *\n * @example\n * ```typescript\n * // Apply some configuration\n * myModule.applyConfig({ visible: true, opacity: 0.5 });\n *\n * // Later, reset to defaults\n * myModule.resetConfig();\n * ```\n *\n * @see {@link applyConfig} to apply a new configuration\n * @see {@link getConfig} to retrieve the current configuration before resetting\n */\n resetConfig(): void {\n this.applyConfig(undefined);\n }\n\n private async restoreDataAndConfig() {\n // defensively declaring the module as not ready to prevent race conditions:\n this.moduleReady = false;\n if (this.sourceType === 'geojson') {\n // Defer GeoJSON layer restoration by one animation frame to work around a MapLibre\n // glitch where symbol layers can't be added right after a styledata event.\n requestAnimationFrame(() => {\n this.restoreDataAndConfigImpl();\n });\n } else {\n this.restoreDataAndConfigImpl();\n }\n }\n\n /**\n * implementation needed to restore the module state (data and config applied to the module).\n * to be used to restore module state after map style change\n * @protected\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n this.initSourcesWithLayers(this.config, true);\n this._applyConfig(this.config);\n }\n\n /**\n * Retrieves a copy of the current module configuration.\n *\n * This method returns a shallow copy of the configuration object that is currently\n * applied to the module. If no configuration has been applied, it returns `undefined`.\n *\n * @returns A shallow copy of the current configuration object, or `undefined` if no\n * configuration is currently applied. The returned object is a copy to prevent\n * unintended modifications to the internal state.\n *\n * @remarks\n * The returned configuration object is a shallow copy, which means that while the\n * top-level properties are copied, any nested objects or arrays are still referenced\n * from the original configuration. This is sufficient for most use cases but should\n * be kept in mind when dealing with complex configurations.\n *\n * @example\n * ```typescript\n * // Apply a configuration\n * myModule.applyConfig({ visible: true, opacity: 0.8 });\n *\n * // Later, retrieve the current configuration\n * const currentConfig = myModule.getConfig();\n * console.log(currentConfig); // { visible: true, opacity: 0.8 }\n *\n * // When no config is applied\n * myModule.resetConfig();\n * console.log(myModule.getConfig()); // undefined\n * ```\n *\n * @see {@link applyConfig} to modify the configuration\n * @see {@link resetConfig} to clear the configuration\n */\n getConfig() {\n return this.config && { ...this.config };\n }\n\n /**\n * Gets the source and layer identifiers for all sources managed by this module.\n *\n * This property provides access to the MapLibre source and layer IDs that were created\n * and are managed by this module. These IDs can be used to interact directly with\n * MapLibre's API or to identify which layers belong to this module.\n *\n * @returns A record mapping each source name to its corresponding source ID and layer IDs.\n * Each entry contains the MapLibre source identifier and an array of layer identifiers\n * associated with that source.\n *\n * @remarks\n * The returned IDs are useful when you need to:\n * - Directly manipulate layers using MapLibre's native API\n * - Identify which layers on the map belong to this module\n * - Set layer ordering or positioning relative to other layers\n * - Access source or layer properties through MapLibre methods\n *\n * @example\n * ```typescript\n * const ids = myModule.sourceAndLayerIDs;\n * console.log(ids);\n * // {\n * // mySource: {\n * // sourceID: 'my-source-id',\n * // layerIDs: ['layer-1', 'layer-2']\n * // }\n * // }\n *\n * // Use with MapLibre API\n * const map = myModule.mapLibreMap;\n * ids.mySource.layerIDs.forEach(layerId => {\n * map.setLayoutProperty(layerId, 'visibility', 'visible');\n * });\n * ```\n */\n get sourceAndLayerIDs(): Record<keyof SOURCES_WITH_LAYERS, SourceWithLayerIDs> {\n return this._sourceAndLayerIDs;\n }\n}\n","import type { ModuleEvents } from './ModuleEvents';\nimport type { EventType, UserEventHandler } from './types';\nimport { UserEvents } from './UserEvents';\n\n/**\n * Unified events interface that blends user interaction events and module lifecycle events\n * under a single `on` / `off` surface.\n *\n * User event types (`click`, `contextmenu`, `hover`, `long-hover`) are handled by the\n * underlying {@link UserEvents} instance and removed with {@link off}.\n *\n * Module lifecycle event types (`config-change`, `shown-features`) are handled by the\n * underlying {@link ModuleEvents} instance. Each `on` call returns an unsubscribe function.\n * Both user and module event types are also removable in bulk via {@link off}.\n *\n * @typeParam T - Feature type surfaced by user interaction handlers.\n * @typeParam CFG - Module configuration type passed to `config-change` handlers.\n * @typeParam TShown - Data type passed to `shown-features` handlers. Use `never` for\n * modules that have no `show` method.\n *\n * @example\n * ```typescript\n * // User interaction\n * const unsub = places.events.on('click', (feature, lngLat) => {\n * console.log('Clicked place:', feature.properties.name);\n * });\n *\n * // Module lifecycle\n * const unsubConfig = places.events.on('config-change', (config) => {\n * console.log('Config updated:', config);\n * });\n *\n * const unsubShown = places.events.on('shown-features', (features) => {\n * console.log('Shown:', features);\n * });\n *\n * // Cleanup\n * unsub();\n * unsubConfig();\n * unsubShown();\n * places.events.off('click'); // also valid for user events\n * ```\n *\n * @group Events\n */\nexport class CombinedEvents<T, CFG, TShown = never> {\n constructor(\n private readonly userEventsInstance: UserEvents<T>,\n private readonly moduleEventsInstance: ModuleEvents<CFG, TShown>,\n ) {}\n\n /**\n * Subscribe to a user interaction event (`click`, `contextmenu`, `hover`, `long-hover`).\n *\n * @param type - The user event type.\n * @param handler - Called with the feature, click/hover coordinates, all features at the\n * point, and the source configuration.\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('click', (feature, lngLat) => {\n * showPlaceDetails(feature.properties);\n * });\n * unsub(); // remove only this handler\n * ```\n */\n on(type: EventType, handler: UserEventHandler<T>): () => void;\n /**\n * Subscribe to config-change events.\n *\n * Fires after any configuration mutation — `applyConfig`, `setVisible`, or any\n * module-specific setter. The handler receives the module's full updated configuration.\n *\n * @param type - `'config-change'`\n * @param handler - Receives the module's config (or `undefined` when config is cleared).\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = trafficFlow.events.on('config-change', (config) => {\n * toggle.checked = config?.visible ?? false;\n * });\n * unsub();\n * ```\n */\n on(type: 'config-change', handler: (config: CFG | undefined) => void): () => void;\n /**\n * Subscribe to shown-features events.\n *\n * Fires immediately after the module's `show()` method (or equivalent) has rendered\n * new data on the map. Only available on modules that have a `show` method.\n *\n * @param type - `'shown-features'`\n * @param handler - Receives the data that was passed to `show()`.\n * @returns An unsubscribe function. Call it to remove **only this handler**.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('shown-features', (features) => {\n * fitMapToResults(features);\n * });\n * unsub();\n * ```\n */\n on(type: 'shown-features', handler: (features: TShown) => void): () => void;\n on(\n type: EventType | 'config-change' | 'shown-features',\n handler: UserEventHandler<T> | ((config: CFG | undefined) => void) | ((features: TShown) => void),\n ): () => void {\n if (type === 'config-change') {\n return this.moduleEventsInstance.on('config-change', handler as (config: CFG | undefined) => void);\n }\n\n if (type === 'shown-features') {\n return this.moduleEventsInstance.on('shown-features', handler as (features: TShown) => void);\n }\n\n return this.userEventsInstance.on(type, handler as UserEventHandler<T>);\n }\n\n /**\n * Remove **all** handlers for the given event type at once.\n *\n * Accepts both user interaction types (`click`, `contextmenu`, `hover`, `long-hover`) and\n * module lifecycle types (`config-change`, `shown-features`).\n *\n * Prefer the unsubscribe function returned by {@link on} when you need to remove a single\n * handler. Use `off` for bulk teardown — e.g. when unmounting a component that registered\n * multiple handlers of the same type.\n *\n * @param type - The event type whose handlers should all be cleared.\n *\n * @example\n * ```typescript\n * places.events.off('click'); // clear all click handlers\n * places.events.off('config-change'); // clear all config-change handlers\n * ```\n */\n off(type: EventType | 'config-change' | 'shown-features'): void {\n if (type === 'config-change' || type === 'shown-features') {\n this.moduleEventsInstance.off(type);\n } else {\n this.userEventsInstance.off(type);\n }\n }\n}\n","import { isEmpty, remove } from 'lodash-es';\nimport type { LayerSpecification, MapGeoJSONFeature } from 'maplibre-gl';\nimport type { EventHandlerConfig, EventType, SourcesWithLayers, SourceWithLayers, UserEventHandler } from './types';\n\n// Handlers for a (source, event-type) are stored as an array: repeated `on()` calls append and\n// ALL of them fire; `on()` returns a per-handler unsubscribe (see `removeHandler`), while `remove`\n// clears every handler for that (source, type). Note a handler fires for any layer of its source —\n// per-layer-subset handlers within a single source are not distinguished.\ntype SourceEventTypeHandler = {\n sourceWithLayers: SourceWithLayers;\n layerIDs: string[];\n fn: UserEventHandler<any>;\n config?: EventHandlerConfig;\n};\n\ntype SourceEventHandlers = Partial<Record<EventType, SourceEventTypeHandler[]>>;\n\n// Source ID to event handlers for that source:\n// Example:\n// const handlers = {\n// vectorTiles: {\n// click: [\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [\"Buildings\", \"Roads - Major\"],\n// fn: UserEventsHandler...\n// },\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [\"Water\", \"Landuse - Parks],\n// fn: UserEventsHandler...\n// }\n// ],\n// hover: [\n// {\n// sourceWithLayers: SourceWithLayers reference,\n// layerIDs: [...],\n// fn: UserEventsHandler...\n// }\n// ]\n// },\ntype EventHandlers = Record<string, SourceEventHandlers>;\n\nconst matchesLayers = (layers: LayerSpecification[], layerIDs: string[]): boolean =>\n layerIDs.every((layerId, index) => layerId === layers[index].id);\n\n/**\n * @ignore\n */\nexport abstract class AbstractEventProxy {\n protected interactiveLayerIDs: string[] = [];\n protected handlers: EventHandlers = {};\n\n /**\n * Adds the given layers as interactive, so we'll listen to them for hover and click.\n * @param sourceWithLayers The sources and layers to listen to.\n */\n private ensureInteractiveLayerIDsAdded(sourceWithLayers: SourceWithLayers) {\n sourceWithLayers._layerSpecs.forEach((layerSpec) => {\n if (!this.interactiveLayerIDs.includes(layerSpec.id)) {\n this.interactiveLayerIDs.push(layerSpec.id);\n }\n });\n }\n\n /**\n * Register an event listener to the list.\n * @param sourceWithLayers The sources and layers to added.\n * @param handlerFn Function that will handle the event.\n * @param type Type of event to listen to.\n * @param config Optional configuration for the event handler.\n */\n addEventHandler<T = MapGeoJSONFeature>(\n sourceWithLayers: SourceWithLayers,\n handlerFn: UserEventHandler<T>,\n type: EventType,\n config: EventHandlerConfig | undefined,\n ) {\n this.ensureInteractiveLayerIDsAdded(sourceWithLayers);\n const sourceId = sourceWithLayers.source.id;\n\n if (!this.handlers[sourceId]) {\n this.handlers[sourceId] = { [type]: [] };\n }\n this.handlers[sourceId][type] ??= [];\n\n this.handlers[sourceId][type]?.push({\n sourceWithLayers,\n layerIDs: sourceWithLayers._layerSpecs.map((layer) => layer.id),\n fn: handlerFn,\n config,\n });\n }\n\n /**\n * Removes a single handler function for the given source and event type.\n * Other handlers registered for the same source and type are not affected.\n * Unlike {@link remove}, this does not remove the source from the interactive layer list —\n * other handlers may still depend on it.\n * @param sourceWithLayers The source whose handler should be removed.\n * @param type The event type.\n * @param handlerFn The exact function reference to remove.\n */\n removeHandler(sourceWithLayers: SourceWithLayers, type: EventType, handlerFn: UserEventHandler<any>) {\n const sourceEventTypeHandlers = this.handlers[sourceWithLayers.source.id]?.[type];\n if (sourceEventTypeHandlers) {\n remove(sourceEventTypeHandlers, (handler) => handler.fn === handlerFn);\n if (!sourceEventTypeHandlers.length) {\n delete this.handlers[sourceWithLayers.source.id]?.[type];\n if (isEmpty(this.handlers[sourceWithLayers.source.id])) {\n delete this.handlers[sourceWithLayers.source.id];\n }\n }\n }\n }\n\n /**\n * Removes the given sources and layers from the interactive list. When not present, nothing happens.\n * @param type The event type to be removed.\n * @param sourceWithLayers The sources and layers to remove, matched by source and layer IDs.\n */\n remove(sourceWithLayers: SourceWithLayers, type: EventType) {\n const sourceEventTypeHandlers = this.handlers[sourceWithLayers.source.id]?.[type];\n if (sourceEventTypeHandlers) {\n // @ts-ignore\n remove(sourceEventTypeHandlers, (handler) => matchesLayers(sourceWithLayers._layerSpecs, handler.layerIDs));\n // cleaning up empty arrays and objects if necessary:\n if (!sourceEventTypeHandlers.length) {\n delete this.handlers[sourceWithLayers.source.id]?.[type];\n if (isEmpty(this.handlers[sourceWithLayers.source.id])) {\n delete this.handlers[sourceWithLayers.source.id];\n }\n }\n }\n for (const layer of sourceWithLayers._layerSpecs) {\n // @ts-ignore\n remove(this.interactiveLayerIDs, (id) => layer.id === id);\n }\n }\n\n /**\n * Removes all interactive sources and layers.\n */\n removeAll() {\n this.interactiveLayerIDs = [];\n this.handlers = {};\n }\n\n /**\n * Returns whether this source is registered for events (has handlers attached).\n * @param sourceId The source id (should be linked to a SourceWithLayers instance).\n */\n hasSourceID(sourceId: string): boolean {\n return !!this.handlers[sourceId];\n }\n\n /**\n * Look up the {@link SourceWithLayers} registered for the given source ID, if any.\n *\n * The SDK assumes at most one `SourceWithLayers` per MapLibre source ID — each\n * module creates its own source — so any non-empty handler bucket gives us the\n * right reference. We iterate event-type buckets defensively to skip transient\n * empty arrays left mid-`removeHandler` before the bucket itself is deleted.\n *\n * NOTE: if two modules ever register against the same source ID with overlapping\n * layers, this returns the first one we encounter.\n */\n protected sourceWithLayersFor(sourceId: string | undefined): SourceWithLayers | undefined {\n if (!sourceId) return undefined;\n const sourceHandlers = this.handlers[sourceId];\n if (!sourceHandlers) return undefined;\n for (const typeHandlers of Object.values(sourceHandlers)) {\n if (typeHandlers?.length) return typeHandlers[0].sourceWithLayers;\n }\n return undefined;\n }\n\n /**\n * Updates the given sourcesWithLayers, if they have any handlers.\n * * (This is typically called to refresh any registered, stale sourceWithLayers references after a map style has changed).\n * @param sourcesWithLayers The new sources with layers to replace existing ones.\n */\n updateIfRegistered(sourcesWithLayers: SourcesWithLayers): void {\n for (const sourceWithLayers of Object.values(sourcesWithLayers)) {\n const sourceHandlers = this.handlers[sourceWithLayers.source.id];\n if (sourceHandlers) {\n for (const sourceEventTypeHandlers of Object.values(sourceHandlers)) {\n for (const handler of sourceEventTypeHandlers) {\n if (sourceWithLayers.equalSourceAndLayerIDs(handler.sourceWithLayers)) {\n // We keep the reference fresh:\n handler.sourceWithLayers = sourceWithLayers;\n }\n }\n }\n }\n }\n }\n\n protected findHandlers = (\n types: EventType[],\n sourceId: string | undefined,\n layerId: string | undefined,\n ): SourceEventTypeHandler[] =>\n (sourceId &&\n layerId &&\n types.flatMap((type) => {\n const sourceEventTypeHandlers = this.handlers[sourceId]?.[type];\n return sourceEventTypeHandlers?.length === 1\n ? // if there's only handler for that source and type, we just return it, no need to match layers:\n sourceEventTypeHandlers\n : this.handlers[sourceId]?.[type]?.filter((handler) => handler.layerIDs.includes(layerId)) || [];\n })) ||\n [];\n}\n","import type { Feature } from 'geojson';\nimport { isNil, omit } from 'lodash-es';\nimport type { MapGeoJSONFeature, Point2D } from 'maplibre-gl';\nimport { findFeatureByRefId, renderedRefId } from './featureId';\nimport { areBothDefinedAndEqual } from './mapUtils';\nimport { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { EventType, SourceWithLayers } from './types';\n\n/**\n * Keep only the hits from `source`; pass everything through when `source` is undefined.\n * Runs on raw `MapGeoJSONFeature`s, before substitution strips `.source`.\n * @ignore\n */\nexport const scopeToSource = (features: MapGeoJSONFeature[], source: string | undefined): MapGeoJSONFeature[] =>\n source ? features.filter((f) => f.source === source) : features;\n\n/**\n * Collapse duplicate hits keyed on `(source, {@link renderedRefId})`, keeping the first\n * (topmost) occurrence. Features with no recoverable id can't be keyed, so they're all kept.\n * @ignore\n */\nexport const dedupeRenderedFeatures = (features: MapGeoJSONFeature[]): MapGeoJSONFeature[] => {\n const seen = new Set<string>();\n const out: MapGeoJSONFeature[] = [];\n for (const f of features) {\n const refId = renderedRefId(f);\n if (refId === undefined) {\n out.push(f);\n continue;\n }\n const key = `${f.source} ${refId}`;\n if (!seen.has(key)) {\n seen.add(key);\n out.push(f);\n }\n }\n return out;\n};\n\nconst isHighPriority = (eventType: EventType): boolean => eventType === 'click' || eventType === 'contextmenu';\n\n/**\n * Puts or removes the given event state to the right feature in featuresToUpdate based on the\n * @param eventState The new event state to apply or to use as reference.\n * @param featureId The ID of the feature to update within featuresToUpdate.\n * @param featuresToUpdate The features list which will be updated.\n * @param mode If updateInProps, replaces the existing eventState. If removeFromProps, removes the existing eventState.\n * @return The index of the updated feature in the mutated featuresToUpdate array.\n * @ignore\n */\nexport const putEventState = (\n eventState: EventType,\n featureId: string | number | undefined,\n featuresToUpdate: Feature[], // \"featuresToUpdate\" will be mutated\n mode: 'updateInProps' | 'removeFromProps' = 'updateInProps',\n): number | undefined => {\n const { feature, index } = findFeatureByRefId(featuresToUpdate, featureId) || {};\n if (feature && (!isHighPriority(feature.properties?.eventState) || isHighPriority(eventState))) {\n const updatedFeature = {\n ...feature,\n properties:\n mode === 'updateInProps'\n ? { ...feature?.properties, eventState }\n : omit(feature?.properties, 'eventState'),\n };\n featuresToUpdate.splice(index as number, 1, updatedFeature);\n return index;\n }\n return undefined;\n};\n\nconst removeEventStateAndShow = (\n newEventType: EventType,\n rawFeature: MapGeoJSONFeature,\n sourceWithLayers: GeoJSONSourceWithLayers,\n): void => {\n const prevFeaturesToUpdate = [...sourceWithLayers.shownFeatures.features];\n // renderedRefId, not rawFeature.id: clustered sources give the rendered feature a synthetic\n // top-level id, so we must key the cache lookup on the real properties.id.\n const updatedIndex = putEventState(\n newEventType,\n renderedRefId(rawFeature),\n prevFeaturesToUpdate,\n 'removeFromProps',\n );\n if (!isNil(updatedIndex)) {\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: prevFeaturesToUpdate },\n { automaticVisibility: false },\n );\n }\n};\n\n/**\n * Mutates the `eventState` marker on the cached `shownFeatures` so MapLibre paint\n * expressions can react to hover/click. Mutates `prevSourceWithLayers` too when the\n * event moved to a different source. Does not produce a caller-facing feature —\n * the dispatch site resolves that from the post-mutation cache.\n * @ignore\n */\nexport const updateEventState = (\n eventState: EventType,\n eventFeature: MapGeoJSONFeature | undefined,\n prevEventFeature: MapGeoJSONFeature | undefined,\n sourceWithLayers: SourceWithLayers | undefined,\n prevSourceWithLayers: SourceWithLayers | undefined,\n): void => {\n if (eventFeature && sourceWithLayers instanceof GeoJSONSourceWithLayers) {\n const featuresToUpdate = [...sourceWithLayers.shownFeatures.features];\n putEventState(eventState, renderedRefId(eventFeature), featuresToUpdate);\n\n if (prevEventFeature && !areBothDefinedAndEqual(prevEventFeature, eventFeature)) {\n // (we have both current and prev features for this event type)\n if (prevSourceWithLayers === sourceWithLayers) {\n // we undo the event state from prev feature next to the new one (they will be shown below):\n putEventState(eventState, renderedRefId(prevEventFeature), featuresToUpdate, 'removeFromProps');\n } else if (prevSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n // the prev feature is in other source/layers, so we update and show them:\n removeEventStateAndShow(eventState, prevEventFeature, prevSourceWithLayers);\n }\n }\n\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: featuresToUpdate },\n { automaticVisibility: false },\n );\n return;\n }\n if (prevEventFeature && prevSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n removeEventStateAndShow(eventState, prevEventFeature, prevSourceWithLayers);\n }\n};\n\n/**\n * Detects whether there's been a hovering change with the given event and state params.\n * @param hoveringPoint The current hovering pixel coordinates.\n * @param hoveringFeature The current feature being hovered, if any.\n * @param prevHoveredPoint The pixel coordinates from the previous hovering event, if any.\n * @param prevHoveredFeature The previous feature being hovered, if any (could be the same as hoveringFeature).\n * @ignore\n */\nexport const detectHoverState = (\n hoveringPoint: Point2D,\n hoveringFeature: MapGeoJSONFeature | undefined,\n prevHoveredPoint: Point2D | undefined,\n prevHoveredFeature: MapGeoJSONFeature | undefined,\n): {\n /**\n * Whether a hover state change happened, such as no-hover -> hover or vice-versa.\n */\n hoverChanged?: boolean;\n /**\n * Whether the mouse is moving along the hovered feature (not stopped on it).\n */\n mouseInMotionOverHoveredFeature?: boolean;\n} => {\n if (hoveringFeature) {\n if (!prevHoveredFeature) {\n return { hoverChanged: true };\n }\n if (\n (hoveringFeature.id && hoveringFeature.id !== prevHoveredFeature.id) ||\n (hoveringFeature.properties.id && hoveringFeature.properties.id !== prevHoveredFeature.properties.id) ||\n hoveringFeature.source !== prevHoveredFeature.source ||\n // comparing by layer ID is needed when two id-less features from the same source but different layers are compared\n // this can happen when e.g. hovering over different layer groups for the base map\n hoveringFeature.layer.id !== prevHoveredFeature.layer.id\n ) {\n // hovering from one feature to another one (from the same or different layer/source):\n return { hoverChanged: true };\n }\n // (else we're hovering along the same feature)\n if (\n prevHoveredPoint &&\n (hoveringPoint.x - prevHoveredPoint.x != 0 || hoveringPoint.y - prevHoveredPoint.y != 0)\n ) {\n return { mouseInMotionOverHoveredFeature: true };\n }\n } else if (prevHoveredFeature) {\n return { hoverChanged: true };\n }\n return {};\n};\n","import type { Feature } from 'geojson';\nimport type { LngLat, Map, MapGeoJSONFeature, MapMouseEvent, Point2D, PointLike } from 'maplibre-gl';\nimport type { MapEventsConfig } from '../init';\nimport { AbstractEventProxy } from './AbstractEventProxy';\nimport { dedupeRenderedFeatures, detectHoverState, scopeToSource, updateEventState } from './eventUtils';\nimport { renderedRefId } from './featureId';\nimport { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { ClickEventType, EventHandlerConfig, SourceWithLayers } from './types';\n\n// Default values for events\nconst eventsProxyDefaultConfig: Required<MapEventsConfig> = {\n precisionMode: 'box',\n paddingBoxPx: 5,\n cursorOnHover: 'pointer',\n cursorOnMouseDown: 'grabbing',\n cursorOnMap: 'default',\n /**\n * Delayed hover control:\n * * The first hover we do after the map moves is longer\n */\n longHoverDelayAfterMapMoveMS: 800,\n /* Followup hovers with the same non-moving map are quicker (\"hovering around mode\") */\n longHoverDelayOnStillMapMS: 300,\n};\n\n/**\n * This is the place where we handle the user events on the map (mousemove/hover and click mostly).\n * To have full control on hovers and clicks when multiple overlapping layers are present, that logic must be centralized here.\n * @ignore\n */\nexport class EventsProxy extends AbstractEventProxy {\n private readonly map: Map;\n private readonly mapCanvas: HTMLCanvasElement;\n private enabled = true;\n private hoveringLngLat?: LngLat;\n private hoveringPoint?: Point2D;\n private hoveringFeature?: MapGeoJSONFeature;\n private hoveringFeatures?: MapGeoJSONFeature[];\n private hoveringSourceWithLayers?: SourceWithLayers;\n private longHoverTimeoutHandlerID?: number;\n // Control flag to indicate that the coming hover is the first one since the map is \"quiet\" again:\n private firstDelayedHoverSinceMapMove = true;\n private lastClickedFeature?: MapGeoJSONFeature;\n private lastClickedSourceWithLayers?: SourceWithLayers;\n private lastCursorStyle: string;\n // Configuration\n private readonly config: Required<MapEventsConfig>;\n\n constructor(map: Map, config: MapEventsConfig = {}) {\n super();\n this.map = map;\n this.mapCanvas = map.getCanvas();\n this.config = { ...eventsProxyDefaultConfig, ...config };\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n this.lastCursorStyle = this.config.cursorOnMap;\n this.listenToEvents();\n }\n\n private listenToEvents() {\n this.map.on('mousemove', (ev) => this.onMouseMove(ev));\n this.map.on('movestart', () => this.onMouseStart());\n this.map.on('mouseout', () => this.onMouseOut());\n this.map.on('mouseover', (ev) => this.onMouseMove(ev));\n this.map.on('mousedown', () => this.onMouseDown());\n this.map.on('mouseup', () => this.onMouseUp());\n this.map.on('click', (ev) => this.onMapClick('click', ev));\n this.map.on('contextmenu', (ev) => this.onMapClick('contextmenu', ev));\n }\n\n // Enable/Disable Events\n public enable(enabled: boolean) {\n this.enabled = enabled;\n if (!enabled) {\n this.clearLongHoverTimeout();\n }\n }\n\n private toPaddedBounds(point: Point2D): [[number, number], [number, number]] {\n const padding = this.config.paddingBoxPx;\n return [\n // sw:\n [point.x - padding, point.y + padding],\n // ne:\n [point.x + padding, point.y - padding],\n ];\n }\n\n private isEnabled() {\n return this.enabled && !this.map.isMoving();\n }\n\n /**\n * Build the consumer-facing `allEventFeatures`: scope to the firing source, dedupe, then\n * substitute cached typed originals. Scope and dedupe run on the raw features, since\n * `.source` and the rendered id survive only before substitution; the top hit stays first.\n *\n * Scoping leaves only `firingSource`'s hits, so its {@link SourceWithLayers} is resolved once\n * here rather than per feature. For GeoJSON-backed sources we substitute the cached typed\n * original (un-stringified properties, real Dates, nested objects); for everything else — vector\n * tiles, unregistered sources — the MapLibre-rendered feature passes through (`MapGeoJSONFeature\n * extends Feature`).\n */\n private toCallerFeatures(rendered: MapGeoJSONFeature[], firingSource: string | undefined): Feature[] {\n const scoped = dedupeRenderedFeatures(scopeToSource(rendered, firingSource));\n const sourceWithLayers = this.sourceWithLayersFor(firingSource);\n return sourceWithLayers instanceof GeoJSONSourceWithLayers\n ? scoped.map((feature) => sourceWithLayers.findById(renderedRefId(feature))?.feature ?? feature)\n : scoped;\n }\n\n private getRenderedFeatures(point: Point2D): MapGeoJSONFeature[] {\n if (!this.interactiveLayerIDs.length) {\n return [];\n }\n const options = { layers: this.interactiveLayerIDs, validate: false };\n const precision = this.config.precisionMode;\n // first optional attempt right in the given coordinates:\n const renderedFeatures =\n precision === 'point-then-box' || precision === 'point'\n ? this.map.queryRenderedFeatures(point as PointLike, options)\n : [];\n return renderedFeatures.length || precision === 'point'\n ? renderedFeatures\n : // second attempt using 'box' = padded bounds (trying to hit something slightly further from the pointer location)\n this.map.queryRenderedFeatures(this.toPaddedBounds(point), options);\n }\n\n private clearLongHoverTimeout() {\n window.clearTimeout(this.longHoverTimeoutHandlerID);\n }\n\n private restartLongHoverTimeout() {\n this.clearLongHoverTimeout();\n this.longHoverTimeoutHandlerID = window.setTimeout(\n () => this.handleLongHoverTimeout(),\n this.firstDelayedHoverSinceMapMove\n ? this.config.longHoverDelayAfterMapMoveMS\n : this.config.longHoverDelayOnStillMapMS,\n );\n }\n\n private handleLongHoverTimeout() {\n // We avoid firing long hovers when the feature is in clicked state:\n this.firstDelayedHoverSinceMapMove = false;\n\n if (this.hoveringSourceWithLayers) {\n updateEventState('long-hover', this.hoveringFeature, undefined, this.hoveringSourceWithLayers, undefined);\n\n const longHoverHandlers = this.findHandlers(\n ['long-hover'],\n this.hoveringFeature?.source,\n this.hoveringFeature?.layer.id,\n );\n\n // Only build `allEventFeatures` when a handler will read it (see onMouseMove).\n if (longHoverHandlers.length) {\n const callerFeatures = this.toCallerFeatures(this.hoveringFeatures ?? [], this.hoveringFeature?.source);\n for (const handler of longHoverHandlers) {\n handler.fn(\n callerFeatures[0],\n this.hoveringLngLat as LngLat,\n callerFeatures,\n handler.sourceWithLayers,\n );\n }\n }\n }\n }\n\n private onMouseStart() {\n this.firstDelayedHoverSinceMapMove = true;\n this.clearLongHoverTimeout();\n }\n\n private onMouseOut() {\n // Preventing accidental de-hover event if we actually leave the map canvas.\n // Since this could potentially be about jumping into a map popup, so we leave that up to the caller.\n this.clearLongHoverTimeout();\n }\n\n private onMouseDown() {\n this.lastCursorStyle = this.mapCanvas.style.cursor;\n this.mapCanvas.style.cursor = this.config.cursorOnMouseDown;\n }\n\n private onMouseUp() {\n this.mapCanvas.style.cursor = this.lastCursorStyle;\n }\n\n private onMouseMove(ev: MapMouseEvent) {\n if (!this.isEnabled()) {\n // We ensure no unwanted hover handling while disabled or the map moves\n return;\n }\n\n this.hoveringFeatures = this.getRenderedFeatures(ev.point);\n const [hoveredTopFeature] = this.hoveringFeatures;\n\n // Check if the layer has any handlers registered.\n // Since hover is the \"lowest\" event type, having a handler for any event type justifies supporting hover state.\n // However, we'll only fire the hover events if there are handlers for hover specifically.\n if (hoveredTopFeature && !this.hasSourceID(hoveredTopFeature.source)) {\n return;\n }\n\n // hoverChangeDetected: whether a change happened, such as no-hover -> hover or vice versa:\n // mouseInMotionOverHoveredFeature: whether the mouse is moving along the hovered feature (not stopped on it):\n const { hoverChanged, mouseInMotionOverHoveredFeature } = detectHoverState(\n ev.point,\n hoveredTopFeature,\n this.hoveringPoint,\n this.hoveringFeature,\n );\n\n if (hoverChanged || mouseInMotionOverHoveredFeature) {\n this.hoveringLngLat = ev.lngLat;\n this.hoveringPoint = ev.point;\n const prevHoveredFeature = this.hoveringFeature;\n this.hoveringFeature = hoveredTopFeature;\n const prevHoveredSourceWithLayers = this.hoveringSourceWithLayers;\n\n // Hovering basic event states are still processed if any other handlers are registered for that source/layers.\n // We do so because basic hovering states indicate a feature is interactive.\n // (e.g. if there's a click handler, we'll still apply basic hover states, even if we don't fire hover events)\n const firstHandler = this.findHandlers(\n ['hover', 'hover-move', 'long-hover', 'click', 'contextmenu'],\n hoveredTopFeature?.source,\n hoveredTopFeature?.layer.id,\n )?.[0];\n\n this.hoveringSourceWithLayers = this.sourceWithLayersFor(hoveredTopFeature?.source);\n\n // Resolved lazily so the `hover` branch picks up the marker-added spread that\n // `updateEventState` writes back to `shownFeatures` (matches the click path's\n // dispatch order).\n let callerFeatures: Feature[] | undefined;\n\n if (hoverChanged) {\n this.updateHoverCursor(firstHandler?.config);\n\n updateEventState(\n 'hover',\n this.hoveringFeature,\n prevHoveredFeature,\n this.hoveringSourceWithLayers,\n prevHoveredSourceWithLayers,\n );\n\n const hoverHandlers = this.findHandlers(\n ['hover'],\n hoveredTopFeature?.source,\n hoveredTopFeature?.layer.id,\n );\n\n // Only build `allEventFeatures` (scope + dedupe + substitute) when a handler\n // will read it — hover state above is tracked for the cursor/eventState even\n // without hover handlers, so a click-only module pays nothing here.\n if (hoverHandlers.length) {\n callerFeatures = this.toCallerFeatures(this.hoveringFeatures, hoveredTopFeature?.source);\n for (const handler of hoverHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n\n if (mouseInMotionOverHoveredFeature) {\n const hoverMoveHandlers = this.findHandlers(\n ['hover-move'],\n this.hoveringFeature?.source,\n this.hoveringFeature?.layer.id,\n );\n\n if (hoverMoveHandlers.length) {\n callerFeatures ??= this.toCallerFeatures(this.hoveringFeatures, this.hoveringFeature?.source);\n for (const handler of hoverMoveHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n\n this.restartLongHoverTimeout();\n }\n }\n\n private updateHoverCursor(config: EventHandlerConfig | undefined) {\n if (this.hoveringFeature) {\n this.mapCanvas.style.cursor = config?.cursorOnHover ?? this.config.cursorOnHover;\n } else {\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n }\n }\n\n private onMapClick(clickType: ClickEventType, ev: MapMouseEvent) {\n if (!this.isEnabled()) {\n // We avoid any accidental click handling while disabled or the map moves\n return;\n }\n\n const clickedFeatures = this.getRenderedFeatures(ev.point);\n\n const prevClickedFeature = this.lastClickedFeature;\n this.lastClickedFeature = clickedFeatures[0];\n const prevClickedSourceWithLayers = this.lastClickedSourceWithLayers;\n const clickHandlers = this.findHandlers(\n [clickType],\n this.lastClickedFeature?.source,\n this.lastClickedFeature?.layer.id,\n );\n\n // Resolve from the firing click handler, NOT sourceWithLayersFor(source): the high-priority\n // `click` eventState must only be written when this module actually handles clicks. Otherwise\n // clicking a feature of a hover-only module would stick a `click` marker that later hovers\n // (lower priority) can't clear, freezing its highlight.\n this.lastClickedSourceWithLayers = clickHandlers[0]?.sourceWithLayers;\n\n updateEventState(\n clickType,\n this.lastClickedFeature,\n prevClickedFeature,\n this.lastClickedSourceWithLayers,\n prevClickedSourceWithLayers,\n );\n\n // Only build `allEventFeatures` when a handler will read it. onMapClick fires on every\n // map click (no source pre-filter), so a click on empty map or a feature whose module\n // has no click handler does no scope/dedupe/substitution work.\n if (clickHandlers.length) {\n const callerFeatures = this.toCallerFeatures(clickedFeatures, this.lastClickedFeature?.source);\n for (const handler of clickHandlers) {\n handler.fn(callerFeatures[0], ev.lngLat, callerFeatures, handler.sourceWithLayers);\n }\n }\n }\n}\n","/**\n * Key layer IDs from the vector map style for positioning custom layers.\n *\n * @remarks\n * This constant provides reference layer IDs from TomTom's vector map styles that are used\n * as anchors when positioning custom layers in the map's layer stack. By referencing these\n * layer IDs, SDK modules and custom implementations can control where their layers appear\n * in the rendering order (visual stacking).\n *\n * **Understanding Layer Order:**\n *\n * \"Lowest\" refers to position in the layer stack, not physical elevation. Layers are rendered\n * from bottom to top - layers lower in the stack are drawn first and appear underneath layers\n * higher in the stack. When you add a layer \"before\" a reference layer, it's inserted lower\n * in the stack and will be drawn underneath that reference layer.\n *\n * **How SDK Modules Use These IDs:**\n *\n * **RoutingModule** - Positions route visualizations below labels for readability:\n * ```typescript\n * // Routes appear below labels but above the base map\n * const routingModule = await RoutingModule.get(map);\n * // Uses mapStyleLayerIDs.lowestLabel by default\n * ```\n *\n * **GeometriesModule** - Configurable layer positioning for polygon areas:\n * ```typescript\n * // Default: below labels\n * const geometriesModule = await GeometriesModule.get(map);\n *\n * // Custom: below buildings to show at ground level\n * const geometriesModule = await GeometriesModule.get(map, {\n * beforeLayerConfig: 'lowestBuilding'\n * });\n *\n * // On top of everything\n * const geometriesModule = await GeometriesModule.get(map, {\n * beforeLayerConfig: 'top'\n * });\n * ```\n *\n * **Custom Layer Implementation:**\n * ```typescript\n * // Add a highlight layer below labels\n * map.addLayer({\n * id: 'search-results',\n * type: 'fill',\n * source: 'results-source',\n * paint: { 'fill-color': '#ffcc00', 'fill-opacity': 0.4 }\n * }, mapStyleLayerIDs.lowestLabel);\n * ```\n *\n * **Style Persistence:**\n *\n * These layer IDs remain consistent across TomTom map style changes (e.g., switching from\n * 'standardLight' to 'standardDark'). The SDK automatically repositions module layers using\n * the corresponding reference layers in the new style, maintaining the intended visual hierarchy.\n *\n * **Important Notes:**\n * - `lowestBuilding` is not available in the Satellite style\n * - If a reference layer doesn't exist, custom layers will be added on top of the style\n * - Both RoutingModule and GeometriesModule default to `lowestLabel` for optimal visibility\n *\n * @see {@link GeometryBeforeLayerConfig} for GeometriesModule layer positioning options\n * @see {@link RouteLayersConfig} for RoutingModule layer configuration\n *\n * @group Map Style\n */\nexport const mapStyleLayerIDs = {\n /**\n * Country name label layer.\n *\n * @remarks\n * Lower in the stack than most labels. Use to position elements below country\n * labels but above other map features.\n */\n country: 'Places - Country name',\n /**\n * The lowest layer for place labels.\n *\n * @remarks\n * Lower in the stack than city labels. Use to position content below all place\n * name labels while keeping it visible above geographic features.\n */\n lowestPlaceLabel: 'Places - Village / Hamlet',\n /**\n * Points of Interest layer.\n *\n * @remarks\n * Use to position custom content in the layer stack relative to POI icons and labels.\n */\n poi: 'POI',\n /**\n * The lowest labels layer in the stack.\n *\n * @remarks\n * **Most commonly used reference layer.** Positioning layers before this ensures\n * content appears below all text labels, maintaining map readability while keeping\n * visualizations prominent.\n *\n * **Default for SDK modules:**\n * - RoutingModule uses this for route lines and waypoints\n * - GeometriesModule uses this as the default for polygon fills and borders\n *\n * This provides optimal balance between visibility and not obscuring map labels.\n */\n lowestLabel: 'Borders - Treaty label',\n /**\n * The lowest road line layer in the stack.\n *\n * @remarks\n * Represents tunnel railway outlines. Use to position content below the road network,\n * useful for showing base layers or features that should appear beneath transportation\n * infrastructure.\n */\n lowestRoadLine: 'Tunnel - Railway outline',\n /**\n * The lowest building layer in the stack.\n *\n * @remarks\n * Use to position content below 3D building extrusions while keeping it above\n * flat map features like roads and terrain.\n *\n * **Note:** Not available in the Satellite style.\n */\n lowestBuilding: 'Buildings - Underground',\n} as const;\n","/**\n * Source identifier for POI (Point of Interest) vector tiles.\n *\n * @remarks\n * Used to reference the POI layer in the map style, which contains\n * business locations, landmarks, and other points of interest.\n *\n * @group POIs\n */\nexport const POI_SOURCE_ID = 'vectorTiles';\n\n/**\n * Source identifier for hillshade terrain visualization.\n *\n * @remarks\n * References the raster source that provides terrain shading to visualize\n * elevation and topography on the map.\n *\n * @group Hillshade\n */\nexport const HILLSHADE_SOURCE_ID = 'hillshade';\n\n/**\n * Source identifier for base map vector tiles.\n *\n * @remarks\n * References the primary vector tile source containing roads, buildings,\n * land use, water bodies, and other fundamental map features.\n *\n * @group Base Map\n */\nexport const BASE_MAP_SOURCE_ID = 'vectorTiles';\n\n/**\n * Source identifier for traffic incidents vector tiles.\n *\n * @remarks\n * References the vector tile source containing real-time traffic incident data\n * such as accidents, road closures, and construction.\n *\n * @group Traffic Incidents\n */\nexport const TRAFFIC_INCIDENTS_SOURCE_ID = 'vectorTilesIncidents';\n\n/**\n * Source identifier for traffic flow vector tiles.\n *\n * @remarks\n * References the vector tile source containing real-time traffic flow data\n * showing current traffic speeds and congestion levels.\n *\n * @group Traffic Flow\n */\nexport const TRAFFIC_FLOW_SOURCE_ID = 'vectorTilesFlow';\n","/**\n * Lifecycle event module for map modules.\n *\n * Provides a unified API for subscribing to module-level events that are not tied\n * to user interaction with map features. Two event types are supported:\n *\n * - `config-change`: emitted whenever the module's configuration is updated\n * (via `applyConfig`, `resetConfig`, or any module-specific setter that mutates config).\n * - `shown-features`: emitted by modules that have a `show` method, immediately after\n * features are displayed on the map.\n *\n * Each `on()` call returns an unsubscribe function so individual handlers can be\n * removed without clearing the entire listener list.\n *\n * @typeParam TConfig - The module's configuration type.\n * @typeParam TShownFeatures - The type of data passed to `show()`. Use `never` for\n * modules that have no `show` method.\n *\n * @example\n * ```typescript\n * const unsubscribeConfig = trafficFlow.events.on('config-change', (config) => {\n * console.log('New config:', config);\n * });\n *\n * const unsubscribeShown = places.events.on('shown-features', (features) => {\n * console.log('Shown:', features);\n * });\n *\n * // Later:\n * unsubscribeConfig();\n * unsubscribeShown();\n * ```\n *\n * @group Events\n */\nexport class ModuleEvents<TConfig, TShownFeatures = never> {\n constructor(\n private readonly configChangeHandlers: ((config: TConfig | undefined) => void)[],\n private readonly shownFeaturesHandlers: ((features: TShownFeatures) => void)[],\n ) {}\n\n /**\n * Subscribe to config-change events.\n *\n * The handler is called with the module's full current configuration immediately after\n * any mutation — whether through `applyConfig`, a module-specific setter, or `resetConfig`.\n *\n * @param type - `'config-change'`\n * @param handler - Receives the module's config (or `undefined` when config is cleared).\n * @returns An unsubscribe function. Call it to remove **only this handler** without\n * affecting other registered handlers for the same event type.\n *\n * @example\n * ```typescript\n * const unsub = trafficFlow.events.on('config-change', (config) => {\n * toggle.checked = config?.visible ?? false;\n * });\n *\n * // Later:\n * unsub();\n * ```\n */\n on(type: 'config-change', handler: (config: TConfig | undefined) => void): () => void;\n /**\n * Subscribe to shown-features events.\n *\n * The handler is called immediately after a module's `show()` method (or equivalent)\n * has updated the map with new data. Only available on modules that have a `show` method.\n *\n * @param type - `'shown-features'`\n * @param handler - Receives the data that was passed to `show()`.\n * @returns An unsubscribe function. Call it to remove **only this handler** without\n * affecting other registered handlers for the same event type.\n *\n * @example\n * ```typescript\n * const unsub = places.events.on('shown-features', (features) => {\n * fitMapToResults(features);\n * });\n *\n * // Later:\n * unsub();\n * ```\n */\n on(type: 'shown-features', handler: (features: TShownFeatures) => void): () => void;\n on(\n type: 'config-change' | 'shown-features',\n handler: ((config: TConfig | undefined) => void) | ((features: TShownFeatures) => void),\n ): () => void {\n if (type === 'config-change') {\n const configHandler = handler as (config: TConfig | undefined) => void;\n this.configChangeHandlers.push(configHandler);\n return () => {\n const index = this.configChangeHandlers.indexOf(configHandler);\n if (index !== -1) {\n this.configChangeHandlers.splice(index, 1);\n }\n };\n }\n\n const shownHandler = handler as (features: TShownFeatures) => void;\n this.shownFeaturesHandlers.push(shownHandler);\n return () => {\n const index = this.shownFeaturesHandlers.indexOf(shownHandler);\n if (index !== -1) {\n this.shownFeaturesHandlers.splice(index, 1);\n }\n };\n }\n\n /**\n * Remove **all** handlers for the given event type at once.\n *\n * Prefer the unsubscribe function returned by {@link on} when you need to remove a\n * single handler. Use `off` for bulk teardown — e.g. when unmounting a component that\n * registered multiple handlers.\n *\n * @param type - The event type whose handlers should all be cleared.\n *\n * @example\n * ```typescript\n * // Clear all config-change listeners in one call\n * trafficFlow.events.off('config-change');\n * ```\n */\n off(type: 'config-change' | 'shown-features'): void {\n if (type === 'config-change') {\n this.configChangeHandlers.length = 0;\n } else {\n this.shownFeaturesHandlers.length = 0;\n }\n }\n}\n","import type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { EventsProxy } from './EventsProxy';\nimport type { EventHandlerConfig, EventType, SourceWithLayers, UserEventHandler } from './types';\n\n/**\n * Event handling interface for map features.\n *\n * Provides a simple API for attaching and removing event handlers for user interactions\n * with map features such as clicks, hovers, and context menus. Each map module (POIs, Routing,\n * Places, etc.) exposes an `events` property that returns a `UserEvents` instance.\n *\n * @typeParam T - The feature type returned in event handlers (extends MapGeoJSONFeature)\n *\n * @remarks\n * **Supported Event Types:**\n * - `click`: User clicks/taps on a feature\n * - `contextmenu`: User right-clicks on a feature (or long-press on mobile)\n * - `hover`: Mouse enters a feature\n * - `long-hover`: Mouse hovers over feature for configured duration (300-800ms)\n *\n * **Event Handler Signature:**\n * ```typescript\n * (feature: T, lngLat: LngLat, features: T[]) => void\n * ```\n *\n * **Parameters:**\n * - `feature`: The primary feature under the cursor\n * - `lngLat`: Geographic coordinates of the event\n * - `features`: All features at this location (when multiple overlap)\n *\n * **Key Features:**\n * - Automatic cursor management (pointer on hover)\n * - Smart event handling for overlapping features\n * - Configurable hover delays\n * - Memory-safe cleanup when removing handlers\n *\n * @example\n * ```typescript\n * // POI click handler\n * const pois = map.pois();\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('Clicked POI:', feature.properties.name);\n * console.log('Location:', lngLat);\n * showInfoWindow(feature.properties);\n * });\n *\n * // Route waypoint hover\n * const routing = map.routing();\n * routing.events.waypoints.on('hover', (waypoint) => {\n * showTooltip(`Waypoint ${waypoint.properties.index}`);\n * });\n *\n * // Long-hover for detailed info\n * pois.events.on('long-hover', (feature) => {\n * loadAndShowDetailedInfo(feature.properties.id);\n * });\n *\n * // Context menu (right-click)\n * routing.events.mainLines.on('contextmenu', (route, lngLat) => {\n * showContextMenu(lngLat, [\n * { label: 'Add waypoint here', action: () => addWaypoint(lngLat) },\n * { label: 'View route details', action: () => showDetails(route) }\n * ]);\n * });\n *\n * // Remove all click handlers\n * pois.events.off('click');\n * ```\n *\n * @example\n * ```typescript\n * // Complete interaction example\n * const places = map.places();\n *\n * // Show name on hover\n * places.events.on('hover', (place) => {\n * tooltip.show(place.properties.name);\n * });\n *\n * // Show details on click\n * places.events.on('click', (place, lngLat) => {\n * sidebar.show({\n * title: place.properties.name,\n * address: place.properties.address.freeformAddress,\n * coordinates: lngLat\n * });\n * });\n *\n * // Cleanup on component unmount\n * onUnmount(() => {\n * places.events.off('hover');\n * places.events.off('click');\n * });\n * ```\n *\n * @group Events\n */\nexport class UserEvents<T = MapGeoJSONFeature> {\n private readonly sources: SourceWithLayers[];\n\n constructor(\n private readonly eventProxy: EventsProxy,\n /**\n * The source(s) whose features this module handles events for.\n * Pass an array to register handlers across multiple sources representing similar data in different display modes (e.g. hexgrid + square\n * in {@link TrafficAreaAnalyticsModule}). A single value is also accepted for\n * backwards compatibility.\n */\n sourcesWithLayers: SourceWithLayers | SourceWithLayers[],\n private readonly config: EventHandlerConfig | undefined,\n /**\n * Optional function that transforms a raw {@link MapGeoJSONFeature} into the\n * feature type `T` exposed by the module. When provided, every feature passed (fired)\n * to event handlers is first run through this function, so callers receive the\n * module's own feature shape instead of the underlying MapLibre feature.\n *\n * When omitted, `T` defaults to `MapGeoJSONFeature` and features are forwarded\n * as-is.\n */\n private readonly mapping?: (feature: MapGeoJSONFeature) => T,\n ) {\n this.sources = Array.isArray(sourcesWithLayers) ? sourcesWithLayers : [sourcesWithLayers];\n }\n\n /**\n * Register an event handler for user interactions with map features.\n *\n * Attaches a callback function that will be invoked when users interact with\n * features in this module (e.g., clicking on a POI, hovering over a route).\n *\n * @param type The type of event to listen for (click, contextmenu, hover, long-hover)\n * @param handler Callback function invoked when the event occurs\n *\n * @returns An unsubscribe function that removes the handler registered by this call.\n *\n * @remarks\n * **Handler Parameters:**\n * - `feature`: The primary feature that triggered the event\n * - `lngLat`: Geographic coordinates [longitude, latitude] of the event\n * - `features`: The features from this module at the event location, de-duplicated (for\n * overlapping features within the module). To inspect features across all modules, query\n * `map.mapLibreMap.queryRenderedFeatures(...)` directly — see {@link UserEventHandler}.\n *\n * **Behavior:**\n * - Multiple handlers per event type are supported — each `on()` adds a handler (it does\n * **not** replace existing ones) and all of them fire. To remove one, call the unsubscribe\n * this returns; to clear them all, use `off(type)`.\n * - Handlers are preserved across map style changes\n * - Cursor automatically changes to pointer on hover\n * - Events respect module visibility (hidden features don't trigger events)\n *\n * **Performance:**\n * - Hover events use spatial indexing for fast lookup\n * - Long-hover has configurable delay to prevent accidental triggers\n * - Event handlers should be lightweight to maintain smooth interaction\n *\n * @example\n * ```typescript\n * // Basic click handler\n * module.events.on('click', (feature, lngLat, features) => {\n * console.log('Clicked feature:', feature.properties);\n * console.log('Location:', lngLat);\n * console.log('All features here:', features.length);\n * });\n *\n * // Hover with tooltip\n * module.events.on('hover', (feature) => {\n * const name = feature.properties.name || 'Unnamed';\n * tooltip.show(name);\n * });\n *\n * // Long-hover for detailed preview\n * module.events.on('long-hover', (feature) => {\n * // Only triggered after hovering for 300-800ms\n * loadPreview(feature.properties.id);\n * });\n *\n * // Right-click context menu\n * module.events.on('contextmenu', (feature, lngLat) => {\n * event.preventDefault(); // Prevent browser context menu\n * showCustomMenu(lngLat, feature);\n * });\n * ```\n *\n * @example\n * ```typescript\n * // Route-specific handlers\n * const routing = map.routing();\n *\n * // Handle route line clicks\n * routing.events.mainLines.on('click', (route) => {\n * highlightRoute(route.properties.routeID);\n * showRouteSummary(route.properties.summary);\n * });\n *\n * // Handle waypoint interactions\n * routing.events.waypoints.on('hover', (waypoint) => {\n * const index = waypoint.properties.index;\n * showTooltip(`Stop ${index + 1}`);\n * });\n * ```\n */\n on(type: EventType, handler: UserEventHandler<T>): () => void {\n const entries: Array<{ source: SourceWithLayers; registeredHandler: UserEventHandler<any> }> = [];\n\n if (this.mapping) {\n const mapping = this.mapping;\n for (const source of this.sources) {\n const wrappedHandler: UserEventHandler<any> = (feature, ...rest) => handler(mapping(feature), ...rest);\n this.eventProxy.addEventHandler(source, wrappedHandler, type, this.config);\n entries.push({ source, registeredHandler: wrappedHandler });\n }\n } else {\n for (const source of this.sources) {\n this.eventProxy.addEventHandler(source, handler, type, this.config);\n entries.push({ source, registeredHandler: handler });\n }\n }\n\n return () => {\n for (const { source, registeredHandler } of entries) {\n this.eventProxy.removeHandler(source, type, registeredHandler);\n }\n };\n }\n\n /**\n * Remove all event handlers for a specific event type.\n *\n * Unregisters the callback function for the specified event type, stopping\n * further event notifications. This is important for cleanup to prevent\n * memory leaks and unwanted behavior.\n *\n * @param type The type of event to stop listening for\n *\n * @remarks\n * **Cleanup Behavior:**\n * - Removes **all** handlers for the specified event type — to remove a single handler, call\n * the unsubscribe function returned by {@link on}\n * - Other event types remain active\n * - Resets cursor behavior for this module\n * - Safe to call multiple times (no error if no handler exists)\n * - Does not affect other modules' event handlers\n *\n * **When to Use:**\n * - Component unmounting/cleanup\n * - Switching between interaction modes\n * - Temporarily disabling interactions\n * - Replacing an existing handler (call `off()` then `on()`)\n *\n * **Best Practices:**\n * - Always clean up event handlers when component unmounts\n * - Remove handlers before removing features from the map\n * - Use framework lifecycle hooks for automatic cleanup\n *\n * @example\n * ```typescript\n * // Remove click handlers\n * module.events.off('click');\n *\n * // Remove all handlers\n * module.events.off('click');\n * module.events.off('hover');\n * module.events.off('long-hover');\n * module.events.off('contextmenu');\n * ```\n *\n * @example\n * ```typescript\n * // React component cleanup\n * useEffect(() => {\n * const pois = map.pois();\n *\n * pois.events.on('click', handlePoiClick);\n * pois.events.on('hover', handlePoiHover);\n *\n * return () => {\n * // Cleanup on unmount\n * pois.events.off('click');\n * pois.events.off('hover');\n * };\n * }, [map]);\n * ```\n *\n * @example\n * ```typescript\n * // Replace handler\n * module.events.off('click'); // Remove old handler\n * module.events.on('click', newHandler); // Add new handler\n *\n * // Disable interactions temporarily\n * const savedHandler = currentHandler;\n * module.events.off('click'); // Disable\n * // ... later ...\n * module.events.on('click', savedHandler); // Re-enable\n * ```\n */\n off(type: EventType) {\n for (const source of this.sources) {\n this.eventProxy.remove(source, type);\n }\n }\n}\n","/**\n * Layer IDs for POIs on the map.\n * @ignore\n */\nexport const poiLayerIDs = ['POI', 'POI - Micro'];\n","import type { POICategory } from '@tomtom-org/maps-sdk/core';\n\n// mappings based on https://docs.tomtom.com/map-display-api/documentation/tomtom-orbis-maps/vector/content#poi\n\n/**\n * POI category mappings that have a direct equivalent in Map Display POI categories.\n * @ignore\n */\nexport const mapDisplayPoiCategoryMappings: Partial<Record<POICategory, string>> = {\n NATIVE_RESERVATION: 'aboriginal_land',\n AIRPORT: 'airport',\n AMUSEMENT_PARK: 'amusement_park',\n ATM: 'atm',\n BANK: 'bank',\n BAR: 'bar',\n BEACH: 'beach_resort',\n ACCESS_GATEWAY: 'border_control',\n FRONTIER_CROSSING: 'border_control',\n BUS_STOP: 'bus_station',\n CAFE: 'cafe',\n CAFE_PUB: 'cafe',\n CAMPING_GROUND: 'camping_ground',\n RENT_A_CAR_FACILITY: 'car_rental',\n CASINO: 'casino_and_gambling',\n CEMETERY: 'cemetery',\n ELECTRIC_VEHICLE_STATION: 'charging_location',\n CINEMA: 'cinema',\n MOVIE_THEATER: 'cinema',\n HEALTH_CARE_SERVICE: 'clinic',\n CLOTHING_SHOP: 'clothing_shop',\n COFFEE_SHOP: 'coffee_shop',\n COLLEGE_OR_UNIVERSITY: 'college_or_university',\n COLLEGE_UNIVERSITY: 'college_or_university',\n AGRICULTURE: 'commercial_area',\n BUSINESS_PARK: 'commercial_area',\n COMMERCIAL_BUILDING: 'commercial_area',\n COMPANY: 'commercial_area',\n RESIDENTIAL_ACCOMMODATION: 'commercial_area',\n EXHIBITION_CONVENTION_CENTER: 'conference_center',\n CLUB_ASSOCIATION: 'consumer_service',\n PUBLIC_AMENITY: 'consumer_service',\n WELFARE_ORGANIZATION: 'consumer_service',\n COURTHOUSE: 'courthouse',\n COMMUNITY_CENTER: 'cultural_center',\n CULTURAL_CENTER: 'cultural_center',\n DENTIST: 'dentist',\n DEPARTMENT_STORE: 'department_store',\n DOCTOR: 'doctor',\n EMERGENCY_MEDICAL_SERVICE: 'emergency_ward_entrance',\n EMERGENCY_ROOM: 'emergency_ward_entrance',\n FAST_FOOD: 'fast_food',\n FERRY_TERMINAL: 'ferry_terminal',\n FIRE_STATION_BRIGADE: 'fire_station',\n FITNESS_CLUB_CENTER: 'fitness_center',\n WINERY: 'food_and_drinks_shop',\n CURRENCY_EXCHANGE: 'foreign_exchange',\n EXCHANGE: 'foreign_exchange',\n GAS_STATION: 'fuel_station',\n GOLD_EXCHANGE: 'gold_exchange',\n GOLF_COURSE: 'golf_course',\n EMBASSY: 'government_office',\n GOVERNMENT_OFFICE: 'government_office',\n HELIPAD_HELICOPTER_LANDING: 'heliport_or_helipad',\n HISTORIC_SITE: 'historic_site',\n HOLIDAY_RENTAL: 'holiday_home',\n GENERAL_HOSPITAL_AND_POLYCLINIC: 'hospital',\n HOSPITAL: 'hospital',\n HOTEL_MOTEL: 'hotel_or_motel',\n ICE_SKATING_RINK: 'ice_rink',\n INDUSTRIAL_BUILDING: 'industrial_area',\n PORT_WAREHOUSE_FACILITY: 'industrial_area',\n PRIMARY_RESOURCE_UTILITY: 'industrial_area',\n GEOGRAPHIC_FEATURE: 'landmark',\n SHOPPING_CENTER: 'mall',\n MANUFACTURING_FACILITY: 'manufacturing_facility',\n MARINA: 'marina',\n MARKET: 'marketplace',\n MEDIA_FACILITY: 'media_facility',\n MOUNTAIN_PASS: 'mountain_pass',\n MUSEUM: 'museum',\n NON_GOVERNMENTAL_ORGANIZATION: 'ngo_office',\n CONCERT_HALL: 'nightlife',\n ENTERTAINMENT: 'nightlife',\n NIGHTLIFE: 'nightlife',\n PARK_RECREATION_AREA: 'park_and_recreation_area',\n TRAIL_SYSTEM: 'park_and_recreation_area',\n LEISURE_CENTER: 'park_and_recreation_facility',\n WATER_SPORT: 'park_and_recreation_facility',\n OPEN_PARKING_AREA: 'parking_facility',\n PARKING_GARAGE: 'parking_facility',\n PHARMACY: 'pharmacy',\n POLICE_STATION: 'police_station',\n POST_OFFICE: 'post_office',\n PRISON_CORRECTIONAL_FACILITY: 'prison',\n PROTECTED_AREA: 'protected_land',\n PUB: 'pub',\n LIBRARY: 'public_library',\n PUBLIC_TRANSPORT_STOP: 'public_transport_stop',\n RAILWAY_STATION: 'railway_station',\n CHURCH: 'religious_site',\n GURUDWARA: 'religious_site',\n MOSQUE: 'religious_site',\n PAGODA: 'religious_site',\n PLACE_OF_WORSHIP: 'religious_site',\n SYNAGOG: 'religious_site',\n TEMPLE: 'religious_site',\n RENT_A_CAR_PARKING: 'rental_car_parking',\n RESEARCH_FACILITY: 'research_facility',\n REST_AREA: 'rest_and_service_area',\n RESTAURANT: 'restaurant',\n RESTAURANT_AREA: 'restaurant',\n SCHOOL: 'school_or_kindergarten',\n SHOP: 'shop_or_store',\n ADVENTURE_SPORTS_FACILITY: 'sport_facility',\n SPORTS_CENTER: 'sport_facility',\n TENNIS_COURT: 'sport_facility',\n STADIUM: 'stadium',\n SUPERMARKETS_HYPERMARKETS: 'supermarket',\n SWIMMING_POOL: 'swimming_pool',\n TAXI_STAND: 'taxi_stand',\n OPERA_HOUSE: 'theater',\n THEATER: 'theater',\n TOLL_GATE: 'toll_booth',\n TOURIST_ATTRACTION: 'tourist_attraction',\n TOURIST_INFORMATION_OFFICE: 'tourist_information',\n TRUCK_STOP: 'truck_stop',\n AUTOMOTIVE_DEALER: 'vehicle_dealer',\n MOTORING_ORGANIZATION_OFFICE: 'vehicle_inspection',\n TRAFFIC_CONTROL_DEPARTMENT: 'vehicle_inspection',\n TRANSPORT_AUTHORITY_VEHICLE_REGISTRATION: 'vehicle_inspection',\n REPAIR_FACILITY: 'vehicle_repair',\n CAR_WASH: 'vehicle_wash',\n VETERINARIAN: 'veterinary',\n HILL: 'viewpoint',\n MOUNTAIN_PEAK: 'viewpoint',\n SCENIC_PANORAMIC_VIEW: 'viewpoint',\n WEIGH_STATION: 'weighbridge',\n AQUATIC_ZOO: 'zoo_or_aquarium',\n ARBORETA_BOTANICAL_GARDENS: 'zoo_or_aquarium',\n ZOOS_ARBORETA_BOTANICAL_GARDEN: 'zoo_or_aquarium',\n} as const;\n\n/**\n * Complete POI category mappings including all POI categories.\n * Extends the original mappings with additional categories mapped to semantically appropriate values.\n * @ignore\n */\nconst completeMapDisplayPoiCategoryMappings: Record<POICategory, string> = {\n ...(mapDisplayPoiCategoryMappings as Record<POICategory, string>),\n // Subtypes and additional categories\n AIRFIELD: 'airport',\n AIRLINE_ACCESS: 'airport',\n PRIVATE_AIRPORT: 'airport',\n PUBLIC_AIRPORT: 'airport',\n ANIMAL_SHELTER: 'animal_shelter',\n DIVERSIFIED_FINANCIALS: 'bank',\n COCKTAIL_BAR: 'bar',\n BEACH_CLUB: 'beach_resort',\n SECURED_ENTRANCE: 'border_control',\n INTERNET_CAFE: 'cafe',\n CAFETERIA: 'cafeteria',\n CARAVAN_SITE: 'camping_ground',\n RECREATIONAL_CAMPING_GROUND: 'camping_ground',\n REST_CAMP: 'camping_ground',\n MAUSOLEUM_GRAVE: 'cemetery',\n CHILD_CARE_FACILITY: 'childcare',\n PRE_SCHOOL: 'childcare',\n DRIVE_IN_MOVIES: 'cinema',\n CHILDRENS_CLOTHES: 'clothing_shop',\n MENS_CLOTHING: 'clothing_shop',\n SPECIALTY_CLOTHING_SHOP: 'clothing_shop',\n WOMENS_CLOTHING: 'clothing_shop',\n JUNIOR_COLLEGE_COMMUNITY_COLLEGE: 'college_or_university',\n COMEDY_CLUB: 'comedy_club',\n ADVERTISING_COMPANY: 'commercial_area',\n AGRICULTURAL_TECHNOLOGY: 'commercial_area',\n AIRLINE_COMPANY: 'commercial_area',\n APARTMENT_RENTAL: 'commercial_area',\n AUTOMOBILE_COMPANY: 'commercial_area',\n BUILDING: 'commercial_area',\n BUSINESS_SERVICES: 'commercial_area',\n BUS_CHARTER_COMPANY: 'commercial_area',\n CABLE_TELEPHONE_COMPANY: 'commercial_area',\n CLEANING_SERVICES: 'commercial_area',\n COMPUTER_DATA_SERVICES: 'commercial_area',\n CONSTRUCTION_COMPANY: 'commercial_area',\n DELIVERY_SERVICE: 'commercial_area',\n ELECTRONICS_COMPANY: 'commercial_area',\n EQUIPMENT_RENTAL: 'commercial_area',\n FARM: 'commercial_area',\n FLATS_APARTMENT_COMPLEX: 'commercial_area',\n HORTICULTURE: 'commercial_area',\n IMPORT_EXPORT_AND_DISTRIBUTION: 'commercial_area',\n INSURANCE_COMPANY: 'commercial_area',\n INVESTMENT_ADVISOR: 'commercial_area',\n LEGAL_SERVICES: 'commercial_area',\n MINING_COMPANY: 'commercial_area',\n MOVING_STORAGE_COMPANY: 'commercial_area',\n OIL_NATURAL_GAS: 'commercial_area',\n PHARMACEUTICAL_COMPANY: 'commercial_area',\n PRIMARY_PRODUCER: 'commercial_area',\n PUBLISHING_TECHNOLOGIES: 'commercial_area',\n REAL_ESTATE_AGENT: 'commercial_area',\n REAL_ESTATE_COMPANY: 'commercial_area',\n RETIREMENT_COMMUNITY: 'commercial_area',\n SERVICE_COMPANY: 'commercial_area',\n SOFTWARE_COMPANY: 'commercial_area',\n TAX_SERVICES: 'commercial_area',\n TELECOMMUNICATIONS: 'commercial_area',\n TRANSPORT_COMPANY: 'commercial_area',\n TRAVEL_AGENT: 'commercial_area',\n WEDDING_SERVICES: 'commercial_area',\n PUBLIC_CALL_BOX: 'consumer_service',\n PUBLIC_TOILET: 'consumer_service',\n GENERAL_PRACTITIONER: 'doctor',\n SPECIALIST: 'doctor',\n AMBULANCE_UNIT: 'emergency_ward_entrance',\n ROAD_RESCUE: 'emergency_ward_entrance',\n MICROBREWERY: 'food_and_drinks_shop',\n BATTLEFIELD: 'historic_site',\n MEMORIAL: 'historic_site',\n BUNGALOW_RENTAL: 'holiday_home',\n CHALET_RENTAL: 'holiday_home',\n COTTAGE_RENTAL: 'holiday_home',\n VILLA_RENTAL: 'holiday_home',\n HORSE_RIDING_CENTER: 'horse_riding',\n BLOOD_BANK: 'hospital',\n HOSPITAL_FOR_WOMEN_AND_CHILDREN: 'hospital',\n HOSPITAL_OF_CHINESE_MEDICINE: 'hospital',\n SPECIAL_HOSPITAL: 'hospital',\n B_B_GUEST_HOUSE: 'hotel_or_motel',\n CABINS_LODGES: 'hotel_or_motel',\n HOSTEL: 'hotel_or_motel',\n HOTEL: 'hotel_or_motel',\n MOTEL: 'hotel_or_motel',\n RESORT: 'hotel_or_motel',\n ICE_CREAM_PARLOR: 'ice_cream_shop',\n ICE_HOCKEY_ARENA: 'ice_rink',\n CHEMICAL_COMPANY: 'industrial_area',\n MECHANICAL_ENGINEERING: 'industrial_area',\n QUARRY: 'industrial_area',\n YOGURT_JUICE_BAR: 'juice_bar',\n ARCH: 'landmark',\n BAY: 'landmark',\n BRIDGE: 'landmark',\n CAPE: 'landmark',\n COVE: 'landmark',\n DAM: 'landmark',\n DUNE: 'landmark',\n ISLAND: 'landmark',\n LOCALE: 'landmark',\n MARSH: 'landmark',\n MONUMENT: 'landmark',\n OASIS: 'landmark',\n OBSERVATORY: 'landmark',\n PARKWAY: 'landmark',\n PLAIN_FLAT: 'landmark',\n PLATEAU: 'landmark',\n RAPIDS: 'landmark',\n REEF: 'landmark',\n RESERVOIR: 'landmark',\n RIVER_CROSSING: 'landmark',\n ROCKS: 'landmark',\n STATUE: 'landmark',\n TOWER: 'landmark',\n TUNNEL: 'landmark',\n VALLEY: 'landmark',\n WATER_HOLE: 'landmark',\n WELL: 'landmark',\n AUTOMOBILE_MANUFACTURING: 'manufacturing_facility',\n MANUFACTURING_COMPANY: 'manufacturing_facility',\n FARMERS_MARKET: 'marketplace',\n FOOD_MARKET: 'marketplace',\n INFORMAL_MARKET: 'marketplace',\n PUBLIC_MARKET: 'marketplace',\n MILITARY_AIRPORT: 'military_airfield',\n FUNERAL_SERVICE_MORTUARIES: 'mourning_facility',\n AMUSEMENT_PLACE: 'nightlife',\n BETTING_STATION: 'nightlife',\n CABARET_THEATER: 'nightlife',\n DISCO_CLUB: 'nightlife',\n FAIRGROUND: 'nightlife',\n JAZZ_CLUB: 'nightlife',\n KARAOKE_CLUB: 'nightlife',\n MUSIC_CENTER: 'nightlife',\n ADVENTURE_VEHICLE_TRAIL: 'park_and_recreation_area',\n FISHING_HUNTING_AREA: 'park_and_recreation_area',\n FOREST_AREA: 'park_and_recreation_area',\n HIKING_TRAIL: 'park_and_recreation_area',\n HISTORICAL_PARK: 'park_and_recreation_area',\n HORSE_RIDING_TRAIL: 'park_and_recreation_area',\n MOUNTAIN_BIKE_TRAIL: 'park_and_recreation_area',\n PARK: 'park_and_recreation_area',\n PICNIC_AREA: 'park_and_recreation_area',\n RECREATION_AREA: 'park_and_recreation_area',\n ROCK_CLIMBING_TRAIL: 'park_and_recreation_area',\n WILDERNESS_AREA: 'park_and_recreation_area',\n OPEN_CAR_PARKING_AREA: 'parking_facility',\n BEAUTY_SALON: 'personal_care_service',\n HAIRDRESSER: 'personal_care_service',\n NAIL_SALON: 'personal_care_service',\n PERSONAL_CARE_FACILITY: 'personal_care_service',\n PERSONAL_SERVICE: 'personal_care_service',\n SAUNA_SOLARIUM_MASSAGE: 'personal_care_service',\n DRUG_STORE: 'pharmacy',\n MARIJUANA_DISPENSARY: 'pharmacy',\n MEDICINAL_MARIJUANA_DISPENSARY: 'pharmacy',\n RECREATIONAL_MARIJUANA_DISPENSARY: 'pharmacy',\n PLANETARIUM: 'planetarium',\n COURIER_DROP_BOX: 'post_depot',\n BUS_LINES: 'public_transport_stop',\n PASSENGER_TRANSPORT_TICKET_OFFICE: 'public_transport_stop',\n STREETCAR_STOP: 'public_transport_stop',\n SUBWAY_STATION: 'public_transport_stop',\n INTERNATIONAL_RAILROAD_STATION: 'railway_station',\n NATIONAL_RAILROAD_STATION: 'railway_station',\n STATION_ACCESS: 'railway_station',\n URBAN_STATION: 'railway_station',\n AFGHAN_RESTAURANT: 'restaurant',\n AFRICAN_RESTAURANT: 'restaurant',\n AMERICAN_RESTAURANT: 'restaurant',\n ARABIAN_RESTAURANT: 'restaurant',\n ARMENIAN_RESTAURANT: 'restaurant',\n ASIAN_RESTAURANT: 'restaurant',\n AUSTRALIAN_RESTAURANT: 'restaurant',\n AUSTRIAN_RESTAURANT: 'restaurant',\n BANQUET_ROOMS: 'restaurant',\n BARBECUE_RESTAURANT: 'restaurant',\n BASQUE_RESTAURANT: 'restaurant',\n BELGIAN_RESTAURANT: 'restaurant',\n BISTRO: 'restaurant',\n BOLIVIAN_RESTAURANT: 'restaurant',\n BRAZILIAN_RESTAURANT: 'restaurant',\n BRITISH_RESTAURANT: 'restaurant',\n BUFFET_RESTAURANT: 'restaurant',\n BULGARIAN_RESTAURANT: 'restaurant',\n BURMESE_RESTAURANT: 'restaurant',\n CALIFORNIAN_RESTAURANT: 'restaurant',\n CAMBODIAN_RESTAURANT: 'restaurant',\n CANADIAN_RESTAURANT: 'restaurant',\n CARIBBEAN_RESTAURANT: 'restaurant',\n CATERING_SERVICES: 'restaurant',\n CHICKEN_RESTAURANT: 'restaurant',\n CHINESE_RESTAURANT: 'restaurant',\n COLOMBIAN_RESTAURANT: 'restaurant',\n CORSICAN_RESTAURANT: 'restaurant',\n CREOLE_RESTAURANT: 'restaurant',\n CREPERIE: 'restaurant',\n CUBAN_RESTAURANT: 'restaurant',\n CZECH_RESTAURANT: 'restaurant',\n DANISH_RESTAURANT: 'restaurant',\n DINNER_THEATER: 'restaurant',\n DONGBEI_RESTAURANT: 'restaurant',\n DOUGHNUT_RESTAURANT: 'restaurant',\n DUTCH_RESTAURANT: 'restaurant',\n EGYPTIAN_RESTAURANT: 'restaurant',\n ENGLISH_RESTAURANT: 'restaurant',\n ETHIOPIAN_RESTAURANT: 'restaurant',\n FINNISH_RESTAURANT: 'restaurant',\n FONDUE_RESTAURANT: 'restaurant',\n FRENCH_RESTAURANT: 'restaurant',\n FUSION_RESTAURANT: 'restaurant',\n GERMAN_RESTAURANT: 'restaurant',\n GREEK_RESTAURANT: 'restaurant',\n GRILL_RESTAURANT: 'restaurant',\n HAMBURGER_RESTAURANT: 'restaurant',\n HAWAIIAN_RESTAURANT: 'restaurant',\n HOT_POT_RESTAURANT: 'restaurant',\n HUNAN_RESTAURANT: 'restaurant',\n HUNGARIAN_RESTAURANT: 'restaurant',\n INDIAN_RESTAURANT: 'restaurant',\n INDONESIAN_RESTAURANT: 'restaurant',\n INTERNATIONAL_RESTAURANT: 'restaurant',\n IRANIAN_RESTAURANT: 'restaurant',\n IRISH_RESTAURANT: 'restaurant',\n ISRAELI_RESTAURANT: 'restaurant',\n ITALIAN_RESTAURANT: 'restaurant',\n JAMAICAN_RESTAURANT: 'restaurant',\n JAPANESE_RESTAURANT: 'restaurant',\n JEWISH_RESTAURANT: 'restaurant',\n KOREAN_RESTAURANT: 'restaurant',\n KOSHER_RESTAURANT: 'restaurant',\n LATIN_AMERICAN_RESTAURANT: 'restaurant',\n LEBANESE_RESTAURANT: 'restaurant',\n MEDITERRANEAN_RESTAURANT: 'restaurant',\n MEXICAN_RESTAURANT: 'restaurant',\n MIDDLE_EASTERN_RESTAURANT: 'restaurant',\n MONGOLIAN_RESTAURANT: 'restaurant',\n MOROCCAN_RESTAURANT: 'restaurant',\n NEPALESE_RESTAURANT: 'restaurant',\n NORWEGIAN_RESTAURANT: 'restaurant',\n ORGANIC_FOOD_RESTAURANT: 'restaurant',\n ORIENTAL_RESTAURANT: 'restaurant',\n PAKISTANI_RESTAURANT: 'restaurant',\n PERUVIAN_RESTAURANT: 'restaurant',\n PHILIPPINE_RESTAURANT: 'restaurant',\n PIZZERIA: 'restaurant',\n POLISH_RESTAURANT: 'restaurant',\n POLYNESIAN_RESTAURANT: 'restaurant',\n PORTUGUESE_RESTAURANT: 'restaurant',\n PUB_FOOD: 'restaurant',\n RUSSIAN_RESTAURANT: 'restaurant',\n SALAD_BAR: 'restaurant',\n SANDWICH_RESTAURANT: 'restaurant',\n SCANDINAVIAN_RESTAURANT: 'restaurant',\n SEAFOOD: 'restaurant',\n SHANDONG_RESTAURANT: 'restaurant',\n SICHUAN_RESTAURANT: 'restaurant',\n SNACKS_RESTAURANT: 'restaurant',\n SOUL_FOOD: 'restaurant',\n SOUP_RESTAURANT: 'restaurant',\n SPANISH_RESTAURANT: 'restaurant',\n STEAK_HOUSE: 'restaurant',\n SUSHI_RESTAURANT: 'restaurant',\n SWEDISH_RESTAURANT: 'restaurant',\n SWISS_RESTAURANT: 'restaurant',\n SYRIAN_RESTAURANT: 'restaurant',\n TAIWANESE_RESTAURANT: 'restaurant',\n TAKEOUT_FOOD: 'restaurant',\n TAPAS_RESTAURANT: 'restaurant',\n TEPPANYAKI_RESTAURANT: 'restaurant',\n THAI_RESTAURANT: 'restaurant',\n TIBETAN_RESTAURANT: 'restaurant',\n TUNISIAN_RESTAURANT: 'restaurant',\n TURKISH_RESTAURANT: 'restaurant',\n VEGETARIAN_RESTAURANT: 'restaurant',\n VENEZUELAN_RESTAURANT: 'restaurant',\n VIETNAMESE_RESTAURANT: 'restaurant',\n WESTERN_RESTAURANT: 'restaurant',\n ART_SCHOOL: 'school_or_kindergarten',\n DANCE_STUDIO_SCHOOL: 'school_or_kindergarten',\n DRIVING_SCHOOL: 'school_or_kindergarten',\n HIGH_SCHOOL: 'school_or_kindergarten',\n LANGUAGE_SCHOOL: 'school_or_kindergarten',\n MIDDLE_SCHOOL: 'school_or_kindergarten',\n PRIMARY_SCHOOL: 'school_or_kindergarten',\n SENIOR_HIGH_SCHOOL: 'school_or_kindergarten',\n VOCATIONAL_SCHOOL: 'school_or_kindergarten',\n AGRICULTURAL_SUPPLIES: 'shop_or_store',\n ANTIQUE_ART_SHOP: 'shop_or_store',\n BAGS_LEATHERWEAR: 'shop_or_store',\n BAKERY: 'shop_or_store',\n BEAUTY_SUPPLIES: 'shop_or_store',\n BOATING_EQUIPMENT_ACCESSORIES: 'shop_or_store',\n BOOK_SHOP: 'shop_or_store',\n BUTCHER: 'shop_or_store',\n CAMERAS_PHOTOGRAPHY: 'shop_or_store',\n CARPET_FLOOR_COVERINGS: 'shop_or_store',\n CHRISTMAS_HOLIDAY_SHOP: 'shop_or_store',\n COMPUTER_COMPUTER_SUPPLIES: 'shop_or_store',\n CONSTRUCTION_MATERIAL_EQUIPMENT: 'shop_or_store',\n CONSUMER_ELECTRONICS: 'shop_or_store',\n CONVENIENCE_STORE: 'shop_or_store',\n CURTAINS_TEXTILES: 'shop_or_store',\n C_DS_DVD_VIDEOS: 'shop_or_store',\n DELICATESSEN: 'shop_or_store',\n DO_IT_YOURSELF_CENTERS: 'shop_or_store',\n DRIVE_THROUGH_BOTTLE_SHOP: 'shop_or_store',\n DRY_CLEANER: 'shop_or_store',\n ELECTRICAL_APPLIANCES_SHOP: 'shop_or_store',\n FACTORY_OUTLET: 'shop_or_store',\n FISHMONGER: 'shop_or_store',\n FLORISTS: 'shop_or_store',\n FOOTWEAR_SHOE_REPAIRS: 'shop_or_store',\n FURNITURE_HOME_FURNISHINGS: 'shop_or_store',\n GARDEN_CENTERS_SERVICES: 'shop_or_store',\n GIFTS_CARDS_NOVELTIES_SOUVENIRS: 'shop_or_store',\n GLASSWARE_CERAMIC_SHOP: 'shop_or_store',\n GLASS_WINDOWS_STORE: 'shop_or_store',\n GREENGROCER: 'shop_or_store',\n GROCERY_STORE: 'shop_or_store',\n HARDWARE_STORE: 'shop_or_store',\n HOBBY_SHOP: 'shop_or_store',\n HOUSE_GARDEN_FURNITURE_FITTINGS: 'shop_or_store',\n JEWELRY_CLOCKS_WATCHES: 'shop_or_store',\n KITCHENS_BATHROOMS: 'shop_or_store',\n LAUNDRY: 'shop_or_store',\n LIGHTING_SHOPS: 'shop_or_store',\n LOCAL_SPECIALITIES_SHOP: 'shop_or_store',\n LOTTERY_SHOP: 'shop_or_store',\n MEDICAL_SUPPLIES_EQUIPMENT: 'shop_or_store',\n MOBILE_PHONE_SHOP: 'shop_or_store',\n MUSIC_INSTRUMENTS_STORE: 'shop_or_store',\n NEWSAGENTS_TOBACCONISTS: 'shop_or_store',\n OFFICE_EQUIPMENT: 'shop_or_store',\n OPTICIAN: 'shop_or_store',\n OTHER_FOOD_SHOPS: 'shop_or_store',\n PAINTING_DECORATING: 'shop_or_store',\n PAWN_SHOP: 'shop_or_store',\n PET_SUPPLIES: 'shop_or_store',\n PHOTOCOPY_SHOP: 'shop_or_store',\n PHOTO_LAB_DEVELOPMENT: 'shop_or_store',\n RECYCLING_SHOP: 'shop_or_store',\n RETAIL_OUTLET: 'shop_or_store',\n SECURITY_PRODUCTS: 'shop_or_store',\n SHOPPING_SERVICE: 'shop_or_store',\n SPECIALTY_FOODS: 'shop_or_store',\n SPORTS_EQUIPMENT_CLOTHING: 'shop_or_store',\n TAILOR_SHOP: 'shop_or_store',\n TOYS_GAMES_SHOP: 'shop_or_store',\n VARIETY_STORE: 'shop_or_store',\n VIDEO_RENTAL_SHOP: 'shop_or_store',\n WHOLESALE_CLUB: 'shop_or_store',\n WINE_SPIRITS: 'shop_or_store',\n ATHLETICS_TRACK: 'sport_facility',\n BASEBALL_PARK: 'sport_facility',\n BASKETBALL_ARENA: 'sport_facility',\n BOWLING_CENTER: 'sport_facility',\n CRICKET_GROUND: 'sport_facility',\n HOCKEY_CLUB: 'sport_facility',\n HORSE_RACING_TRACK: 'sport_facility',\n RACE_TRACK: 'sport_facility',\n RUGBY_GROUND: 'sport_facility',\n SNOOKER_POOL_BILLIARD: 'sport_facility',\n THEMATIC_SPORT_CENTER: 'sport_facility',\n FOOTBALL_STADIUM: 'stadium',\n MOTOR_RACING_STADIUM: 'stadium',\n MULTI_PURPOSE_STADIUM: 'stadium',\n NETBALL_STADIUM: 'stadium',\n SOCCER_STADIUM: 'stadium',\n STOCK_EXCHANGE: 'stock_exchange',\n TAXI_LIMOUSINE_SHUTTLE_SERVICE: 'taxi_stand',\n TEA_HOUSE: 'tea_house',\n AMPHITHEATER: 'theater',\n CAVE: 'tourist_attraction',\n MINERAL_HOT_SPRINGS: 'tourist_attraction',\n NATURAL_TOURIST_ATTRACTION: 'tourist_attraction',\n BOAT_LAUNCHING_RAMP: 'transport_access',\n HARBOR: 'transport_access',\n YACHT_BASIN: 'transport_access',\n ATV_DEALER: 'vehicle_dealer',\n BOAT_DEALER: 'vehicle_dealer',\n BUS_DEALER: 'vehicle_dealer',\n CAR_DEALER: 'vehicle_dealer',\n MOTORCYCLE_DEALER: 'vehicle_dealer',\n RECREATIONAL_VEHICLE_DEALER: 'vehicle_dealer',\n TRUCK_DEALER: 'vehicle_dealer',\n VAN_DEALER: 'vehicle_dealer',\n ROAD_TRAFFIC_CONTROL_CENTER: 'vehicle_inspection',\n BODYSHOP: 'vehicle_repair',\n CAR_GLASS_REPLACEMENT_SHOP: 'vehicle_repair',\n CAR_REPAIR_AND_SERVICE: 'vehicle_repair',\n HOME_APPLIANCE_REPAIR: 'vehicle_repair',\n MOTORCYCLE_REPAIR: 'vehicle_repair',\n TIRE_SERVICE: 'vehicle_repair',\n TRUCK_REPAIR_AND_SERVICE: 'vehicle_repair',\n ANIMAL_SERVICES: 'veterinary',\n RIDGE: 'viewpoint',\n OTHER_WINTER_SPORT: 'winter_sports',\n SKI_RESORT: 'winter_sports',\n WILDLIFE_PARK: 'zoo_or_aquarium',\n ZOO: 'zoo_or_aquarium',\n} as const;\n\n/**\n * Map style POI category type.\n *\n * @remarks\n * Represents all available POI (Point of Interest) categories used in the map style.\n * These categories correspond to Search API classification codes and are used for\n * filtering, styling, and displaying POI icons on the map.\n *\n * Each category maps to a specific icon sprite and can be used with custom icon configurations.\n *\n * @example\n * ```ts\n * const category: MapStylePOICategory = 'RESTAURANT';\n * ```\n *\n * @see {@link POICategoryGroup} - For grouped collections of related categories\n *\n * @group POIs\n */\nexport type MapStylePOICategory = keyof typeof mapDisplayPoiCategoryMappings;\n\n/**\n * @ignore\n */\nexport const toBaseMapPOICategory = (category: POICategory): string => completeMapDisplayPoiCategoryMappings[category];\n\n/**\n * The deduplicated set of base-map POI categories — the right-hand side of\n * {@link completeMapDisplayPoiCategoryMappings}. Each value names the\n * `poi-<value>` sprite the base-map style ships, so this set is what callers\n * (e.g., the `pin-clustered` cluster pin's `match` expression) statically\n * enumerate when they need MapLibre to pre-load every possible sprite into\n * the atlas.\n *\n * @ignore\n */\nexport const baseMapPOICategoryValues: readonly string[] = Array.from(\n new Set(Object.values(completeMapDisplayPoiCategoryMappings)),\n);\n\n/**\n * Maps the base map POI category (see {@link toBaseMapPOICategory}) to the\n * group used by the base map style's `POI`/`POI - Micro` layers for color and\n * micro-icon selection. Groups mirror the match arms in the style's\n * `text-color` expression (e.g., `eat_and_drink`, `lodging`, `driving`).\n * @ignore\n */\nconst baseMapCategoryToGroup: Record<string, string> = {\n // eat_and_drink\n bar: 'eat_and_drink',\n cafe: 'eat_and_drink',\n cafeteria: 'eat_and_drink',\n coffee_shop: 'eat_and_drink',\n fast_food: 'eat_and_drink',\n ice_cream_shop: 'eat_and_drink',\n juice_bar: 'eat_and_drink',\n pub: 'eat_and_drink',\n restaurant: 'eat_and_drink',\n tea_house: 'eat_and_drink',\n // shopping\n clothing_shop: 'shopping',\n department_store: 'shopping',\n food_and_drinks_shop: 'shopping',\n mall: 'shopping',\n marketplace: 'shopping',\n shop_or_store: 'shopping',\n supermarket: 'shopping',\n // finance\n atm: 'finance',\n bank: 'finance',\n foreign_exchange: 'finance',\n gold_exchange: 'finance',\n stock_exchange: 'finance',\n // lodging\n camping_ground: 'lodging',\n holiday_home: 'lodging',\n hotel_or_motel: 'lodging',\n // transport\n airport: 'transport',\n bus_station: 'transport',\n ferry_terminal: 'transport',\n heliport_or_helipad: 'transport',\n marina: 'transport',\n public_transport_stop: 'transport',\n railway_station: 'transport',\n taxi_stand: 'transport',\n transport_access: 'transport',\n // driving\n border_control: 'driving',\n car_rental: 'driving',\n charging_location: 'driving',\n fuel_station: 'driving',\n rest_and_service_area: 'driving',\n toll_booth: 'driving',\n truck_stop: 'driving',\n vehicle_dealer: 'driving',\n vehicle_inspection: 'driving',\n vehicle_repair: 'driving',\n vehicle_wash: 'driving',\n weighbridge: 'driving',\n // parking\n parking_facility: 'parking',\n rental_car_parking: 'parking',\n // healthcare\n clinic: 'healthcare',\n dentist: 'healthcare',\n doctor: 'healthcare',\n emergency_ward_entrance: 'healthcare',\n hospital: 'healthcare',\n pharmacy: 'healthcare',\n veterinary: 'healthcare',\n // education\n childcare: 'education',\n college_or_university: 'education',\n research_facility: 'education',\n school_or_kindergarten: 'education',\n // cultural\n cultural_center: 'cultural',\n historic_site: 'cultural',\n museum: 'cultural',\n planetarium: 'cultural',\n theater: 'cultural',\n // leisure\n amusement_park: 'leisure',\n beach_resort: 'leisure',\n casino_and_gambling: 'leisure',\n cinema: 'leisure',\n comedy_club: 'leisure',\n nightlife: 'leisure',\n park_and_recreation_facility: 'leisure',\n tourist_attraction: 'leisure',\n tourist_information: 'leisure',\n zoo_or_aquarium: 'leisure',\n // sport\n fitness_center: 'sport',\n golf_course: 'sport',\n horse_riding: 'sport',\n ice_rink: 'sport',\n sport_facility: 'sport',\n stadium: 'sport',\n swimming_pool: 'sport',\n winter_sports: 'sport',\n // outdoor\n aboriginal_land: 'outdoor',\n landmark: 'outdoor',\n mountain_pass: 'outdoor',\n park_and_recreation_area: 'outdoor',\n viewpoint: 'outdoor',\n // protected\n protected_land: 'protected',\n // public\n cemetery: 'public',\n courthouse: 'public',\n fire_station: 'public',\n government_office: 'public',\n police_station: 'public',\n post_depot: 'public',\n post_office: 'public',\n prison: 'public',\n public_library: 'public',\n // military\n military_airfield: 'military',\n // religion\n religious_site: 'religion',\n // business\n animal_shelter: 'business',\n commercial_area: 'business',\n conference_center: 'business',\n consumer_service: 'business',\n industrial_area: 'business',\n manufacturing_facility: 'business',\n media_facility: 'business',\n mourning_facility: 'business',\n ngo_office: 'business',\n personal_care_service: 'business',\n};\n\n/**\n * @ignore\n */\nexport const toBaseMapPOIGroup = (category: POICategory): string | undefined => {\n const baseMapCategory = toBaseMapPOICategory(category);\n return baseMapCategory ? baseMapCategoryToGroup[baseMapCategory] : undefined;\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\n\n/**\n * Bold font face used in the TomTom map style.\n *\n * @remarks\n * This is the primary bold font used for prominent labels and headings in the map.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_BOLD_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_BOLD_FONT],\n * 'text-field': ['get', 'name']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_BOLD_FONT = 'Noto-Bold';\n\n/**\n * Regular font face used in the TomTom map style.\n *\n * @remarks\n * This is the standard font used for most text labels on the map.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_REGULAR_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_REGULAR_FONT],\n * 'text-field': ['get', 'description']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_REGULAR_FONT = 'Noto-Regular';\n\n/**\n * Medium weight font face used in the TomTom map style.\n *\n * @remarks\n * This font provides a middle ground between regular and bold weights.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_MEDIUM_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_MEDIUM_FONT],\n * 'text-field': ['get', 'title']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_MEDIUM_FONT = 'Noto-Medium';\n\n/**\n * Italic font face used in the TomTom map style.\n *\n * @remarks\n * This is the italic variant used for emphasized or secondary text labels.\n * Use this constant when creating custom layers to maintain visual consistency with the map style.\n *\n * @example\n * ```typescript\n * import { MAP_ITALIC_FONT } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [MAP_ITALIC_FONT],\n * 'text-field': ['get', 'subtitle']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const MAP_ITALIC_FONT = 'NotoSans-MediumItalic';\n\n/**\n * Default text size expression used in the TomTom map style.\n *\n * @remarks\n * This MapLibre expression defines zoom-dependent text sizing that scales from 12px at zoom 10\n * to 14px at zoom 16. Use this for consistent text sizing across zoom levels.\n *\n * @example\n * ```typescript\n * import { DEFAULT_TEXT_SIZE } from '@tomtom-international/maps-sdk-js/map';\n *\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-size': DEFAULT_TEXT_SIZE,\n * 'text-field': ['get', 'name']\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const DEFAULT_TEXT_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 10, 14, 18, 16];\n\n/**\n * Array of all available font faces in the TomTom map style.\n *\n * @remarks\n * Contains all font variants available in the map style. Useful for font selection\n * or validation when creating custom layers.\n *\n * @example\n * ```typescript\n * import { mapFonts } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Check if a font is available\n * if (mapFonts.includes('Noto-Bold')) {\n * // Use the font\n * }\n *\n * // Use as fallback list\n * const textLayer = {\n * type: 'symbol',\n * layout: {\n * 'text-font': [...mapFonts]\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport const mapFonts = [MAP_REGULAR_FONT, MAP_ITALIC_FONT, MAP_BOLD_FONT, MAP_MEDIUM_FONT] as const;\n\n/**\n * Type representing valid font faces available in the TomTom map style.\n *\n * @remarks\n * Use this type to ensure type-safe font selection when working with text layers.\n * Restricts values to only the fonts available in the map style.\n *\n * @example\n * ```typescript\n * import type { MapFont } from '@tomtom-international/maps-sdk-js/map';\n *\n * function createTextLayer(font: MapFont) {\n * return {\n * type: 'symbol',\n * layout: {\n * 'text-font': [font],\n * 'text-field': ['get', 'name']\n * }\n * };\n * }\n *\n * // Type-safe: only accepts valid map fonts\n * const layer = createTextLayer('Noto-Bold');\n * ```\n *\n * @group Map Style\n */\nexport type MapFont = (typeof mapFonts)[number];\n\n/**\n * Default pin icon scale at max zoom level (zoom 22).\n * This is the icon-size scale factor - 1.0 would be the original icon size.\n * @ignore\n */\nexport const DEFAULT_MAX_PIN_SCALE = 0.8;\n\n/**\n * @ignore\n */\nexport const PIN_ICON_SIZE: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 8,\n 0.6,\n 22,\n DEFAULT_MAX_PIN_SCALE,\n];\n\n/**\n * @ignore\n */\nexport const SELECTED_PIN_ICON_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 8, 0.8, 22, 1];\n","import { SymbolLayerSpecification } from 'maplibre-gl';\nimport { LayerSpecTemplate } from '../types';\nimport { DEFAULT_TEXT_SIZE, MAP_BOLD_FONT, PIN_ICON_SIZE } from './commonLayerProps';\n\n/**\n * @ignore\n */\nexport const TITLE = 'title';\n\n/**\n * @ignore\n */\nexport const ICON_ID = 'iconID';\n\n/**\n * Y-offset multiplier for text positioned below icons (top anchor).\n * In ems, relative to icon scale.\n * @ignore\n */\nexport const DEFAULT_TEXT_OFFSET_Y = 0.7;\n\n/**\n * X-offset multiplier for text positioned beside icons (left/right anchors).\n * In ems, relative to icon scale.\n * @ignore\n */\nexport const DEFAULT_TEXT_OFFSET_X = 1.4;\n\n/**\n * @ignore\n */\nexport const DEFAULT_PLACE_ICON_ID = 'default_place';\n\n// Fallback sprite for `base-map` theme places that don't match any category. The\n// style ships `poi-<group>-micro` sprites (e.g. `poi-parking-micro`) for the\n// `POI - Micro` layer — reusing one of those keeps uncategorized places visually\n// consistent with the native base-map POI labels (small dot) instead of showing\n// the large `default_place` pin.\n/**\n * @ignore\n */\nexport const DEFAULT_BASE_MAP_PLACE_ICON_ID = 'poi-parking-micro';\n\n/**\n * @ignore\n */\nexport const pinIconBaseLayout: SymbolLayerSpecification['layout'] = {\n 'icon-image': ['get', ICON_ID],\n 'icon-anchor': 'bottom',\n 'icon-size': PIN_ICON_SIZE,\n 'icon-allow-overlap': true,\n 'icon-padding': 0,\n};\n\n/**\n * @ignore\n */\nexport const pinIconBasePaint: SymbolLayerSpecification['paint'] = {\n 'icon-translate': [0, 5],\n 'icon-translate-anchor': 'viewport',\n};\n\n/**\n * @ignore\n */\nexport const pinTextBaseLayout: SymbolLayerSpecification['layout'] = {\n 'text-optional': true,\n 'text-font': [MAP_BOLD_FONT],\n 'text-field': ['get', TITLE],\n 'text-justify': 'auto',\n 'text-variable-anchor': ['top', 'left', 'right'],\n // NOTE: make sure to text against pins and waypoints, in a way that there's enough distance from the pin so the text doesn't disappear\n 'text-variable-anchor-offset': [\n 'top',\n [0, DEFAULT_TEXT_OFFSET_Y],\n 'left',\n [DEFAULT_TEXT_OFFSET_X, -DEFAULT_TEXT_OFFSET_X],\n 'right',\n [-DEFAULT_TEXT_OFFSET_X, -DEFAULT_TEXT_OFFSET_X],\n ],\n 'text-size': DEFAULT_TEXT_SIZE,\n 'text-padding': 5,\n};\n\n/**\n * Note: `text-color` and `text-halo-color` are intentionally omitted so\n * {@link buildPaintConfig} can inject theme-adaptive defaults on layers that\n * don't supply their own. Layers that bring a category/group-driven expression\n * (e.g., the base-map POI layer) keep their own colors untouched.\n * @ignore\n */\nexport const pinTextBasePaint: SymbolLayerSpecification['paint'] = {\n 'text-halo-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 10, 1.5],\n 'text-translate-anchor': 'viewport',\n};\n\n/**\n * Pin places, base layer with mostly the icon portion.\n * @ignore\n */\nexport const pinLayerBaseSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: { ...pinIconBaseLayout, ...pinTextBaseLayout },\n paint: { ...pinIconBasePaint, ...pinTextBasePaint },\n};\n","/**\n * Returns a text with a number suffixed after a hyphen.\n * @ignore\n */\nexport const suffixNumber = (text: string, numberToSuffix: number): string => `${text}-${numberToSuffix}`;\n","import type { CircleLayerSpecification, ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport { baseMapPOICategoryValues } from '../../pois/util/poiCategoryMapping';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { DEFAULT_BASE_MAP_PLACE_ICON_ID } from '../../shared/layers/symbolLayers';\n\n// Filter that picks cluster features out of a GeoJSON source with `cluster: true`.\n// MapLibre adds `point_count` to every cluster feature; using `has` keeps the\n// expression decoupled from any particular cluster property name.\nconst isClusterFilter: ExpressionSpecification = ['has', 'point_count'];\n\n// Filter for individual (non-cluster) features in a clustered source. Used by the\n// caller to derive the \"main pin\" filter when clustering is enabled.\nexport const isNotClusterFilter: ExpressionSpecification = ['!', isClusterFilter];\n\n/**\n * Cluster property key for the deduplicated comma-separated list of base-map\n * sprite IDs (each leaf's `baseMapIconID`). Single-category clusters carry a\n * single id here — the cluster pin layer's `match` expression picks the\n * matching `poi-<category>` sprite. The presence of a comma in this property\n * is also what {@link isMixedCategoryClusterFilter} keys off to route mixed\n * clusters to the centred badge + count pair instead.\n *\n * @ignore\n */\nexport const CLUSTER_PROPERTY_BASE_MAP_ICON_IDS = 'clusterBaseMapIconIDs';\n\n/**\n * Source-feature property key carrying each place's base-map sprite id,\n * populated by `preparePlacesForDisplay` when the `pin-clustered` theme is\n * active. The cluster pin (single-category case) reads it via the\n * {@link CLUSTER_PROPERTY_BASE_MAP_ICON_IDS} aggregator.\n *\n * @ignore\n */\nexport const FEATURE_PROPERTY_BASE_MAP_ICON_ID = 'baseMapIconID';\n\n// Cluster has only one unique base-map sprite among its leaves — i.e. every\n// place in the cluster shares a category. Used to gate single-category-only\n// renderings (cluster icon, top-right count badge) so mixed clusters fall\n// through to the centred badge + count pair.\nconst isSingleCategoryClusterFilter: ExpressionSpecification = [\n 'all',\n isClusterFilter,\n ['==', ['index-of', ',', ['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]], -1],\n];\n\n// Cluster contains 2+ unique base-map sprites — flagged by a comma in the\n// dedup-concat aggregator. Used to gate the centred badge + count circle.\nconst isMixedCategoryClusterFilter: ExpressionSpecification = [\n 'all',\n isClusterFilter,\n ['>=', ['index-of', ',', ['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]], 0],\n];\n\n/**\n * Symbol layer drawing the lone POI category's base-map sprite at the centre\n * of single-category clusters. Mixed clusters skip this layer entirely and\n * surface their count via {@link buildClusterMixedBadgeCircleLayerSpec} +\n * {@link buildClusterMixedCountTextLayerSpec} instead.\n *\n * @ignore\n */\nexport const buildClusterPinLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isSingleCategoryClusterFilter,\n layout: {\n // Single-category clusters carry exactly one entry in\n // `clusterBaseMapIconIDs`. We map it to a literal `image()` arm via\n // {@link buildKnownBaseMapSpriteImageExpr} so MapLibre statically\n // pre-loads every possible base-map sprite into the atlas — without\n // that enumeration, dynamic image references render as blanks.\n 'icon-image': buildKnownBaseMapSpriteImageExpr(['get', CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]),\n // Constant 1.0 — the cluster icon stays the same on-screen size at\n // every zoom level so users can rely on a stable hit target while\n // panning/zooming. The pin theme's per-place sprites still scale via\n // `PIN_ICON_SIZE`; only the cluster pin is fixed.\n 'icon-size': 1,\n 'icon-allow-overlap': true,\n 'icon-ignore-placement': true,\n },\n});\n\n// Translate (in viewport pixels) that places the count badge at the\n// top-right edge of the single-category cluster icon. The cluster icon is\n// anchored 'center' at viewport (0, 0), so [12, -14] tucks the badge above\n// and to the right of it without obscuring the icon.\nconst SINGLE_BADGE_TRANSLATE: [number, number] = [12, -14];\n\n// Radius of the small top-right badge for single-category clusters.\nconst SINGLE_BADGE_RADIUS = 11;\n\n// Radius of the centred badge that occupies a mixed cluster's pin slot. Larger\n// than the single-category corner badge because there's no icon behind it —\n// the circle itself is the visual.\nconst MIXED_BADGE_RADIUS = 15;\n\n/**\n * Black circle drawn at the top-right corner of single-category clusters.\n * Background for {@link buildClusterCountTextLayerSpec}; mixed clusters use\n * {@link buildClusterMixedBadgeCircleLayerSpec} instead.\n *\n * @ignore\n */\nexport const buildClusterBadgeCircleLayerSpec = (): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isSingleCategoryClusterFilter,\n paint: {\n 'circle-radius': SINGLE_BADGE_RADIUS,\n 'circle-color': '#000000',\n 'circle-stroke-color': '#FFFFFF',\n 'circle-stroke-width': 1.5,\n 'circle-translate': SINGLE_BADGE_TRANSLATE,\n 'circle-translate-anchor': 'viewport',\n },\n});\n\n/**\n * Symbol layer rendering the cluster count on top of the top-right badge\n * (single-category clusters). Mixed clusters use\n * {@link buildClusterMixedCountTextLayerSpec}.\n *\n * @ignore\n */\nexport const buildClusterCountTextLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isSingleCategoryClusterFilter,\n layout: {\n 'text-field': ['get', 'point_count_abbreviated'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': 12,\n 'text-allow-overlap': true,\n 'text-ignore-placement': true,\n },\n paint: {\n 'text-color': '#FFFFFF',\n 'text-translate': SINGLE_BADGE_TRANSLATE,\n 'text-translate-anchor': 'viewport',\n },\n});\n\n/**\n * Centred count circle for mixed-category clusters — replaces the cluster\n * icon entirely. {@link buildClusterMixedCountTextLayerSpec} renders the\n * count on top.\n *\n * @ignore\n */\nexport const buildClusterMixedBadgeCircleLayerSpec = (): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isMixedCategoryClusterFilter,\n paint: {\n 'circle-radius': MIXED_BADGE_RADIUS,\n 'circle-color': '#000000',\n 'circle-stroke-color': '#FFFFFF',\n 'circle-stroke-width': 1.5,\n },\n});\n\n/**\n * Cluster count rendered on top of the centred badge for mixed-category\n * clusters. Mirrors {@link buildClusterCountTextLayerSpec} but with no\n * translate, so it lands inside the centred badge from\n * {@link buildClusterMixedBadgeCircleLayerSpec}.\n *\n * @ignore\n */\nexport const buildClusterMixedCountTextLayerSpec = (): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isMixedCategoryClusterFilter,\n layout: {\n 'text-field': ['get', 'point_count_abbreviated'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': 12,\n 'text-allow-overlap': true,\n 'text-ignore-placement': true,\n },\n paint: {\n 'text-color': '#FFFFFF',\n },\n});\n\n/**\n * `clusterProperties` aggregator that exposes a deduplicated, comma-separated\n * list of source-feature property values on every generated cluster.\n *\n * @remarks\n * Uses MapLibre's advanced `[reduceExpression, mapExpression]` form: the map\n * expression extracts the value from each clustered feature; the reduce\n * expression appends it to the running accumulator only when not already\n * present (membership test via `index-of` over `,acc,` ↔ `,current,`).\n *\n * @ignore\n */\nconst buildDeduplicatedConcatAggregator = (sourceProperty: string, outputProperty: string) => [\n [\n 'case',\n ['==', ['get', outputProperty], null],\n ['accumulated'],\n ['==', ['accumulated'], null],\n ['get', outputProperty],\n ['>=', ['index-of', ['concat', ',', ['get', outputProperty], ','], ['concat', ',', ['accumulated'], ',']], 0],\n ['accumulated'],\n ['concat', ['accumulated'], ',', ['get', outputProperty]],\n ],\n ['get', sourceProperty],\n];\n\n/**\n * `clusterProperties` map for clustered places sources. Aggregates a\n * deduplicated, comma-separated list of base-map sprite IDs\n * (`baseMapIconID` per leaf) onto every cluster feature, feeding the\n * cluster pin's single-vs-mixed `match` expression and the\n * single/mixed cluster filters.\n *\n * @ignore\n */\nexport const clusteredPlacesAggregateProperties = {\n [CLUSTER_PROPERTY_BASE_MAP_ICON_IDS]: buildDeduplicatedConcatAggregator(\n FEATURE_PROPERTY_BASE_MAP_ICON_ID,\n CLUSTER_PROPERTY_BASE_MAP_ICON_IDS,\n ),\n};\n\n// Wrap a dynamic sprite-name expression in a `match` whose arms statically\n// enumerate every known `poi-<category>` sprite. Each arm returns\n// `image(literal)`, which MapLibre walks at style-parse time to enumerate\n// sprite usages and pre-load them into the atlas. Without this enumeration\n// the dynamic value resolves to a missing image at render time and nothing\n// shows.\n//\n// The default arm falls back to `DEFAULT_BASE_MAP_PLACE_ICON_ID` (the same\n// fallback the `base-map` theme uses for unmapped places); callers should\n// gate the layer with a filter so the default never fires for valid input.\nconst buildKnownBaseMapSpriteImageExpr = (dynamicNameExpr: ExpressionSpecification): ExpressionSpecification => {\n const arms: (string | ExpressionSpecification)[] = ['match', dynamicNameExpr];\n for (const category of baseMapPOICategoryValues) {\n const spriteId = `poi-${category}`;\n arms.push(spriteId, ['image', spriteId]);\n }\n arms.push(['image', DEFAULT_BASE_MAP_PLACE_ICON_ID]);\n return arms as ExpressionSpecification;\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { PlaceConnectionsConfig } from '../types/placesModuleConfig';\n\n/**\n * @ignore\n */\nexport type ConnectionLayerSpecs = {\n line: LayerSpecTemplate<LineLayerSpecification>;\n label: LayerSpecTemplate<SymbolLayerSpecification>;\n};\n\n// Base dashed line for connections. Width/colour mirror the places `selected`\n// highlight colour so the connection reads as visually tied to the place markers.\nconst baseLineSpec: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-color': '#3f9cd9',\n 'line-width': 1.5,\n 'line-dasharray': [2, 2],\n 'line-opacity': 0.9,\n },\n};\n\n// Label placed along the line (`line-center` keeps one label per connection, centred).\n// A small negative `text-offset` nudges the text perpendicular to the line so it reads\n// \"a bit on the side\" rather than sitting on top of the dashes.\nconst baseLabelSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line-center',\n 'text-field': ['get', 'label'],\n 'text-size': 12,\n 'text-offset': [0, -1],\n 'text-allow-overlap': false,\n 'text-ignore-placement': false,\n 'text-keep-upright': true,\n },\n paint: {\n 'text-color': '#1a5f8a',\n 'text-halo-color': '#ffffff',\n 'text-halo-width': 1.5,\n },\n};\n\n/**\n * Builds layer specifications for rendering place-to-place connections.\n * @ignore\n */\nexport const buildConnectionLayerSpecs = (config: PlaceConnectionsConfig | undefined): ConnectionLayerSpecs => {\n const customLine = config?.layers?.line;\n const customLabel = config?.layers?.label;\n\n return {\n line: {\n ...baseLineSpec,\n ...customLine,\n layout: { ...baseLineSpec.layout, ...customLine?.layout },\n paint: { ...baseLineSpec.paint, ...customLine?.paint },\n },\n label: {\n ...baseLabelSpec,\n ...customLabel,\n layout: { ...baseLabelSpec.layout, ...customLabel?.layout },\n paint: { ...baseLabelSpec.paint, ...customLabel?.paint },\n },\n };\n};\n","import { suffixNumber } from '../../shared/layers/utils';\nimport { parseSvg } from '../../shared/resources';\nimport type { PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\n\n/**\n * Map of icon IDs to their text offset scale factors.\n * @ignore\n */\nexport type IconScalesMap = Map<string, { heightScale: number; widthScale: number }>;\n\n/**\n * Default pin dimensions (from base pin.svg)\n */\nconst DEFAULT_PIN_HEIGHT_PX = 140;\nconst DEFAULT_PIN_WIDTH_PX = 120;\n\n/**\n * Default base-map POI icon dimensions (from base-theme POI icons)\n */\nconst DEFAULT_MAP_POI_HEIGHT_PX = 54;\nconst DEFAULT_MAP_POI_WIDTH_PX = 54;\n\n/**\n * Extracts dimensions from an SVG string or HTMLImageElement.\n * For SVGs, parses the viewBox or width/height attributes.\n * For images, uses naturalWidth/naturalHeight.\n * @ignore\n */\nexport const extractImageDimensions = (image: string | HTMLImageElement): { width: number; height: number } | null => {\n try {\n if (typeof image === 'string') {\n if (image.includes('<svg')) {\n // Parse SVG to extract dimensions\n const svgElement = parseSvg(image);\n\n // Try viewBox first (format: \"minX minY width height\")\n const viewBox = svgElement.getAttribute('viewBox');\n if (viewBox) {\n const parts = viewBox.split(/\\s+/);\n if (parts.length === 4) {\n return {\n width: Number.parseFloat(parts[2]),\n height: Number.parseFloat(parts[3]),\n };\n }\n }\n\n // Fallback to width/height attributes\n const width = svgElement.getAttribute('width');\n const height = svgElement.getAttribute('height');\n if (width && height) {\n return {\n width: Number.parseFloat(width),\n height: Number.parseFloat(height),\n };\n }\n }\n // For URL strings, we can't extract dimensions synchronously\n return null;\n } else {\n // HTMLImageElement - check if loaded\n if (image.complete && image.naturalWidth > 0) {\n return {\n width: image.naturalWidth,\n height: image.naturalHeight,\n };\n }\n return null;\n }\n } catch (error) {\n console.warn('Failed to extract image dimensions:', error);\n return null;\n }\n};\n\n/**\n * Calculate the scale factors for a custom icon based on its dimensions relative to standard icon sizes.\n *\n * Calculates both height and width scales independently:\n * - Height scale is used for vertical text offset\n * - Width scale is used for horizontal text offset\n *\n *\n * @param image The image to extract dimensions from\n * @param theme The places theme ('base-map'/'circle-icon' for centered icons, 'pin' for bottom-anchored pins)\n * @returns Object with heightScale and widthScale, or undefined if icon is standard-sized (within tolerance)\n * @ignore\n */\nexport const calculateIconScale = (\n image: string | HTMLImageElement | undefined,\n theme?: PlacesTheme,\n): { heightScale: number; widthScale: number } | undefined => {\n if (!image) {\n return undefined;\n }\n\n const dimensions = extractImageDimensions(image);\n if (!dimensions) {\n return undefined;\n }\n\n const isCenteredTheme = theme === 'base-map' || theme === 'circle-icon';\n\n // Centered themes (base-map / circle-icon) scale relative to POI icon dimensions.\n // Pin theme scales relative to default pin dimensions.\n const refHeight = isCenteredTheme ? DEFAULT_MAP_POI_HEIGHT_PX : DEFAULT_PIN_HEIGHT_PX;\n const refWidth = isCenteredTheme ? DEFAULT_MAP_POI_WIDTH_PX : DEFAULT_PIN_WIDTH_PX;\n\n return { heightScale: dimensions.height / refHeight, widthScale: dimensions.width / refWidth };\n};\n\n/**\n * Builds a map of icon IDs to their text offset scales for custom icons.\n * @ignore\n */\nexport const buildCustomIconScalesMap = (\n config: PlacesModuleConfig | undefined,\n instanceIndex: number,\n): IconScalesMap => {\n const iconTextOffsetScales = new Map<string, { heightScale: number; widthScale: number }>();\n const customIcons = config?.icon?.categoryIcons ?? [];\n\n for (const icon of customIcons) {\n if (icon.image) {\n const scales = calculateIconScale(icon.image, config?.theme);\n if (scales !== undefined) {\n // Base icon ID with instance suffix\n const suffixedIconId = suffixNumber(icon.id, instanceIndex);\n iconTextOffsetScales.set(suffixedIconId, scales);\n\n // If this icon has an availability level, also add the availability-suffixed version\n // (e.g., \"ELECTRIC_VEHICLE_STATION-available-0\")\n if (icon.availabilityLevel) {\n const availabilitySuffixedId = suffixNumber(`${icon.id}-${icon.availabilityLevel}`, instanceIndex);\n iconTextOffsetScales.set(availabilitySuffixedId, scales);\n }\n }\n }\n }\n\n return iconTextOffsetScales;\n};\n","import type { ChargingPark, ChargingParkWithAvailability, Place, POICategory } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport type { AvailabilityLevel } from '../../shared';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport type { EVAvailabilityConfig, PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\n\n/**\n * Type guard to check if a charging park has availability data.\n * @ignore\n */\nexport const hasChargingAvailability = (\n chargingPark: ChargingPark | ChargingParkWithAvailability | undefined,\n): chargingPark is ChargingParkWithAvailability =>\n Boolean(chargingPark && 'availability' in chargingPark && chargingPark.availability);\n\n/**\n * Check if a place is an EV charging station with availability data.\n * @ignore\n */\nexport const isEVStationWithAvailability = (place: Place): boolean => {\n const category = place.properties.poi?.categories?.[0];\n return category === 'ELECTRIC_VEHICLE_STATION' && hasChargingAvailability(place.properties.chargingPark);\n};\n\n/**\n * Calculate charging point availability information.\n * @ignore\n */\nexport const getChargingPointAvailability = (\n place: Place,\n): { availableCount: number; totalCount: number; ratio: number } | undefined => {\n const chargingPark = place.properties.chargingPark;\n if (hasChargingAvailability(chargingPark)) {\n const availability = chargingPark.availability.chargingPointAvailability;\n const available = availability.statusCounts.Available ?? 0;\n return {\n availableCount: available,\n totalCount: availability.count,\n ratio: available / availability.count,\n };\n }\n return undefined;\n};\n\n/**\n * Default threshold for EV availability - available vs occupied.\n * @ignore\n */\nexport const DEFAULT_EV_AVAILABILITY_THRESHOLD = 0;\n\n/**\n * Default formatter for EV availability text.\n * @ignore\n */\nexport const defaultFormatAvailabilityText = (available: number, total: number): string => `${available}/${total}`;\n\n/**\n * Build availability text for a place with EV availability data.\n * @ignore\n */\nexport const buildAvailabilityText = (place: Place, config?: EVAvailabilityConfig): string => {\n const availability = getChargingPointAvailability(place);\n if (!availability) {\n return '';\n }\n\n const formatFn = config?.formatText ?? defaultFormatAvailabilityText;\n return formatFn(availability.availableCount, availability.totalCount);\n};\n\n/**\n * Build availability ratio for a place with EV availability data.\n * @ignore\n */\nexport const getAvailabilityRatio = (place: Place): number => {\n const availability = getChargingPointAvailability(place);\n return availability?.ratio ?? 0;\n};\n\n/**\n * Get the color expression for EV availability based on ratio.\n * @ignore\n */\nexport const getAvailabilityColorExpression = (config?: EVAvailabilityConfig): ExpressionSpecification => {\n const threshold = config?.threshold ?? DEFAULT_EV_AVAILABILITY_THRESHOLD;\n\n return ['case', ['>=', ['get', 'evAvailabilityRatio'], threshold], 'green', 'red'] as ExpressionSpecification;\n};\n\n/**\n * Handles icon selection for EV stations with availability data.\n * Returns icon ID if availability-specific icon should be used, or undefined to fall through to regular selection.\n * @ignore\n */\nexport const getEVAvailabilityIconID = (\n place: Place,\n poiCategory: POICategory,\n instanceIndex: number,\n config: PlacesModuleConfig,\n iconTheme: PlacesTheme,\n): string | undefined => {\n if (!config.evAvailability?.enabled || !isEVStationWithAvailability(place)) {\n return undefined;\n }\n\n const ratio = getAvailabilityRatio(place);\n const threshold = config.evAvailability.threshold ?? 0.3;\n const requiredLevel: AvailabilityLevel = ratio >= threshold ? 'available' : 'occupied';\n const hasCustomIcons = config.icon?.categoryIcons && config.icon.categoryIcons.length > 0;\n\n const customIconWithAvailability = config.icon?.categoryIcons?.find(\n (customIcon) => customIcon.id === poiCategory && customIcon.availabilityLevel === requiredLevel,\n );\n\n // If a custom icon with the required availability level exists, use it\n if (customIconWithAvailability) {\n return suffixNumber(`${customIconWithAvailability.id}-${requiredLevel}`, instanceIndex);\n }\n\n // For pin theme: use CDN availability sprites when no custom icons are defined\n if (!hasCustomIcons && iconTheme === 'pin') {\n return `7309-${requiredLevel}`;\n }\n\n // Otherwise, fall through to regular icon selection\n return undefined;\n};\n","import type {\n DataDrivenPropertyValueSpecification,\n ExpressionSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport { DEFAULT_MAX_PIN_SCALE } from '../../shared/layers/commonLayerProps';\nimport { DEFAULT_TEXT_OFFSET_X, DEFAULT_TEXT_OFFSET_Y, ICON_ID } from '../../shared/layers/symbolLayers';\nimport type { PlacesTheme } from '../types/placesModuleConfig';\n\ntype VariableAnchorOffset = NonNullable<SymbolLayerSpecification['layout']>['text-variable-anchor-offset'];\n\n/**\n * Extracts the maximum icon scale factor from an icon-size expression.\n * For interpolate expressions, returns the last value (max zoom scale).\n * @ignore\n */\nconst extractMaxIconScale = (expression: DataDrivenPropertyValueSpecification<number> | undefined): number => {\n if (!expression) {\n return DEFAULT_MAX_PIN_SCALE;\n }\n\n // Literal: 0.8\n if (typeof expression === 'number') {\n return expression;\n }\n\n // Interpolate expression: ['interpolate', ['linear'], ['zoom'], 8, 0.6, 22, **0.8**]\n if (Array.isArray(expression)) {\n const lastValue = expression.at(-1);\n if (typeof lastValue === 'number') {\n return lastValue;\n }\n }\n\n // Legacy stops object: { stops: [[10, 0.7], [18, **1**]] }\n if (typeof expression === 'object' && 'stops' in expression) {\n const stops = (expression as { stops: [number, number][] }).stops;\n if (Array.isArray(stops) && stops.length > 0) {\n const lastStop = stops.at(-1);\n if (Array.isArray(lastStop) && typeof lastStop[1] === 'number') {\n return lastStop[1];\n }\n }\n }\n\n return DEFAULT_MAX_PIN_SCALE;\n};\n\n/**\n * Layout properties for text offset - can be spread directly into layer layout.\n */\ntype TextOffsetLayout = {\n 'text-offset'?: [number, number];\n 'text-variable-anchor-offset'?: VariableAnchorOffset;\n};\n\n/**\n * Rounds to 4 decimal places — prevents float noise (e.g. 0.7*2*1.5 = 2.0999…)\n * from leaking into style JSON when custom icon scales produce non-exact products.\n * @ignore\n */\nconst round4 = (n: number): number => Math.round(n * 10000) / 10000;\n\n/**\n * Builds anchor offsets for variable-anchor-offset property.\n * @ignore\n */\nconst buildAnchorOffsets = (\n topOffset: number,\n sideOffset: number,\n pinVerticalAdjustment: number,\n customTextOffset?: number,\n): { top: [number, number]; left: [number, number]; right: [number, number] } => {\n const hasCustomOffset = customTextOffset !== undefined;\n // For pin theme with custom offset, override only the primary direction for each anchor\n // top anchor → custom offset applies to vertical, left/right → custom offset applies to horizontal\n return {\n top: hasCustomOffset ? [0, customTextOffset] : [0, round4(topOffset)],\n left: hasCustomOffset\n ? [customTextOffset, round4(pinVerticalAdjustment)]\n : [round4(sideOffset), round4(pinVerticalAdjustment)],\n right: hasCustomOffset\n ? [-customTextOffset, round4(pinVerticalAdjustment)]\n : [-round4(sideOffset), round4(pinVerticalAdjustment)],\n };\n};\n\n/**\n * Calculates text offset for place labels\n *\n * @param iconSizeExpression The icon-size property from the layer specification\n * @param iconTextOffsetScales Map of icon IDs to their scale factors (heightScale for vertical, widthScale for horizontal)\n * @param theme The places theme ('base-map'/'circle-icon' for centered icons, 'pin' for bottom-anchored pins)\n * @param customTextOffset Custom text offset multiplier (overrides default TEXT_OFFSET constants)\n * @returns Configuration object with the MapLibre property type and value to apply\n * @ignore\n */\nexport const getTextOffset = (\n iconSizeExpression: DataDrivenPropertyValueSpecification<number> | undefined,\n iconTextOffsetScales: Map<string, { heightScale: number; widthScale: number }>,\n theme?: PlacesTheme,\n customTextOffset?: number,\n): TextOffsetLayout => {\n const maxIconScale = extractMaxIconScale(iconSizeExpression);\n const iconScaleMultiplier = maxIconScale / DEFAULT_MAX_PIN_SCALE;\n // 'base-map' and 'circle-icon' both use centered icons; 'pin' is bottom-anchored.\n const isCenteredTheme = theme === 'base-map' || theme === 'circle-icon';\n const hasCustomOffset = customTextOffset !== undefined;\n\n // For centered themes with a custom offset, use simple text-offset\n if (isCenteredTheme && hasCustomOffset) {\n return {\n 'text-offset': [customTextOffset, customTextOffset],\n };\n }\n\n // Calculate fallback offsets (used when no custom icons or as default case)\n const fallbackTopOffset = DEFAULT_TEXT_OFFSET_Y * iconScaleMultiplier;\n const fallbackSideOffset = DEFAULT_TEXT_OFFSET_X * iconScaleMultiplier;\n const fallbackVerticalAdjustment = isCenteredTheme ? 0 : -fallbackSideOffset;\n const fallbackOffsets = buildAnchorOffsets(\n fallbackTopOffset,\n fallbackSideOffset,\n fallbackVerticalAdjustment,\n customTextOffset,\n );\n const fallbackAnchorOffset = [\n 'top',\n fallbackOffsets.top,\n 'left',\n fallbackOffsets.left,\n 'right',\n fallbackOffsets.right,\n ];\n\n // No custom icons - return literal value directly (no case expression needed)\n if (iconTextOffsetScales.size === 0) {\n return {\n 'text-variable-anchor-offset': fallbackAnchorOffset as VariableAnchorOffset,\n };\n }\n\n // Build case expression for custom icons\n const offsetCaseExpression: (string | number | ExpressionSpecification)[] = ['case'];\n\n for (const [iconId, scales] of iconTextOffsetScales.entries()) {\n // Centered themes (base-map / circle-icon) use larger vertical offsets to match native map styling\n const baseTopOffset = isCenteredTheme ? DEFAULT_TEXT_OFFSET_Y * 2 : DEFAULT_TEXT_OFFSET_Y;\n const topOffset = baseTopOffset * scales.heightScale;\n const sideOffset = DEFAULT_TEXT_OFFSET_X * scales.widthScale;\n\n // Centered themes (centered icon anchor) → no vertical adjustment for side anchors\n // Pin theme uses pins (bottom-anchored) → shift labels upward to align with visual center\n const pinVerticalAdjustment = isCenteredTheme ? 0 : -sideOffset;\n\n const offsets = buildAnchorOffsets(topOffset, sideOffset, pinVerticalAdjustment, customTextOffset);\n offsetCaseExpression.push(\n ['==', ['get', ICON_ID], iconId],\n ['literal', ['top', offsets.top, 'left', offsets.left, 'right', offsets.right]],\n );\n }\n\n // Add fallback for icons not in the custom scales map\n offsetCaseExpression.push(['literal', fallbackAnchorOffset]);\n\n return {\n 'text-variable-anchor-offset': offsetCaseExpression as VariableAnchorOffset,\n };\n};\n","import { LightDark } from '../../shared/types/style';\n/**\n * Calculates theme-adaptive text colors based on light/dark mode.\n * @param lightDark Whether the current theme is light or dark\n * @returns Object with text and halo colors appropriate for the theme\n * @ignore\n *\n * TODO: Should these colors adapt to the POI context colors,\n * or should they remain as fixed defaults?\n */\nexport const getThemeAdaptiveTextColors = (\n lightDark: LightDark,\n): {\n textColor: string;\n haloColor: string;\n} => {\n return {\n textColor: lightDark === 'dark' ? '#FFFFFF' : '#333333',\n haloColor: lightDark === 'dark' ? '#333333' : '#FFFFFF',\n };\n};\n","import type { DataDrivenPropertyValueSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { TITLE } from '../../shared/layers/symbolLayers';\nimport type { PlaceLayerName, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport type { IconScalesMap } from './customIconScales';\nimport { getAvailabilityColorExpression } from './evAvailabilityHelpers';\nimport { getTextOffset } from './textOffsetCalculator';\nimport { getThemeAdaptiveTextColors } from './themeAdaptation';\n\n/**\n * Builds the text field expression for place labels\n * Supports EV availability text when enabled.\n * @ignore\n */\n\nexport const buildTextFieldExpression = (\n config: PlacesModuleConfig | undefined,\n evAvailabilityEnabled: boolean,\n): DataDrivenPropertyValueSpecification<string> => {\n if (!evAvailabilityEnabled) {\n return ['get', TITLE];\n }\n\n return [\n 'case',\n ['has', 'evAvailabilityText'],\n // If has EV availability, show two-line format with colored availability\n [\n 'format',\n ['get', TITLE],\n {},\n '\\n',\n {},\n ['get', 'evAvailabilityText'],\n {\n 'font-scale': 1.1,\n 'text-color': getAvailabilityColorExpression(config?.evAvailability),\n },\n ],\n // Otherwise, show normal title\n ['get', TITLE],\n ];\n};\n\n/**\n * Builds the layout configuration\n * @ignore\n */\nexport const buildLayoutConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: PlaceLayerName,\n textField: DataDrivenPropertyValueSpecification<string> | undefined,\n iconTextOffsetScales?: IconScalesMap,\n): SymbolLayerSpecification['layout'] => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const hasCustomIcons = iconTextOffsetScales && iconTextOffsetScales.size > 0;\n\n // Start with base layout\n const baseLayout = { ...layerSpec.layout };\n\n // Remove and recalculate offset properties when custom icons, custom offset, or the circle-icon\n // theme is present. `circle-icon` inherits pin-style offsets from pinLayerBaseSpec which must\n // be replaced with centered offsets.\n const needsOffsetRecalculation =\n hasCustomIcons || textConfig?.offset !== undefined || config?.theme === 'circle-icon';\n\n if (needsOffsetRecalculation) {\n delete baseLayout['text-offset'];\n delete baseLayout['text-variable-anchor-offset'];\n delete baseLayout['text-radial-offset'];\n }\n\n const layout = {\n ...baseLayout,\n ...customLayer?.layout,\n ...(textConfig?.size && { 'text-size': textConfig.size }),\n ...(textConfig?.font && { 'text-font': textConfig.font }),\n ...(textField !== undefined && { 'text-field': textField }),\n };\n\n // Apply offset configuration\n if (needsOffsetRecalculation) {\n // Dynamic offset calculation handles custom icons, custom offset, and circle-icon theme centering\n const iconSize = layout['icon-size'];\n const scales = iconTextOffsetScales ?? new Map();\n return { ...layout, ...getTextOffset(iconSize, scales, config?.theme, textConfig?.offset) };\n }\n\n return layout;\n};\n\n/**\n * Builds the paint configuration with theme-adaptive colors.\n * @ignore\n */\nexport const buildPaintConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: PlaceLayerName,\n lightDark: LightDark,\n): SymbolLayerSpecification['paint'] => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const { textColor: baseTextColor, haloColor: baseHaloColor } = getThemeAdaptiveTextColors(lightDark);\n const basePaint = layerSpec.paint ?? {};\n // Theme-adaptive defaults only fill in when the base layer doesn't already\n // define a color — that way layers carrying their own `text-color` (e.g., the\n // base-map style's category/group-driven expression, or `SELECTED_COLOR` on\n // the selected layer) are preserved. User `text.*` config still wins.\n return {\n ...basePaint,\n ...(basePaint['text-color'] === undefined && { 'text-color': baseTextColor }),\n ...(basePaint['text-halo-color'] === undefined && { 'text-halo-color': baseHaloColor }),\n ...(textConfig?.color && { 'text-color': textConfig.color }),\n ...(textConfig?.haloColor && { 'text-halo-color': textConfig.haloColor }),\n ...(textConfig?.haloWidth && { 'text-halo-width': textConfig.haloWidth }),\n ...customLayer?.paint,\n };\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\n\n/**\n * @ignore\n */\nexport const isClickEventState: ExpressionSpecification = [\n 'in',\n ['get', 'eventState'],\n ['literal', ['click', 'contextmenu']],\n];\n","import { omit } from 'lodash-es';\nimport type { DataDrivenPropertyValueSpecification, Map as MapLibreMap, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { isClickEventState } from '../../shared/layers/eventState';\nimport { ICON_ID, pinLayerBaseSpec, TITLE } from '../../shared/layers/symbolLayers';\n\n/**\n * Replaces placeholders in text size spec with the actual title property.\n * @ignore\n */\nexport const getTextSizeSpec = (\n textSize?: DataDrivenPropertyValueSpecification<number>,\n): DataDrivenPropertyValueSpecification<number> => {\n return JSON.parse(JSON.stringify(textSize)?.replaceAll('name', TITLE));\n};\n\nconst findStyleLayer = (map: MapLibreMap, layerID: string): SymbolLayerSpecification =>\n (map.getStyle().layers.find((layer) => layer.id === layerID) as SymbolLayerSpecification) ?? {};\n\n// Shared foundation for every POI-like layer variant: filter, symbol type, and\n// paint/layout props copied verbatim from the base map style layer so the style's\n// original expressions (e.g., text-color keyed on `category`/`group`) keep working\n// against our GeoJSON features. We strip inherited `visibility` because some styles\n// hide `POI - Micro` by default — our places layer owns visibility via\n// `PlacesModule.applyPlacesVisibility` and must not carry the style's hidden state.\nconst buildBaseMapPOILayerFoundation = (\n poiLayer: SymbolLayerSpecification,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n filter: ['!', isClickEventState],\n type: 'symbol',\n paint: poiLayer.paint,\n layout: poiLayer.layout ? omit(poiLayer.layout, 'visibility') : undefined,\n});\n\n/**\n * Builds a POI-like layer spec that mirrors the base map style's 'POI' layer,\n * overriding the icon image (bound to our `iconID` feature prop) and text field\n * so custom category icons and titles take effect. Paint and remaining layout\n * props — notably `text-color`, which reads `['get','category']`/`['get','group']`\n * — are inherited verbatim from the style.\n * @ignore\n */\nexport const buildBaseMapPOILayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const foundation = buildBaseMapPOILayerFoundation(findStyleLayer(map, 'POI'));\n const textSize = foundation.layout?.['text-size'];\n return {\n ...foundation,\n layout: {\n ...foundation.layout,\n 'icon-image': ['get', ICON_ID],\n 'text-field': ['get', TITLE],\n ...(textSize && { 'text-size': getTextSizeSpec(textSize) }),\n },\n };\n};\n\n// Builds a POI-like layer spec that mirrors the base map style's 'POI - Micro' layer.\n// Inherits the style's `['get','group']`-driven `icon-image` verbatim — custom\n// `PlaceIconConfig.categoryIcons` icons only apply on the `main` layer. Three\n// style-carried properties would otherwise suppress rendering:\n// - `minzoom: 15.5` and `icon-opacity` zoom-interp starting at 0 below 15.5 — our places\n// should show at all zooms, so we drop the zoom gate and force opacity to 1;\n// - collision culling against the `main` POI layer above — bypassed via\n// `icon-allow-overlap` + `icon-ignore-placement`.\nexport const buildPOIMicroLikeLayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const foundation = buildBaseMapPOILayerFoundation(findStyleLayer(map, 'POI - Micro'));\n return {\n ...foundation,\n minzoom: 0,\n layout: {\n ...foundation.layout,\n 'icon-allow-overlap': true,\n 'icon-ignore-placement': true,\n },\n paint: {\n ...foundation.paint,\n 'icon-opacity': 1,\n },\n };\n};\n\n/**\n * Builds a base layer spec for the 'circle-icon' theme.\n * @ignore\n */\nexport const buildCircleBaseLayerSpec = (map: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => {\n const poiLayout = findStyleLayer(map, 'POI').layout ?? {};\n return {\n ...pinLayerBaseSpec,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'icon-anchor': 'center',\n ...(poiLayout['icon-size'] !== undefined && { 'icon-size': poiLayout['icon-size'] }),\n ...(poiLayout['icon-padding'] !== undefined && { 'icon-padding': poiLayout['icon-padding'] }),\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n 'icon-translate': [0, 0],\n },\n };\n};\n","import type {\n CircleLayerSpecification,\n ExpressionSpecification,\n Map as MapLibreMap,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport { LayerSpecTemplate, LightDark, mapStyleLayerIDs } from '../../shared';\nimport { SELECTED_PIN_ICON_SIZE } from '../../shared/layers/commonLayerProps';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport type { PlaceLayerName, PlaceLayersConfig, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport { buildCustomIconScalesMap, type IconScalesMap } from '../utils/customIconScales';\nimport { buildLayoutConfig, buildPaintConfig, buildTextFieldExpression } from '../utils/layerConfiguration';\nimport {\n buildBaseMapPOILayerSpec,\n buildCircleBaseLayerSpec,\n buildPOIMicroLikeLayerSpec,\n} from '../utils/layerSpecBuilders';\nimport {\n buildClusterBadgeCircleLayerSpec,\n buildClusterCountTextLayerSpec,\n buildClusterMixedBadgeCircleLayerSpec,\n buildClusterMixedCountTextLayerSpec,\n buildClusterPinLayerSpec,\n isNotClusterFilter,\n} from './clusterLayers';\n\n/**\n * @ignore\n */\nexport const hasEventState: ExpressionSpecification = ['has', 'eventState'];\n\n// A minimal placeholder spec used to keep a layer registered but hidden. This mirrors\n// the pattern in `TrafficAreaAnalyticsModule` (toggling `layout.visibility`), so theme\n// switches just flip visibility via `setLayoutProperty` without adding/removing layers.\nconst HIDDEN_LAYER_SPEC: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: { visibility: 'none' },\n};\n\n// Hidden circle-layer placeholder for the cluster badge slot. Like `HIDDEN_LAYER_SPEC`\n// but typed as `circle` because the cluster badge is the only non-symbol layer the\n// places module registers, and `addLayers`/`updateLayersAndSource` reject layer-type\n// changes for an existing id.\nconst HIDDEN_CIRCLE_LAYER_SPEC: LayerSpecTemplate<CircleLayerSpecification> = {\n type: 'circle',\n layout: { visibility: 'none' },\n};\n\n/**\n * @ignore\n */\nexport const SELECTED_COLOR = '#3f9cd9';\n\n/**\n * @ignore\n */\nexport const pinLayerSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...pinLayerBaseSpec,\n filter: ['!', hasEventState],\n};\n\n/**\n * We use an extra layer for highlighted text since it's not easy to enforce z-ordering with icons and text\n * while text has different collision rules.\n * @ignore\n */\nexport const selectedPinLayerSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...pinLayerBaseSpec,\n filter: hasEventState,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'icon-size': SELECTED_PIN_ICON_SIZE,\n 'text-allow-overlap': true,\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n 'text-color': SELECTED_COLOR,\n },\n};\n\n// Per-theme builders: each returns the layer spec for one (theme, role) pair.\n// `buildPlacesLayerSpecs` dispatches to these based on `config.theme`; roles a theme\n// doesn't render fall back to `HIDDEN_LAYER_SPEC` so layers stay registered but\n// invisible (theme switches toggle `visibility` rather than adding/removing layers).\n\n// Derives a `selected` variant from a POI-like base spec: match only on event state,\n// allow text overlap so selected labels stay visible, and paint text in SELECTED_COLOR.\nconst toSelectedPOILikeSpec = (\n base: LayerSpecTemplate<SymbolLayerSpecification>,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...base,\n filter: hasEventState,\n layout: {\n ...base.layout,\n 'text-allow-overlap': true,\n },\n paint: {\n ...base.paint,\n 'text-color': SELECTED_COLOR,\n },\n});\n\nconst buildBaseMapSelectedLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> =>\n toSelectedPOILikeSpec(buildBaseMapPOILayerSpec(mapLibreMap));\n\nconst buildCircleIconMainLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...buildCircleBaseLayerSpec(mapLibreMap),\n filter: ['!', hasEventState],\n});\n\nconst buildCircleIconSelectedLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> =>\n toSelectedPOILikeSpec(buildCircleBaseLayerSpec(mapLibreMap));\n\n// Subset of layer slots that go through the symbol-layer `withConfig` pipeline.\n// Cluster slots are excluded because `clusterBadge` / `clusterMixedBadge` are\n// circle layers and the cluster pin / count / mixed-count layers don't\n// participate in EV availability or custom text expressions.\ntype SymbolPlaceLayerName = Exclude<\n PlaceLayerName,\n 'cluster' | 'clusterBadge' | 'clusterCount' | 'clusterMixedBadge' | 'clusterMixedCount'\n>;\n\n// Compute a text-field override only when user config or EV availability demands it.\n// Otherwise the layerSpec's own text-field passes through untouched — which lets the\n// base-map `micro` layer keep the style's own expression verbatim and avoids redundantly\n// re-setting the default on layers whose base spec already binds `text-field`.\nconst withConfig = (\n layerSpec: LayerSpecTemplate<SymbolLayerSpecification>,\n config: PlacesModuleConfig | undefined,\n layerName: SymbolPlaceLayerName,\n lightDark: LightDark,\n iconTextOffsetScales?: IconScalesMap,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const textConfig = config?.text;\n const customLayer = config?.layers?.[layerName];\n const evAvailabilityEnabled = config?.evAvailability?.enabled === true;\n\n const textField =\n textConfig?.title && typeof textConfig?.title !== 'function'\n ? textConfig.title\n : evAvailabilityEnabled\n ? buildTextFieldExpression(config, evAvailabilityEnabled)\n : undefined;\n\n return {\n ...layerSpec,\n ...customLayer,\n layout: buildLayoutConfig(layerSpec, config, layerName, textField, iconTextOffsetScales),\n paint: buildPaintConfig(layerSpec, config, layerName, lightDark),\n };\n};\n\n/**\n * Builds layer specifications for places display.\n * @ignore\n */\nexport const buildPlacesLayerSpecs = (\n config: PlacesModuleConfig | undefined,\n mapLibreMap: MapLibreMap,\n styleLightDarkTheme: LightDark,\n instanceIndex: number,\n): PlaceLayersConfig => {\n const iconTextOffsetScales = buildCustomIconScalesMap(config, instanceIndex);\n\n let main: LayerSpecTemplate<SymbolLayerSpecification>;\n let selected: LayerSpecTemplate<SymbolLayerSpecification>;\n // `micro` is always registered so theme switches only update its filter/paint/layout\n // (fast path via `updateLayersAndSource` + `changeLayerProps`) rather than adding or\n // removing a layer. For themes that don't render on `micro` it's kept present but\n // hidden via `layout.visibility = 'none'`.\n let micro: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n\n // Cluster slots: same \"always-registered, toggled via visibility\" pattern as `micro`.\n // Only the `pin-clustered` theme populates them with real specs.\n let cluster: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n let clusterBadge: LayerSpecTemplate<CircleLayerSpecification> = HIDDEN_CIRCLE_LAYER_SPEC;\n let clusterCount: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n let clusterMixedBadge: LayerSpecTemplate<CircleLayerSpecification> = HIDDEN_CIRCLE_LAYER_SPEC;\n let clusterMixedCount: LayerSpecTemplate<SymbolLayerSpecification> = HIDDEN_LAYER_SPEC;\n\n if (config?.theme === 'base-map') {\n main = { ...buildBaseMapPOILayerSpec(mapLibreMap), minzoom: 10 };\n micro = buildPOIMicroLikeLayerSpec(mapLibreMap);\n selected = buildBaseMapSelectedLayerSpec(mapLibreMap);\n } else if (config?.theme === 'circle-icon') {\n main = buildCircleIconMainLayerSpec(mapLibreMap);\n selected = buildCircleIconSelectedLayerSpec(mapLibreMap);\n } else if (config?.theme === 'pin-clustered') {\n // The pin layers stay the same as the regular `pin` theme but are restricted\n // to non-cluster (singleton) features so a clustered point is not double-drawn\n // as both a cluster pin and an individual pin. The dedicated cluster pin layer\n // renders single-category clusters with that category's base-map sprite;\n // mixed clusters skip the icon and surface a centred count circle via\n // `clusterMixedBadge` + `clusterMixedCount`.\n //\n // The `micro` slot is also populated — same builder the `base-map` theme uses\n // — restricted to non-cluster features so it only renders the un-clustered\n // singletons under the base-map style's POI - Micro layer expressions, blending\n // them with the native base-map POIs at low zoom.\n main = { ...pinLayerSpec, filter: ['all', isNotClusterFilter, ['!', hasEventState]] };\n selected = { ...selectedPinLayerSpec, filter: ['all', isNotClusterFilter, hasEventState] };\n const microBase = buildPOIMicroLikeLayerSpec(mapLibreMap);\n micro = {\n ...microBase,\n filter: microBase.filter\n ? (['all', isNotClusterFilter, microBase.filter] as ExpressionSpecification)\n : isNotClusterFilter,\n };\n cluster = buildClusterPinLayerSpec();\n clusterBadge = buildClusterBadgeCircleLayerSpec();\n clusterCount = buildClusterCountTextLayerSpec();\n clusterMixedBadge = buildClusterMixedBadgeCircleLayerSpec();\n clusterMixedCount = buildClusterMixedCountTextLayerSpec();\n } else {\n // pin (default)\n main = pinLayerSpec;\n selected = selectedPinLayerSpec;\n }\n\n // Layer stacking (bottom → top): micro < main < selected, expressed via\n // `beforeID` so `addLayers` resolves ordering the same way RoutingModule does.\n // PlacesModule rewrites these internal names into instance-scoped layer IDs.\n // Cluster slots stack on top: cluster pin < clusterBadge < clusterCount <\n // clusterMixedBadge < clusterMixedCount, anchored above `selected` so the\n // count text is always legible. The mixed-cluster pair is last so the\n // centred count circle paints on top of the (filtered-out) single-cluster\n // slots without depending on draw-order luck.\n\n return {\n micro: {\n ...withConfig(micro, config, 'micro', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: mapStyleLayerIDs.lowestPlaceLabel,\n },\n main: {\n ...withConfig(main, config, 'main', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: 'selected',\n },\n selected: withConfig(selected, config, 'selected', styleLightDarkTheme, iconTextOffsetScales),\n cluster: {\n ...cluster,\n ...config?.layers?.cluster,\n beforeID: 'clusterBadge',\n },\n clusterBadge: {\n ...clusterBadge,\n ...config?.layers?.clusterBadge,\n beforeID: 'clusterCount',\n },\n clusterCount: {\n ...clusterCount,\n ...config?.layers?.clusterCount,\n beforeID: 'clusterMixedBadge',\n },\n clusterMixedBadge: {\n ...clusterMixedBadge,\n ...config?.layers?.clusterMixedBadge,\n beforeID: 'clusterMixedCount',\n },\n clusterMixedCount: {\n ...clusterMixedCount,\n ...config?.layers?.clusterMixedCount,\n },\n ...config?.layers?.additional,\n };\n};\n","import { SVGIconStyleOptions } from '../../shared';\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { pinSvg } from '../../shared/resources';\n\n/**\n * Default pin for selected images without a specific category on it.\n * @ignore\n */\nexport const defaultPin = (options?: SVGIconStyleOptions): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return svgToImg(pinSvg(options));\n};\n","import { generateId, Place, Places } from '@tomtom-org/maps-sdk/core';\nimport type { Feature, FeatureCollection, LineString } from 'geojson';\nimport type { PlaceConnectionDisplay, PlacesModuleConfig } from '../types/placesModuleConfig';\n\n/**\n * Properties carried by each connection feature once it is on the map.\n * @ignore\n */\nexport type ConnectionFeatureProps = {\n id: string;\n fromId?: string;\n toId?: string;\n label: string;\n};\n\n/**\n * @ignore\n */\nexport type ConnectionFeatureCollection = FeatureCollection<LineString, ConnectionFeatureProps>;\n\n// Looks up an endpoint: a full Place is used directly, a string is resolved against\n// the module's currently shown places. Unknown ids return `undefined` and the whole\n// connection is dropped in `prepareConnectionsForDisplay`.\nconst resolveEndpoint = (endpoint: Place | string, shownPlaces: Places): Place | undefined => {\n if (typeof endpoint !== 'string') return endpoint;\n return shownPlaces.features.find((p) => p.id === endpoint);\n};\n\n/**\n * Turns a list of {@link PlaceConnectionDisplay} into a GeoJSON FeatureCollection of\n * LineStrings, resolving id references against the module's currently shown places and\n * applying the optional label function.\n * @ignore\n */\nexport const prepareConnectionsForDisplay = (\n connections: PlaceConnectionDisplay[],\n shownPlaces: Places,\n config: PlacesModuleConfig = {},\n): ConnectionFeatureCollection => {\n const labelFn = config.connections?.label;\n const features: Feature<LineString, ConnectionFeatureProps>[] = [];\n\n for (const connection of connections) {\n const from = resolveEndpoint(connection.from, shownPlaces);\n const to = resolveEndpoint(connection.to, shownPlaces);\n if (!from || !to) continue;\n\n const id = connection.id ?? generateId();\n const label = labelFn ? labelFn(connection) : '';\n\n features.push({\n type: 'Feature',\n id,\n geometry: {\n type: 'LineString',\n coordinates: [from.geometry.coordinates, to.geometry.coordinates],\n },\n properties: {\n id,\n fromId: typeof connection.from === 'string' ? connection.from : from.id,\n toId: typeof connection.to === 'string' ? connection.to : to.id,\n label,\n },\n });\n }\n\n return { type: 'FeatureCollection', features };\n};\n","// Supported sub-categories for map display pins\n// See: https://github.com/tomtom-internal/mdt-backend-mapbox-gl-js-styles/blob/orbis-preview/src/orbis/sprites/poi_light/config.json\n// For the rest we'll use the main categories (which are the first 4 digits of a category)\nconst supportedPinSubcategories: Set<number> = new Set([\n 7339002, 8099002, 7369004, 7321003, 7376006, 9362003, 9362004, 9160004, 9376007, 7315015, 9376006, 9376002, 7315078,\n 7315149, 9376005, 7372003, 9352045, 9352032, 9361048, 9902003, 9378005, 7383004, 9910004, 7320002, 9362016, 7320003,\n 9362025, 9942002, 9942003, 7380005, 9663003, 9361021, 9379009, 9379004, 7315147, 9376003, 9160002, 9160003, 9352008,\n 9361006, 7389004, 9910006,\n]);\n\n/**\n * @ignore\n */\nexport const toPinImageID = (categoryID: number | undefined): string | undefined => {\n if (!categoryID) {\n return undefined;\n }\n\n // Check if the category ID is in our supported subcategories:\n if (supportedPinSubcategories.has(categoryID)) {\n return categoryID.toString();\n }\n\n // If not, fall back to the main category (first 4 digits of the category ID):\n return categoryID.toString().substring(0, 4);\n};\n","import { generateId, Place, Places, POICategory, poiCategoriesToID } from '@tomtom-org/maps-sdk/core';\nimport { toBaseMapPOICategory, toBaseMapPOIGroup } from '../../pois/util/poiCategoryMapping';\nimport { DEFAULT_BASE_MAP_PLACE_ICON_ID, DEFAULT_PLACE_ICON_ID } from '../../shared/layers/symbolLayers';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { FEATURE_PROPERTY_BASE_MAP_ICON_ID } from '../layers/clusterLayers';\nimport type { DisplayPlaceProps } from '../types/placeDisplayProps';\nimport type { PlacesModuleConfig, PlacesTheme } from '../types/placesModuleConfig';\nimport {\n buildAvailabilityText,\n getAvailabilityRatio,\n getEVAvailabilityIconID,\n isEVStationWithAvailability,\n} from './evAvailabilityHelpers';\nimport { toPinImageID } from './toPinImageID';\n\n/**\n * Builds the title of the place to display it on the map.\n * @param place The place to display.\n * @ignore\n */\nexport const buildPlaceTitle = (place: Place): string =>\n place.properties.poi?.name ?? place.properties.address.freeformAddress;\n\n/**\n * Resolves the image ID to use for the given POI category and icon theme.\n */\nconst toImageID = (poiCategory: POICategory, iconTheme: PlacesTheme, defaultPlaceIconID: string): string => {\n // `pin-clustered` uses the same per-category sprites as `pin` for individual\n // (un-clustered) features — only the cluster pin layer renders the default\n // sprite, regardless of the leaves' categories.\n if (iconTheme === 'pin' || iconTheme === 'pin-clustered') {\n const imageID = toPinImageID(poiCategoriesToID[poiCategory]);\n return imageID ?? defaultPlaceIconID;\n } else {\n const imageID = toBaseMapPOICategory(poiCategory);\n return imageID ? `poi-${imageID}` : defaultPlaceIconID;\n }\n};\n\n// When the caller hasn't registered a custom default image for this module\n// instance, the `base-map` theme should fall back to a built-in micro sprite\n// (small dot next to native POI labels) rather than the large `default_place`\n// pin sprite used by the `pin` theme.\nconst resolveDefaultPlaceIconID = (config: PlacesModuleConfig, instanceIndex: number): string => {\n const hasCustomDefault = !!config.icon?.default;\n if (!hasCustomDefault && (config.theme ?? 'pin') === 'base-map') {\n return DEFAULT_BASE_MAP_PLACE_ICON_ID;\n }\n return suffixNumber(DEFAULT_PLACE_ICON_ID, instanceIndex);\n};\n\n/**\n * Gets the map style sprite image ID to display on the map for the give place.\n * @ignore\n */\nexport const getIconIDForPlace = (place: Place, instanceIndex: number, config: PlacesModuleConfig = {}): string => {\n const iconTheme = config.theme ?? 'pin';\n const defaultPlaceIconID = resolveDefaultPlaceIconID(config, instanceIndex);\n\n const imageMapping = config.icon?.mapping;\n // First, try custom mapping if provided:\n if (imageMapping) {\n if (imageMapping.to === 'imageID') {\n // Direct image ID mapping\n return imageMapping.fn(place);\n } else {\n // POI category mapping - resolve category to icon ID\n return toImageID(imageMapping.fn(place), iconTheme, defaultPlaceIconID);\n }\n }\n\n // Next, try to match any custom icon:\n const poiCategory = place.properties.poi?.categories?.[0] as POICategory;\n\n // Check for EV availability-specific icon selection\n const evAvailabilityIconID = getEVAvailabilityIconID(place, poiCategory, instanceIndex, config, iconTheme);\n if (evAvailabilityIconID) {\n return evAvailabilityIconID;\n }\n\n // Regular custom icon matching (no availability)\n const matchingCustomIcon = config.icon?.categoryIcons?.find((customIcon) => customIcon.id === poiCategory);\n if (matchingCustomIcon) {\n return suffixNumber(matchingCustomIcon.id, instanceIndex);\n }\n\n // Else: if no custom icon matched, we map to the map style icons or default:\n return toImageID(poiCategory, iconTheme, defaultPlaceIconID);\n};\n\n// When a user provides `icon.mapping.to === 'poiCategory'`, that function becomes the\n// authoritative source of the place's category — the original `poi.categories[0]` may be\n// missing or unrelated (e.g., user data that isn't a search-API Place). For base-map\n// themes the style's `icon-image` / `text-color` expressions read `category` / `group`,\n// so without honoring the mapping here those expressions resolve to `poi-` and icons\n// fail to load.\nconst getEffectivePOICategory = (place: Place, config?: PlacesModuleConfig): POICategory | undefined => {\n const mapping = config?.icon?.mapping;\n if (mapping?.to === 'poiCategory') {\n return mapping.fn(place);\n }\n return place.properties.poi?.categories?.[0];\n};\n\n/**\n * Maps a Place category to the poi layer one, so the latter's style can apply it.\n * @ignore\n */\nexport const getPOILayerCategoryForPlace = (place: Place, config?: PlacesModuleConfig): string | undefined => {\n const category = getEffectivePOICategory(place, config);\n // if it's one of the different categories between search and poi layer, use poi layer category\n return category && toBaseMapPOICategory(category);\n};\n\n/**\n * Maps a Place to the base map POI group used by the style's `text-color` and\n * `POI - Micro` icon expressions (e.g., `eat_and_drink`, `lodging`, `driving`).\n * @ignore\n */\nexport const getPOIGroupForPlace = (place: Place, config?: PlacesModuleConfig): string | undefined => {\n const category = getEffectivePOICategory(place, config);\n return category && toBaseMapPOIGroup(category);\n};\n\n/**\n * Transforms the input of a \"show\" call to FeatureCollection \"Places\".\n * @ignore\n */\nexport const toPlaces = (places: Place | Place[] | Places): Places => {\n if (Array.isArray(places)) {\n return { type: 'FeatureCollection', features: places };\n }\n return places.type === 'Feature' ? { type: 'FeatureCollection', features: [places] } : places;\n};\n\n/**\n * Merges EV availability props into extraFeatureProps if enabled.\n * This makes EV stations use the same mechanism as any other custom properties.\n * @ignore\n */\nconst mergeEVAvailabilityProps = (\n extraFeatureProps: PlacesModuleConfig['extraFeatureProps'],\n evAvailabilityConfig: PlacesModuleConfig['evAvailability'],\n places: Places,\n): PlacesModuleConfig['extraFeatureProps'] => {\n if (evAvailabilityConfig?.enabled !== true) {\n return extraFeatureProps;\n }\n\n // Check if any EV stations exist but lack availability data\n let hasEVStations = false;\n let hasEVStationsWithAvailability = false;\n\n for (const place of places.features) {\n const isEVStation = place.properties.poi?.categories?.[0] === 'ELECTRIC_VEHICLE_STATION';\n if (isEVStation) {\n hasEVStations = true;\n if (isEVStationWithAvailability(place)) {\n hasEVStationsWithAvailability = true;\n break;\n }\n }\n }\n\n if (hasEVStations && !hasEVStationsWithAvailability) {\n console.warn(\n 'PlacesModule: evAvailability is enabled but no availability data found. ' +\n 'Did you call getPlacesWithEVAvailability()?',\n );\n }\n\n return {\n ...extraFeatureProps,\n evAvailabilityText: (place: Place) =>\n isEVStationWithAvailability(place) ? buildAvailabilityText(place, evAvailabilityConfig) : '',\n evAvailabilityRatio: (place: Place) => (isEVStationWithAvailability(place) ? getAvailabilityRatio(place) : 0),\n };\n};\n\n// Resolves the per-leaf base-map sprite id consumed by the `pin-clustered`\n// theme's cluster pin (single-category case). It's the same\n// `poi-<category>` sprite the `base-map` theme renders for individual\n// places, which lets the cluster icon match the surrounding base-map style\n// when only one category is present. Falls back to the parking-micro\n// sprite when the place has no recognisable category — same fallback the\n// `base-map` theme uses for unmatched places.\nconst getBaseMapIconIDForPlace = (place: Place, config: PlacesModuleConfig | undefined): string => {\n const category = getPOILayerCategoryForPlace(place, config);\n return category ? `poi-${category}` : DEFAULT_BASE_MAP_PLACE_ICON_ID;\n};\n\n/**\n * prepare places features to be displayed on map by adding needed properties for title, icon and style\n * @ignore\n */\nexport const preparePlacesForDisplay = (\n placesInput: Place | Place[] | Places,\n instanceIndex: number,\n config: PlacesModuleConfig = {},\n): Places<DisplayPlaceProps> => {\n const places = toPlaces(placesInput);\n\n // Only merge EV availability props when explicitly enabled\n const mergedExtraFeatureProps =\n config.evAvailability?.enabled === true\n ? mergeEVAvailabilityProps(config.extraFeatureProps, config.evAvailability, places)\n : config.extraFeatureProps;\n\n return {\n ...places,\n features: places.features.map((place) => {\n const title =\n typeof config?.text?.title === 'function' ? config?.text?.title(place) : buildPlaceTitle(place);\n\n const extraFeatureProps = mergedExtraFeatureProps\n ? Object.fromEntries(\n Object.entries(mergedExtraFeatureProps).map(([prop, value]) => [\n prop,\n typeof value === 'function' ? value(place) : value,\n ]),\n )\n : {};\n\n const id = place.id ?? generateId();\n\n return {\n ...place,\n id,\n geometry: { ...place.geometry, bbox: place.bbox },\n properties: {\n ...place.properties,\n id, // we need id in properties due to promoteId feature\n title,\n iconID: getIconIDForPlace(place, instanceIndex, config),\n // The base-map style's POI / POI - Micro layer expressions key off\n // `category` and `group` for icon-image and text-color. Both the\n // `base-map` theme and the `pin-clustered` theme's micro layer\n // (registered for un-clustered singletons) reuse those expressions,\n // so the leaf needs `category`/`group` populated under either theme.\n ...((config?.theme === 'base-map' || config?.theme === 'pin-clustered') && {\n category: getPOILayerCategoryForPlace(place, config),\n group: getPOIGroupForPlace(place, config),\n }),\n // For the `pin-clustered` theme we promote the per-place\n // base-map sprite ID to a top-level feature property so the\n // source's `clusterProperties` aggregator can read it via\n // `['get', ...]` from each leaf feature. `baseMapIconID`\n // resolves to a `poi-<category>` sprite and feeds the cluster pin's\n // single-vs-mixed `match` expression.\n ...(config?.theme === 'pin-clustered' && {\n [FEATURE_PROPERTY_BASE_MAP_ICON_ID]: getBaseMapIconIDForPlace(place, config),\n }),\n ...extraFeatureProps,\n },\n };\n }),\n };\n};\n","import type { Place, Places } from '@tomtom-org/maps-sdk/core';\nimport type { LayerSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type {\n CleanEventStateOptions,\n CleanEventStatesOptions,\n GeoJSONSourceClusterOptions,\n PutEventStateOptions,\n ToBeAddedLayerSpecWithoutSource,\n} from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { DEFAULT_PLACE_ICON_ID } from '../shared/layers/symbolLayers';\nimport { suffixNumber } from '../shared/layers/utils';\nimport { addLayers, addOrUpdateImage, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { clusteredPlacesAggregateProperties } from './layers/clusterLayers';\nimport { buildConnectionLayerSpecs } from './layers/connectionsLayers';\nimport { buildPlacesLayerSpecs } from './layers/placesLayers';\nimport { defaultPin } from './resources';\nimport type { DisplayPlaceProps } from './types/placeDisplayProps';\nimport {\n PlaceConnectionDisplay,\n PlaceIconConfig,\n PlaceLayerName,\n PlacesModuleConfig,\n PlacesTheme,\n PlaceTextConfig,\n} from './types/placesModuleConfig';\nimport type { ConnectionFeatureCollection } from './utils/prepareConnectionsForDisplay';\nimport { prepareConnectionsForDisplay } from './utils/prepareConnectionsForDisplay';\nimport { preparePlacesForDisplay } from './utils/preparePlacesForDisplay';\n\ntype PlacesSourcesAndLayers = {\n /**\n * Places source id with corresponding layers ids. Carries every layer the\n * module owns under non-clustered themes; under `pin-clustered` it carries\n * everything except `micro` and is configured with `cluster: true` so the\n * pin / cluster pin / cluster-badge / cluster-count / mixed-cluster\n * badge + count layers all see aggregated cluster features.\n */\n places: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>;\n /**\n * Companion source — only created under `pin-clustered`. Feeds the `micro`\n * layer with the same place features but **without** clustering, so the\n * tiny base-map-style POI dots render on every place individually\n * regardless of how the larger pins above them get aggregated.\n */\n placesUnclustered?: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>;\n /**\n * Connections source id with corresponding layers ids (line + label).\n */\n connections: GeoJSONSourceWithLayers<ConnectionFeatureCollection>;\n};\n\ntype ConnectionLayerSpecMap = {\n line: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>;\n label: ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>;\n};\n\n/**\n * Map module for displaying and managing place markers.\n *\n * The PlacesModule provides functionality to display location markers (pins) on the map\n * for points of interest, search results, or custom locations. It supports various marker\n * styles, custom icons, text labels, and interactive events.\n *\n * @remarks\n * **Features:**\n * - Multiple marker styles (pin, circle-icon, base-map POI-like)\n * - Custom icons per POI category\n * - Text labels with styling options\n * - Data-driven styling via MapLibre expressions\n * - Interactive events (click, hover, etc.)\n * - Support for custom feature properties\n * - EV charging station availability display (opt-in)\n *\n * **Marker Styles:**\n * - `pin`: Traditional teardrop-shaped map pins\n * - `circle-icon`: Simple circular markers (previously named `circle`)\n * - `base-map`: Mimics the map's built-in POI layer styling (combines `main`, `selected`, and `micro` layers). For micro-only rendering, hide the `main` layer via `layers.main.layout.visibility = 'none'`.\n *\n * **EV Charging Station Availability:**\n * When displaying EV charging stations with availability data from\n * {@link getPlacesWithEVAvailability}, the module can:\n * - Show available/total charging points (e.g., \"3/10\")\n * - Color-code availability (green = good, orange = limited, red = none/low)\n * - Display as formatted text within the station's label\n *\n * This feature is disabled by default. To enable it, set `evAvailability.enabled` to `true`\n * in the configuration.\n *\n * **Common Use Cases:**\n * - Search result visualization\n * - Custom location markers\n * - Store locators\n * - EV charging station maps with real-time availability\n * - Delivery/pickup points\n * - Saved locations display\n *\n * @example\n * ```typescript\n * // Create places module with pin markers\n * const placesModule = await PlacesModule.get(map, {\n * icon: {\n * categoryIcons: []\n * },\n * text: {\n * field: (place) => place.properties.poi?.name || 'Unknown'\n * },\n * theme: 'pin'\n * });\n *\n * // Display places from search\n * await placesModule.show(searchResults);\n *\n * // EV Charging Stations - Opt-in to availability display\n * const evStations = await PlacesModule.get(map, {\n * evAvailability: { enabled: true }\n * });\n * const results = await search({ poiCategories: ['ELECTRIC_VEHICLE_STATION'] });\n * evStations.show(await getPlacesWithEVAvailability(results)); // Shows availability\n *\n * // Granular control: Enable for searched stations only, background stations without\n * const bgStations = await PlacesModule.get(map); // EV availability disabled\n * const searched = await PlacesModule.get(map, {\n * evAvailability: { enabled: true }\n * });\n *\n * // Handle clicks\n * placesModule.events.on('click', (feature) => {\n * console.log('Clicked:', feature.properties);\n * });\n *\n * placesModule.events.on('hover', (feature) => {\n * showTooltip(feature.properties.poi?.name);\n * });\n * ```\n *\n * @see [Places Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/places)\n *\n * @group Places\n */\n// Default radius (in pixels) used by MapLibre when clustering pins for the\n// `pin-clustered` theme. Tuned to the default pin sprite footprint so that\n// adjacent pins are visually merged but cities/blocks aren't collapsed into\n// a single cluster.\nconst DEFAULT_CLUSTER_RADIUS_PX = 60;\n\nexport class PlacesModule extends AbstractMapModule<PlacesSourcesAndLayers, PlacesModuleConfig> {\n // Cluster layers (`clusterBadge` in particular) aren't symbol layers, so the\n // record value is widened to the full layer-spec union.\n private layerSpecs!: Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>;\n private connectionLayerSpecs!: ConnectionLayerSpecMap;\n // Tracks the cluster mode reflected in the currently-mounted source. Used to\n // detect cluster mode toggles in `updateLayersAndData` so we can rebuild the\n // source (MapLibre fixes `cluster` at source-creation time).\n private currentClusterMode = false;\n private sourceID!: string;\n // Companion source id used by the `pin-clustered` theme to back the\n // un-clustered `micro` layer. Allocated alongside `sourceID` on first init\n // so it stays stable across rebuilds.\n private unclusteredSourceID!: string;\n private connectionSourceID!: string;\n private layerIDPrefix!: string;\n private defaultPlaceIconID!: string;\n // Assigned in `_initSourcesWithLayers` (which runs before `_applyConfig` during the\n // base-class constructor). A class-field initializer would be too late: it runs\n // after `super()`, by which point `_applyConfig` has already read this field.\n private shownConnections!: PlaceConnectionDisplay[];\n private readonly shownFeaturesHandlers: ((features: Place | Place[] | Places) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n */\n static async get(tomtomMap: TomTomMap, config?: PlacesModuleConfig): Promise<PlacesModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new PlacesModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: PlacesModuleConfig) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: PlacesModuleConfig, restore?: boolean): PlacesSourcesAndLayers {\n // `instanceIndex` is auto-assigned by AbstractMapModule on construction; nothing\n // to bump on restore (we keep the same IDs across style changes).\n if (!restore) {\n this.sourceID = `places-${this.instanceIndex}`;\n this.unclusteredSourceID = `places-${this.instanceIndex}-unclustered`;\n this.connectionSourceID = `places-connections-${this.instanceIndex}`;\n this.layerIDPrefix = this.sourceID;\n this.defaultPlaceIconID = suffixNumber(DEFAULT_PLACE_ICON_ID, this.instanceIndex);\n this.shownConnections = [];\n }\n\n // Update each layer id with the instance-specific prefix\n this.layerSpecs = this.buildLayerSpecs(config);\n\n const clusterOptions = this.buildClusterOptions(config);\n this.currentClusterMode = clusterOptions?.cluster === true;\n\n // Under `pin-clustered` we split the layer specs across two sources:\n // - clustered (`places`): every layer that needs aggregated cluster\n // features (main pin, selected, cluster pin, cluster badge,\n // cluster count, mixed-cluster badge, mixed-cluster count)\n // - unclustered (`placesUnclustered`): just `micro`, so the small\n // base-map-style POI dots render per place even when the bigger\n // pins above them collapse into clusters\n // For all other themes there's only one source; the second one stays\n // `undefined` to keep the surface area minimal.\n const splitSpecs = this.partitionSpecsForSources(this.layerSpecs);\n\n // Defer the built-in layer add (`false`) — our specs use `beforeID` stacking\n // (`micro` → `main` → `selected` → `cluster` → `clusterBadge` →\n // `clusterCount` → `clusterMixedBadge` → `clusterMixedCount`)\n // which `ensureLayersAddedToMap` can't resolve topologically. `addLayers` walks\n // the dependency order instead; running it at init keeps the layers on the map\n // before `updateLayersAndSource` touches them.\n const places = new GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>(\n this.mapLibreMap,\n this.sourceID,\n splitSpecs.clusteredSpecs,\n false,\n clusterOptions,\n );\n\n let placesUnclustered: GeoJSONSourceWithLayers<Places<DisplayPlaceProps>> | undefined;\n if (splitSpecs.unclusteredSpecs.length > 0) {\n placesUnclustered = new GeoJSONSourceWithLayers<Places<DisplayPlaceProps>>(\n this.mapLibreMap,\n this.unclusteredSourceID,\n splitSpecs.unclusteredSpecs,\n false,\n );\n }\n\n // Both source's layers must be on the map before `addLayers` resolves\n // any cross-spec `beforeID` dependencies. The combined list is what\n // existing layers (`cluster`, the row, etc.) anchor against, so feed\n // them in together.\n addLayers([...places._layerSpecs, ...(placesUnclustered?._layerSpecs ?? [])], this.mapLibreMap);\n\n // Connections are added after places so that their `beforeID` anchor (the\n // places `main` layer) already exists on the map — MapLibre then inserts\n // connection layers below places, keeping pins visible on top of the lines.\n this.connectionLayerSpecs = this.buildConnectionLayerSpecs(config);\n const connections = new GeoJSONSourceWithLayers<ConnectionFeatureCollection>(\n this.mapLibreMap,\n this.connectionSourceID,\n Object.values(this.connectionLayerSpecs),\n false,\n );\n addLayers(connections._layerSpecs, this.mapLibreMap);\n\n // Only include `placesUnclustered` when it actually exists — the base\n // class iterates every entry to derive `sourceAndLayerIDs`, so an\n // undefined value would crash on themes that don't use the companion.\n return placesUnclustered ? { places, placesUnclustered, connections } : { places, connections };\n }\n\n // Splits the per-name layer specs into the clustered and unclustered\n // buckets. Under `pin-clustered` the `micro` slot belongs to the\n // unclustered bucket so its base-map-style sprites render per leaf\n // regardless of how the cluster aggregator collapses points; every other\n // theme leaves the unclustered bucket empty (single-source mode).\n private partitionSpecsForSources(\n layerSpecs: Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>,\n ): {\n clusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[];\n unclusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[];\n } {\n if (!this.currentClusterMode) {\n return { clusteredSpecs: Object.values(layerSpecs), unclusteredSpecs: [] };\n }\n const unclusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[] = [];\n const clusteredSpecs: ToBeAddedLayerSpecWithoutSource<LayerSpecification>[] = [];\n for (const [name, spec] of Object.entries(layerSpecs)) {\n if (name === 'micro') unclusteredSpecs.push(spec);\n else clusteredSpecs.push(spec);\n }\n return { clusteredSpecs, unclusteredSpecs };\n }\n\n private buildLayerSpecs(\n config?: PlacesModuleConfig,\n ): Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>> {\n const layerSpecTemplates = buildPlacesLayerSpecs(\n config,\n this.tomtomMap.mapLibreMap,\n this.tomtomMap.styleLightDarkTheme,\n this.instanceIndex,\n );\n const internalNames = new Set(Object.keys(layerSpecTemplates));\n\n // Rewrite internal layer IDs and any `beforeID` that refers to another\n // internal layer (e.g., `main`, `selected`) into instance-scoped IDs.\n return Object.fromEntries(\n Object.entries(layerSpecTemplates).map(([key, spec]) => {\n const beforeID = (spec as { beforeID?: string }).beforeID;\n return [\n key,\n {\n ...spec,\n id: `${this.layerIDPrefix}-${key}`,\n ...(beforeID && internalNames.has(beforeID)\n ? { beforeID: `${this.layerIDPrefix}-${beforeID}` }\n : {}),\n },\n ];\n }),\n ) as Record<PlaceLayerName, ToBeAddedLayerSpecWithoutSource<LayerSpecification>>;\n }\n\n // Returns the cluster options to apply on the GeoJSON source for the given\n // theme, or `undefined` to leave clustering off entirely. Only the\n // `pin-clustered` theme opts in.\n private buildClusterOptions(config?: PlacesModuleConfig): GeoJSONSourceClusterOptions | undefined {\n if (config?.theme !== 'pin-clustered') return undefined;\n return {\n cluster: true,\n clusterRadius: DEFAULT_CLUSTER_RADIUS_PX,\n clusterProperties: clusteredPlacesAggregateProperties,\n };\n }\n\n private buildConnectionLayerSpecs(config?: PlacesModuleConfig): ConnectionLayerSpecMap {\n const specs = buildConnectionLayerSpecs(config?.connections);\n const lineID = `${this.layerIDPrefix}-connection-line`;\n const labelID = `${this.layerIDPrefix}-connection-label`;\n // Stack connections under the base-map label layer (`lowestLabel`) so city /\n // street labels and place pins all render on top. `addLayers` resolves\n // `beforeID` in dependency order; two layers sharing the same anchor end up\n // stacked in insertion order (line first → label on top).\n const anchor = mapStyleLayerIDs.lowestLabel;\n return {\n line: { ...specs.line, id: lineID, beforeID: anchor },\n label: { ...specs.label, id: labelID, beforeID: anchor },\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: PlacesModuleConfig | undefined) {\n this.updateLayersAndData(config);\n return config;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previousShownFeatures = this.sourcesWithLayers.places.shownFeatures;\n const previousConnections = this.shownConnections;\n this.initSourcesWithLayers(this.config, true);\n this.config && this._applyConfig(this.config);\n this.show(previousShownFeatures);\n if (previousConnections.length) {\n this.showConnections(previousConnections);\n }\n }\n\n /**\n * Updates the visual theme for displayed places.\n *\n * @param theme - The theme style to apply to place markers.\n *\n * @remarks\n * **Available Themes:**\n * - `pin`: Traditional teardrop-shaped map pins\n * - `circle-icon`: Simple circular markers (previously named `circle`)\n * - `base-map`: Mimics the map's built-in POI layer style with category icons (combines `main`, `selected`, and `micro`). For micro-only rendering, hide the `main` layer via `layers.main.layout.visibility = 'none'`.\n *\n * Changes apply immediately to all currently shown places. Other configuration\n * properties (icon config, text config) remain unchanged.\n *\n * @example\n * ```typescript\n * // Switch to pin markers\n * placesModule.applyTheme('pin');\n *\n * // Use simple circles\n * placesModule.applyTheme('circle-icon');\n *\n * // Match map's POI style (ideal to blend in)\n * placesModule.applyTheme('base-map');\n * ```\n */\n applyTheme(theme: PlacesTheme): void {\n this.applyConfigPart({ theme });\n }\n\n /**\n * Updates the icon configuration for displayed places.\n *\n * @param iconConfig - New icon configuration settings.\n *\n * @remarks\n * - Changes apply immediately to currently shown places\n * - Custom icons are loaded if not already in style\n * - Other configuration properties remain unchanged\n *\n * @example\n * ```typescript\n * placesModule.applyIconConfig({\n * categoryIcons: [\n * { category: 'RESTAURANT', id: 'restaurant-icon', image: '/icons/food.png' }\n * ]\n * });\n * ```\n */\n applyIconConfig(iconConfig: PlaceIconConfig): void {\n this.applyConfigPart({ icon: iconConfig });\n }\n\n /**\n * Updates the text/label configuration for displayed places.\n *\n * @param textConfig - New text configuration settings.\n *\n * @remarks\n * Supports both functions and MapLibre expressions for dynamic text.\n *\n * @example\n * ```typescript\n * // Use function\n * placesModule.applyTextConfig({\n * field: (place) => place.properties.poi?.name || 'Unknown'\n * });\n *\n * // Use MapLibre expression\n * placesModule.applyTextConfig({\n * field: ['get', 'title'],\n * size: 14,\n * color: '#333'\n * });\n * ```\n */\n applyTextConfig(textConfig: PlaceTextConfig): void {\n this.applyConfigPart({ text: textConfig });\n }\n\n private applyConfigPart(partialConfig: Partial<PlacesModuleConfig>): void {\n const config = { ...this.config, ...partialConfig };\n this.updateLayersAndData(config);\n this.config = config;\n this.emitConfigChange();\n }\n\n /**\n * Applies additional feature properties to displayed places.\n *\n * @param extraFeatureProps - Object mapping property names to values or functions.\n *\n * @remarks\n * Useful for adding computed properties or metadata for styling/filtering.\n *\n * @example\n * ```typescript\n * placesModule.applyExtraFeatureProps({\n * category: (place) => place.properties.poi?.localizedCategories?.[0],\n * rating: (place) => place.properties.poi?.rating || 0,\n * isOpen: true\n * });\n * ```\n */\n applyExtraFeatureProps(extraFeatureProps: { [key: string]: any }): void {\n const config = { ...this.config, extraFeatureProps };\n this.updateData(config);\n this.config = config;\n this.emitConfigChange();\n }\n\n private updateLayersAndData(config: PlacesModuleConfig | undefined): void {\n this.setupImages(config);\n\n // Toggling clustering on/off requires recreating the GeoJSON source — MapLibre\n // fixes the `cluster` flag at source-creation time. The fast `updateLayersAndSource`\n // path can't help here, so we tear the module down and rebuild it from scratch via\n // the same flow used for style restorations.\n const nextClusterMode = this.buildClusterOptions(config)?.cluster === true;\n if (nextClusterMode !== this.currentClusterMode) {\n this.rebuildSourcesAndLayers(config);\n return;\n }\n\n const newLayerSpecs = this.buildLayerSpecs(config);\n // The two-source pin-clustered layout means specs per source can no\n // longer change in lock-step here: a layer's `source` is fixed at add\n // time, and partitioning re-evaluates it. Within a single cluster\n // mode the assignment is stable (clustered: everything-but-micro;\n // unclustered: micro), so we can update each source's specs in place.\n const newSplit = this.currentClusterMode\n ? this.partitionSpecsForSources(newLayerSpecs)\n : { clusteredSpecs: Object.values(newLayerSpecs), unclusteredSpecs: [] };\n const oldSplit = this.currentClusterMode\n ? this.partitionSpecsForSources(this.layerSpecs)\n : { clusteredSpecs: Object.values(this.layerSpecs), unclusteredSpecs: [] };\n updateLayersAndSource(\n newSplit.clusteredSpecs,\n oldSplit.clusteredSpecs,\n this.sourcesWithLayers.places,\n this.mapLibreMap,\n );\n if (this.sourcesWithLayers.placesUnclustered && newSplit.unclusteredSpecs.length > 0) {\n updateLayersAndSource(\n newSplit.unclusteredSpecs,\n oldSplit.unclusteredSpecs,\n this.sourcesWithLayers.placesUnclustered,\n this.mapLibreMap,\n );\n }\n this.layerSpecs = newLayerSpecs;\n this.applyPlacesVisibility();\n this.updateData(config);\n this.updateConnectionLayersAndData(config);\n }\n\n // Rebuilds the underlying source + layers when the cluster flag changes.\n // Mirrors `restoreDataAndConfigImpl` (used on style reload) but without the\n // recursive `_applyConfig` call — the caller is *already* applying config.\n private rebuildSourcesAndLayers(config: PlacesModuleConfig | undefined): void {\n const previousShownFeatures = this.sourcesWithLayers.places.shownFeatures;\n const previousConnections = this.shownConnections;\n for (const spec of Object.values(this.layerSpecs)) {\n if (this.mapLibreMap.getLayer(spec.id)) this.mapLibreMap.removeLayer(spec.id);\n }\n for (const spec of Object.values(this.connectionLayerSpecs)) {\n if (this.mapLibreMap.getLayer(spec.id)) this.mapLibreMap.removeLayer(spec.id);\n }\n if (this.mapLibreMap.getSource(this.sourceID)) this.mapLibreMap.removeSource(this.sourceID);\n // The unclustered companion only exists under `pin-clustered`; on\n // teardown we always check before removing so leaving and re-entering\n // the theme doesn't leave a stale source on the map.\n if (this.mapLibreMap.getSource(this.unclusteredSourceID)) {\n this.mapLibreMap.removeSource(this.unclusteredSourceID);\n }\n if (this.mapLibreMap.getSource(this.connectionSourceID)) this.mapLibreMap.removeSource(this.connectionSourceID);\n\n this.initSourcesWithLayers(config, true);\n this.setupImages(config);\n const prepared = preparePlacesForDisplay(previousShownFeatures, this.instanceIndex, config);\n // Opt out of `setLayersVisible` inside `show` — it would mark every\n // layer in the source as visible, defeating the spec-level\n // `visibility: 'none'` we use to keep theme-hidden cluster slots\n // (badge / count / mixed badge / mixed count) from leaking onto\n // themes that don't cluster. `applyPlacesVisibility` runs after and\n // honours those flags.\n this.sourcesWithLayers.places.show(prepared, { automaticVisibility: false });\n this.sourcesWithLayers.placesUnclustered?.show(prepared, { automaticVisibility: false });\n this.applyPlacesVisibility();\n if (previousConnections.length) {\n this.shownConnections = [...previousConnections];\n this.updateConnectionsData(config);\n }\n }\n\n private updateConnectionLayersAndData(config: PlacesModuleConfig | undefined): void {\n const newSpecs = this.buildConnectionLayerSpecs(config);\n const connectionsSource = this.sourcesWithLayers.connections;\n updateLayersAndSource(\n Object.values(newSpecs),\n Object.values(this.connectionLayerSpecs),\n connectionsSource,\n this.mapLibreMap,\n );\n this.connectionLayerSpecs = newSpecs;\n this.updateConnectionsData(config);\n }\n\n private updateConnectionsData(config: PlacesModuleConfig | undefined): void {\n const shownPlaces = this.sourcesWithLayers.places.shownFeatures;\n this.sourcesWithLayers.connections.show(\n prepareConnectionsForDisplay(this.shownConnections, shownPlaces, config),\n );\n }\n\n // Data-driven \"visible iff any features\" — but skip specs carrying\n // `visibility: 'none'` so theme-hidden slots (e.g., `micro` on pin/circle-icon) and\n // caller-hidden layers (via `layers.*`) stay hidden. We iterate `this.layerSpecs`\n // rather than `placesSource._layerSpecs` because the latter is stale for updated\n // layers after a theme switch.\n //\n // The two-source split under `pin-clustered` doesn't change the visibility\n // contract: every layer's id still appears in `this.layerSpecs`, so a\n // single sweep here flips visibility correctly across both sources.\n private applyPlacesVisibility(): void {\n const hasData = !!this.sourcesWithLayers.places.shownFeatures.features.length;\n const visibility = hasData ? 'visible' : 'none';\n for (const spec of Object.values(this.layerSpecs)) {\n if (spec.layout?.visibility === 'none') {\n continue;\n }\n this.mapLibreMap.setLayoutProperty(spec.id, 'visibility', visibility, { validate: false });\n }\n }\n\n private setupImages(config: PlacesModuleConfig | undefined): void {\n // Ensure default pin is added:\n if (config?.icon) {\n // If we have custom icons, ensure they're added to the map style:\n for (const customIcon of config.icon.categoryIcons ?? []) {\n // Create unique ID for each custom icon, including availability level if present\n const iconID = customIcon.availabilityLevel\n ? `${customIcon.id}-${customIcon.availabilityLevel}`\n : customIcon.id;\n\n addOrUpdateImage(\n 'if-not-in-sprite',\n suffixNumber(iconID, this.instanceIndex),\n customIcon.image as string | HTMLImageElement,\n this.mapLibreMap,\n {\n pixelRatio: customIcon.pixelRatio ?? 2,\n },\n );\n }\n\n if (config.icon.default) {\n if (config.icon.default.image) {\n addOrUpdateImage(\n 'if-not-in-sprite',\n this.defaultPlaceIconID,\n config.icon.default.image.image as string | HTMLImageElement,\n this.mapLibreMap,\n {\n pixelRatio: config.icon.default.image.pixelRatio ?? 2,\n },\n );\n }\n if (config.icon.default.style) {\n addOrUpdateImage(\n 'if-not-in-sprite',\n this.defaultPlaceIconID,\n defaultPin(config.icon.default.style),\n this.mapLibreMap,\n { pixelRatio: 2 },\n );\n }\n }\n } else {\n // Ensure default pin is added:\n addOrUpdateImage('if-not-in-sprite', this.defaultPlaceIconID, defaultPin(), this.mapLibreMap, {\n pixelRatio: 2,\n });\n }\n }\n\n private updateData(config: PlacesModuleConfig | undefined): void {\n const prepared = preparePlacesForDisplay(\n this.sourcesWithLayers.places.shownFeatures,\n this.instanceIndex,\n config,\n );\n this.sourcesWithLayers.places.source.runtimeSource?.setData(prepared);\n // Mirror the same data into the unclustered companion (if any) so the\n // micro layer stays in sync with the rest of the module.\n this.sourcesWithLayers.placesUnclustered?.source.runtimeSource?.setData(prepared);\n }\n\n /**\n * Displays the given places on the map.\n *\n * @param places - Place data to display. Can be a single Place, array of Places,\n * or a Places FeatureCollection.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown places\n * - Applies current module styling configuration\n * - Automatically generates labels if text config is set\n * - Waits for module to be ready before displaying\n *\n * **Data Sources:**\n * - TomTom Search API results\n * - Custom place objects matching the Place interface\n * - GeoJSON Point features\n *\n * @example\n * Display search results:\n * ```typescript\n * import { search } from '@tomtom-international/maps-sdk-js/services';\n *\n * const results = await search({ query: 'coffee' });\n * await placesModule.show(results);\n * ```\n *\n * @example\n * Display single place:\n * ```typescript\n * await placesModule.show({\n * type: 'Feature',\n * geometry: { type: 'Point', coordinates: [4.9041, 52.3676] },\n * properties: {\n * address: { freeformAddress: 'Amsterdam' },\n * poi: { name: 'Amsterdam Central' }\n * }\n * });\n * ```\n *\n * @example\n * Display multiple places:\n * ```typescript\n * await placesModule.show([place1, place2, place3]);\n * ```\n */\n async show(places: Place | Place[] | Places) {\n await this.waitUntilModuleReady();\n // Calling `show` swaps the underlying place set, which invalidates any\n // previously-shown connections (their endpoints may no longer exist). Clear\n // them before showing new places so stale lines don't linger.\n this.clearConnectionsSync();\n const prepared = preparePlacesForDisplay(places, this.instanceIndex, this.config);\n // Opt out of the blanket visibility flip — `applyPlacesVisibility` honors\n // spec-level `visibility: 'none'` so theme-hidden layers stay hidden.\n this.sourcesWithLayers.places.show(prepared, { automaticVisibility: false });\n // Mirror into the unclustered companion (only present under\n // `pin-clustered`) so the `micro` layer renders the same place set.\n this.sourcesWithLayers.placesUnclustered?.show(prepared, { automaticVisibility: false });\n this.applyPlacesVisibility();\n for (const handler of this.shownFeaturesHandlers) {\n handler(places);\n }\n }\n\n /**\n * Removes all places from the map.\n *\n * @remarks\n * - Clears all displayed places\n * - Does not reset styling configuration\n * - Module remains initialized and ready for new data\n *\n * @example\n * ```typescript\n * await placesModule.clear();\n * ```\n */\n async clear() {\n await this.waitUntilModuleReady();\n this.clearConnectionsSync();\n this.sourcesWithLayers.places.clear();\n this.sourcesWithLayers.placesUnclustered?.clear();\n }\n\n /**\n * Displays connection lines between pairs of places on the map.\n *\n * @param connections - List of connections to render. Each connection has a `from`\n * and `to` endpoint, expressed either as a {@link Place} or as the id of a place\n * currently shown by this module.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown connections\n * - Connections whose id reference cannot be resolved against the currently shown\n * places are silently skipped\n * - When {@link PlaceConnectionsConfig.label} is set on the module config, the\n * returned string is rendered as a label along each line, slightly offset to\n * the side. Without a label function, no label is shown.\n * - Calling `show()` or `clear()` pre-clears connections, since their endpoints\n * may no longer be on the map.\n *\n * @example\n * ```typescript\n * const places = await PlacesModule.get(map, {\n * connections: {\n * label: (c) => `${c.distanceMeters} m`\n * }\n * });\n * await places.show([station, ...cafes]);\n * await places.showConnections(\n * cafes.features.map((cafe) => ({\n * from: station.id,\n * to: cafe.id,\n * distanceMeters: Math.round(cafe.properties.dist ?? 0),\n * })),\n * );\n * ```\n */\n async showConnections(connections: PlaceConnectionDisplay[]) {\n await this.waitUntilModuleReady();\n this.shownConnections = [...connections];\n this.updateConnectionsData(this.config);\n }\n\n /**\n * Removes all connection lines from the map, leaving displayed places intact.\n *\n * @example\n * ```typescript\n * await placesModule.clearConnections();\n * ```\n */\n async clearConnections() {\n await this.waitUntilModuleReady();\n this.clearConnectionsSync();\n }\n\n private clearConnectionsSync(): void {\n this.shownConnections = [];\n this.sourcesWithLayers.connections?.clear();\n }\n\n /**\n * Returns the currently shown places.\n *\n * @returns The places currently displayed on the map.\n *\n * @remarks\n * Returns the exact data that was passed to the `show()` method.\n *\n * @example\n * ```typescript\n * const shown = placesModule.getShown();\n * console.log(`Showing ${shown.places.features.length} places`);\n * ```\n */\n getShown() {\n return {\n places: this.sourcesWithLayers.places.shownFeatures,\n connections: [...this.shownConnections],\n };\n }\n\n /**\n * Programmatically sets an event state on a specific place.\n *\n * @param options - Configuration for the event state to apply.\n *\n * @remarks\n * Use this to make places appear clicked or hovered programmatically.\n *\n * @example\n * ```typescript\n * // Make first place appear clicked\n * placesModule.putEventState({\n * index: 0,\n * state: 'click',\n * mode: 'put'\n * });\n * ```\n */\n putEventState(options: PutEventStateOptions) {\n this.sourcesWithLayers.places.putEventState(options);\n }\n\n /**\n * Removes an event state from a specific place.\n *\n * @param options - Configuration for which event state to remove.\n *\n * @example\n * ```typescript\n * placesModule.cleanEventState({ index: 0 });\n * ```\n */\n cleanEventState(options: CleanEventStateOptions): void {\n this.sourcesWithLayers.places.cleanEventState(options);\n }\n\n /**\n * Removes event states from multiple places.\n *\n * @param options - Optional filter for which states to remove.\n *\n * @example\n * ```typescript\n * // Remove all event states\n * placesModule.cleanEventStates();\n *\n * // Remove only hover states\n * placesModule.cleanEventStates({ states: ['hover'] });\n * ```\n */\n cleanEventStates(options?: CleanEventStatesOptions) {\n this.sourcesWithLayers.places.cleanEventStates(options);\n }\n\n get events(): CombinedEvents<Place<DisplayPlaceProps>, PlacesModuleConfig, Place | Place[] | Places> {\n return new CombinedEvents(\n new UserEvents<Place<DisplayPlaceProps>>(\n this.eventsProxy,\n this.sourcesWithLayers.places,\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n}\n","import type { ExpressionFilterSpecification, FilterSpecification, LegacyFilterSpecification } from 'maplibre-gl';\nimport type { FilterShowMode, FilterSyntaxVersion, MultiSyntaxFilter, ValuesFilter } from './types';\n\n/**\n * @ignore\n */\nconst isExpressionFilter = (filter: FilterSpecification): filter is ExpressionFilterSpecification => {\n if (filter === true || filter === false) {\n return true;\n }\n\n if (!Array.isArray(filter) || filter.length === 0) {\n return false;\n }\n switch (filter[0]) {\n case 'has':\n return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type';\n\n case 'in':\n return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2]));\n\n case '!in':\n case '!has':\n case 'none':\n return false;\n\n case '==':\n case '!=':\n case '>':\n case '>=':\n case '<':\n case '<=':\n return filter.length !== 3 || Array.isArray(filter[1]) || Array.isArray(filter[2]);\n\n case 'any':\n case 'all':\n for (const f of filter.slice(1)) {\n if (!isExpressionFilter(f as FilterSpecification) && typeof f !== 'boolean') {\n return false;\n }\n }\n return true;\n\n default:\n return true;\n }\n};\n\n/**\n * @ignore\n */\nexport const getSyntaxVersion = (expression: FilterSpecification): FilterSyntaxVersion =>\n isExpressionFilter(expression) ? 'expression' : 'legacy';\n\n/**\n * @ignore\n */\nexport const getMergedAnyFilter = (filters: MultiSyntaxFilter[]): MultiSyntaxFilter | null => {\n if (!filters?.length) {\n return null;\n }\n if (filters.length === 1) {\n return filters[0];\n }\n return {\n expression: ['any', ...filters.map((filter) => filter?.expression)],\n legacy: ['any', ...filters.map((filter) => filter?.legacy)],\n };\n};\n\n/**\n * @ignore\n */\nexport const getMergedAllFilter = (\n filterToAdd: MultiSyntaxFilter,\n originalFilter: FilterSpecification | undefined,\n): FilterSpecification => {\n if (originalFilter) {\n return ['all', filterToAdd[getSyntaxVersion(originalFilter)], originalFilter] as FilterSpecification;\n }\n return filterToAdd.expression;\n};\n\n/**\n * @ignore\n */\nexport const buildMappedValuesFilter = <T>(\n propName: string,\n showMode: FilterShowMode,\n values: T[],\n): MultiSyntaxFilter => {\n if (values.length === 1) {\n const comparator = showMode === 'only' ? '==' : '!=';\n return {\n expression: [comparator, ['get', propName], values[0]] as ExpressionFilterSpecification,\n legacy: [comparator, propName, values[0]] as LegacyFilterSpecification,\n };\n }\n const filterArrayNew = ['in', ['get', propName], ['literal', values]];\n if (showMode === 'only') {\n return {\n expression: filterArrayNew as ExpressionFilterSpecification,\n legacy: ['in', propName, ...values] as LegacyFilterSpecification,\n };\n }\n return {\n expression: ['!', filterArrayNew as ExpressionFilterSpecification],\n legacy: ['!in', propName, ...values] as LegacyFilterSpecification,\n };\n};\n\n/**\n * @ignore\n */\nexport const buildValuesFilter = <T>(\n propName: string,\n filter: ValuesFilter<T>,\n valuesMapping?: (value: T) => unknown,\n): MultiSyntaxFilter =>\n buildMappedValuesFilter(propName, filter.show, valuesMapping ? filter.values.map(valuesMapping) : filter.values);\n","import type { MapStylePOICategory } from '../../places';\n\n/**\n * Predefined groups of related POI categories for convenient filtering.\n *\n * @remarks\n * This object maps group names to arrays of {@link MapStylePOICategory} values.\n * Each group contains POI categories that share a common theme or purpose,\n * making it easier to filter multiple related POI types with a single identifier.\n *\n * **Available Groups:**\n * - `FOOD_DRINKS_GROUP` - Dining and beverage establishments (restaurants, cafes, pubs, etc.)\n * - `SHOPPING_GROUP` - Retail stores and shopping centers (shops, markets, supermarkets, etc.)\n * - `TRANSPORTATION_GROUP` - Transportation hubs and stops (airports, stations, terminals, etc.)\n * - `HEALTH_GROUP` - Healthcare facilities and services (hospitals, clinics, pharmacies, etc.)\n * - `PARKING_GROUP` - Parking facilities (garages and open parking areas)\n * - `HOLIDAY_TOURISM_GROUP` - Tourist attractions and recreational sites (museums, parks, beaches, etc.)\n * - `EV_CHARGING_STATIONS_GROUP` - Electric vehicle charging locations\n * - `GAS_STATIONS_GROUP` - Fuel stations for traditional vehicles\n * - `ACCOMMODATION_GROUP` - Lodging facilities (hotels, motels, camping grounds, etc.)\n * - `ENTERTAINMENT_GROUP` - Entertainment venues (cinemas, theaters, nightlife, casinos, etc.)\n * - `SPORTS_LEISURE_GROUP` - Sports and leisure facilities (stadiums, gyms, pools, golf courses, etc.)\n * - `EDUCATION_GROUP` - Educational institutions (schools, universities, libraries, etc.)\n * - `GOVERNMENT_GROUP` - Government and public safety facilities (offices, courts, embassies, police, fire stations)\n *\n * @example\n * Filter to show only food-related POIs:\n * ```ts\n * import { poiCategoryGroups } from '@tomtom-international/maps-sdk-js/map';\n *\n * const foodCategories = poiCategoryGroups.FOOD_DRINKS_GROUP;\n * console.log(foodCategories);\n * // ['RESTAURANT', 'FAST_FOOD', 'CAFE_PUB', 'PUB', 'WINERY', ...]\n * ```\n *\n * @example\n * Use with POI module filtering:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'show',\n * values: ['FOOD_DRINKS_GROUP', 'ENTERTAINMENT_GROUP']\n * }\n * });\n * ```\n *\n * @see {@link POICategoryGroup} - Type representing all available group names\n * @see {@link MapStylePOICategory} - Individual POI category identifiers\n *\n * @group POIs\n */\nexport const poiCategoryGroups: Record<string, MapStylePOICategory[]> = {\n FOOD_DRINKS_GROUP: [\n 'RESTAURANT',\n 'FAST_FOOD',\n 'CAFE_PUB',\n 'PUB',\n 'WINERY',\n 'PUB_FOOD',\n 'SOUL_FOOD',\n 'DELICATESSEN',\n ],\n SHOPPING_GROUP: [\n 'SHOP',\n 'SHOPPING_CENTER',\n 'CLOTHING_SHOP',\n 'MARKET',\n 'FOOD_MARKET',\n 'SUPERMARKETS_HYPERMARKETS',\n 'DEPARTMENT_STORE',\n 'CONVENIENCE_STORE',\n 'GROCERY_STORE',\n 'HARDWARE_STORE',\n 'ELECTRICAL_APPLIANCES_SHOP',\n ],\n TRANSPORTATION_GROUP: [\n 'AIRPORT',\n 'FERRY_TERMINAL',\n 'HELIPAD_HELICOPTER_LANDING',\n 'PUBLIC_TRANSPORT_STOP',\n 'RAILWAY_STATION',\n 'BUS_STOP',\n 'TAXI_STAND',\n ],\n HEALTH_GROUP: [\n 'DOCTOR',\n 'EMERGENCY_MEDICAL_SERVICE',\n 'EMERGENCY_ROOM',\n 'HEALTH_CARE_SERVICE',\n 'HOSPITAL',\n 'PHARMACY',\n 'DENTIST',\n 'WELFARE_ORGANIZATION',\n ],\n PARKING_GROUP: ['PARKING_GARAGE', 'OPEN_PARKING_AREA'],\n HOLIDAY_TOURISM_GROUP: [\n 'AMUSEMENT_PARK',\n 'BEACH',\n 'HOLIDAY_RENTAL',\n 'GEOGRAPHIC_FEATURE',\n 'TOURIST_ATTRACTION',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n 'MUSEUM',\n 'SCENIC_PANORAMIC_VIEW',\n 'TOURIST_INFORMATION_OFFICE',\n ],\n EV_CHARGING_STATIONS_GROUP: ['ELECTRIC_VEHICLE_STATION'],\n GAS_STATIONS_GROUP: ['GAS_STATION'],\n ACCOMMODATION_GROUP: ['CAMPING_GROUND', 'HOTEL_MOTEL', 'HOLIDAY_RENTAL'],\n ENTERTAINMENT_GROUP: [\n 'CINEMA',\n 'THEATER',\n 'MOVIE_THEATER',\n 'NIGHTLIFE',\n 'CONCERT_HALL',\n 'ENTERTAINMENT',\n 'CLUB_ASSOCIATION',\n 'CASINO',\n ],\n SPORTS_LEISURE_GROUP: [\n 'SPORTS_CENTER',\n 'WATER_SPORT',\n 'SWIMMING_POOL',\n 'GOLF_COURSE',\n 'STADIUM',\n 'BEACH',\n 'ICE_SKATING_RINK',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n ],\n EDUCATION_GROUP: ['SCHOOL', 'COLLEGE_UNIVERSITY', 'LIBRARY', 'CULTURAL_CENTER'],\n GOVERNMENT_GROUP: ['GOVERNMENT_OFFICE', 'COURTHOUSE', 'EMBASSY', 'FIRE_STATION_BRIGADE', 'POLICE_STATION'],\n};\n\n/**\n * POI category group type.\n *\n * @remarks\n * Represents predefined groups of related POI categories for convenient filtering.\n * Each group contains multiple {@link MapStylePOICategory} values that share a common theme.\n *\n * Using category groups simplifies filtering by allowing you to show or hide\n * multiple related POI types with a single filter value.\n *\n * **Available groups:**\n * - `FOOD_DRINKS_GROUP` - Restaurants, cafes, fast food, wineries, etc.\n * - `SHOPPING_GROUP` - Stores, malls, markets, supermarkets, etc.\n * - `TRANSPORTATION_GROUP` - Airports, train stations, bus stops, ferry terminals, etc.\n * - `HEALTH_GROUP` - Hospitals, clinics, pharmacies, doctors, dentists, etc.\n * - `PARKING_GROUP` - Parking garages and open parking areas\n * - `HOLIDAY_TOURISM_GROUP` - Tourist attractions, museums, beaches, scenic views, etc.\n * - `EV_CHARGING_STATIONS_GROUP` - Electric vehicle charging stations\n * - `GAS_STATIONS_GROUP` - Gas and petrol stations\n * - `ACCOMMODATION_GROUP` - Hotels, motels, camping grounds, etc.\n * - `ENTERTAINMENT_GROUP` - Cinemas, theaters, nightlife, casinos, etc.\n * - `SPORTS_LEISURE_GROUP` - Stadiums, sports centers, swimming pools, golf courses, etc.\n * - `EDUCATION_GROUP` - Schools, universities, libraries, cultural centers\n * - `GOVERNMENT_GROUP` - Government offices, courthouses, embassies, police, fire stations\n *\n * @example\n * Filter to show only food-related POIs:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'show',\n * values: ['FOOD_DRINKS_GROUP']\n * }\n * });\n * ```\n *\n * @example\n * Hide parking and gas stations:\n * ```ts\n * poisModule.configure({\n * categoryFilter: {\n * mode: 'hide',\n * values: ['PARKING_GROUP', 'GAS_STATIONS_GROUP']\n * }\n * });\n * ```\n *\n * @example\n * Combine multiple groups for tourism use case:\n * ```ts\n * const tourismFilter = {\n * mode: 'show',\n * values: [\n * 'HOLIDAY_TOURISM_GROUP',\n * 'ACCOMMODATION_GROUP',\n * 'FOOD_DRINKS_GROUP',\n * 'ENTERTAINMENT_GROUP'\n * ]\n * };\n * ```\n *\n * @see {@link poiCategoryGroups} - The object containing all group definitions\n * @see {@link MapStylePOICategory} - For individual POI categories\n *\n * @group POIs\n */\nexport type POICategoryGroup = keyof typeof poiCategoryGroups;\n","import type { Point } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { POIsModuleFeature } from '../types/poisModuleFeature';\n\n/**\n * @ignore\n */\nexport const poisMapping = (feature: MapGeoJSONFeature): POIsModuleFeature => {\n const { properties } = feature;\n return {\n id: feature.id,\n type: feature.type,\n geometry: feature.geometry as Point,\n properties: {\n id: String(properties?.id),\n name: properties?.name,\n category: properties?.category,\n group: properties?.group,\n priority: properties?.priority,\n },\n };\n};\n","import { POICategory } from '@tomtom-org/maps-sdk/core';\nimport { isNil } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport { toBaseMapPOICategory } from '../places';\nimport type { ValuesFilter } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n ModuleEvents,\n POI_SOURCE_ID,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { buildMappedValuesFilter, getMergedAllFilter } from '../shared/mapLibreFilterUtils';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { poiLayerIDs } from './layers/poisLayers';\nimport { poiCategoryGroups } from './types/poiCategoryGroups';\nimport type { FilterablePOICategory, POIsModuleConfig } from './types/poisModuleConfig';\nimport type { POIsModuleFeature } from './types/poisModuleFeature';\nimport { poisMapping } from './util/poisMapping';\n\n/**\n * Gets the specified filtered categories icon IDs to be used in map filtering.\n * @param categories list of filtered categories.\n * @ignore\n */\nexport const getStyleCategories = (categories: FilterablePOICategory[]): string[] => {\n const categoryIds: string[] = [];\n categories.forEach((category: FilterablePOICategory) => {\n if (category in poiCategoryGroups) {\n categoryIds.push(...poiCategoryGroups[category].map(toBaseMapPOICategory));\n } else {\n categoryIds.push(toBaseMapPOICategory(category as POICategory));\n }\n });\n return [...new Set(categoryIds)];\n};\n\n/**\n * IDs of sources and layers for places of interest module.\n */\ntype PoIsSourcesAndLayers = {\n /**\n * Places of interest with corresponding layer ids.\n * TODO: technically source ID is vectorTiles if POIs stay included in base map for Orbis\n */\n poi: StyleSourceWithLayers;\n};\n\n/**\n * POIs Module for controlling Points of Interest displayed in the map style.\n *\n * This module manages the built-in POI layer from the vector map, allowing you to\n * show/hide POIs and filter them by category. POIs are already part of the map style\n * and include businesses, landmarks, and other points of interest.\n *\n * @remarks\n * **Features:**\n * - Toggle POI visibility on/off\n * - Filter by POI categories or category groups\n * - Event handling for POI interactions\n * - Based on vector tile data in the map style\n *\n * **POI Categories:**\n * - Individual categories (e.g., RESTAURANT, HOTEL_MOTEL, PARKING_GARAGE)\n * - Category groups (e.g., FOOD_DRINKS_GROUP, SHOPPING_GROUP, TRANSPORTATION_GROUP)\n *\n * **Difference from PlacesModule:**\n * - POIsModule: Controls existing POIs in the map style\n * - PlacesModule: Displays custom place data from Search API or other sources\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { POIsModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module\n * const poisModule = await POIsModule.get(map);\n *\n * // Toggle visibility\n * poisModule.setVisible(false);\n * poisModule.setVisible(true);\n * ```\n *\n * @example\n * Filter specific categories:\n * ```typescript\n * // Show only restaurants and hotels\n * poisModule.filterCategories({\n * show: 'only',\n * values: ['RESTAURANT', 'HOTEL_MOTEL']\n * });\n *\n * // Hide parking garages\n * poisModule.filterCategories({\n * show: 'all_except',\n * values: ['PARKING_GARAGE', 'OPEN_PARKING_AREA']\n * });\n * ```\n *\n * @example\n * Filter using category groups:\n * ```typescript\n * // Show only food and shopping POIs\n * poisModule.filterCategories({\n * show: 'only',\n * values: ['FOOD_DRINKS_GROUP', 'SHOPPING_GROUP']\n * });\n *\n * // Hide transportation POIs\n * pois.filterCategories({\n * show: 'all_except',\n * values: ['TRANSPORTATION_GROUP']\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('Clicked POI:', feature.properties.name);\n * console.log('Category:', feature.properties.category);\n * });\n * ```\n *\n * @see [POIs Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/pois)\n *\n * @group POIs\n */\nexport class POIsModule extends AbstractMapModule<PoIsSourcesAndLayers, POIsModuleConfig> {\n private categoriesFilter?: ValuesFilter<FilterablePOICategory> | null;\n private originalFilter?: FilterSpecification;\n\n /**\n * Retrieves a POIsModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional initial configuration for visibility and filters.\n *\n * @returns A promise that resolves to the initialized POIsModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `filters.categories`: Category filter to apply on initialization\n *\n * @throws Error if the POI source is not found in the map style\n *\n * @example\n * Default initialization:\n * ```typescript\n * const poisModule = await POIsModule.get(map);\n * ```\n *\n * @example\n * With initial filter:\n * ```typescript\n * const poisModule = await POIsModule.get(map, {\n * visible: true,\n * filters: {\n * categories: {\n * show: 'only',\n * values: ['RESTAURANT', 'CAFE_PUB']\n * }\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: POIsModuleConfig): Promise<POIsModule> {\n await waitUntilMapIsReady(map);\n return new POIsModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: POIsModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const poiRuntimeSource = this.mapLibreMap.getSource(POI_SOURCE_ID);\n if (!poiRuntimeSource) {\n throw notInTheStyle(`init ${POIsModule.name} with source ID ${POI_SOURCE_ID}`);\n }\n const poi = new StyleSourceWithLayers(this.mapLibreMap, poiRuntimeSource, (layer) =>\n poiLayerIDs.includes(layer.id),\n );\n // TODO: check if the layers are present in the style, and if not, throw exception?\n const mainLayer = poi.sourceAndLayerIDs.layerIDs[0];\n if (this.mapLibreMap.getLayer(mainLayer)) {\n this.originalFilter = this.mapLibreMap.getFilter(mainLayer) as FilterSpecification;\n }\n return { poi };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: POIsModuleConfig | undefined) {\n if (config && !isNil(config.visible)) {\n this._setVisible(config.visible, false);\n } else if (!this._initializing && !this.isVisible()) {\n // applying default:\n this._setVisible(true, false);\n }\n\n this._filterCategories(config?.filters?.categories, false);\n return config;\n }\n\n /**\n * Checks if POI layers are currently visible.\n *\n * @returns `true` if at least one POI layer is visible, `false` if all are hidden.\n *\n * @example\n * ```typescript\n * if (pois.isVisible()) {\n * console.log('POIs are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.poi.isAnyLayerVisible();\n }\n\n /**\n * Sets the visibility of POI layers.\n *\n * @param visible - `true` to show POIs, `false` to hide them.\n *\n * @remarks\n * Changes are applied immediately if the map is ready.\n *\n * @example\n * ```typescript\n * pois.setVisible(false); // Hide all POIs\n * pois.setVisible(true); // Show all POIs\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.poi.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Filters POIs by category or category group.\n *\n * @param categoriesFilter - Filter configuration specifying which categories to show/hide.\n * Pass `undefined` to reset to default (show all).\n *\n * @remarks\n * **Filter Modes:**\n * - `only`: Show only the specified categories, hide all others\n * - `all_except`: Show all categories except the specified ones\n *\n * **Category Types:**\n * - Individual categories (e.g., 'RESTAURANT', 'HOTEL_MOTEL')\n * - Category groups (e.g., 'FOOD_DRINKS_GROUP', 'SHOPPING_GROUP')\n *\n * **Available Category Groups:**\n * - FOOD_DRINKS_GROUP\n * - SHOPPING_GROUP\n * - TRANSPORTATION_GROUP\n * - HEALTH_GROUP\n * - PARKING_GROUP\n * - HOLIDAY_TOURISM_GROUP\n * - EV_CHARGING_STATIONS_GROUP\n * - GAS_STATIONS_GROUP\n * - ACCOMMODATION_GROUP\n * - ENTERTAINMENT_GROUP\n * - SPORTS_LEISURE_GROUP\n * - EDUCATION_GROUP\n * - GOVERNMENT_GROUP\n *\n * @example\n * Show only restaurants:\n * ```typescript\n * pois.filterCategories({\n * show: 'only',\n * values: ['RESTAURANT']\n * });\n * ```\n *\n * @example\n * Hide parking:\n * ```typescript\n * pois.filterCategories({\n * show: 'all_except',\n * values: ['PARKING_GROUP']\n * });\n * ```\n *\n * @example\n * Reset filter (show all):\n * ```typescript\n * pois.filterCategories(undefined);\n * ```\n */\n filterCategories(categoriesFilter?: ValuesFilter<FilterablePOICategory> | undefined): void {\n this._filterCategories(categoriesFilter);\n }\n\n private _filterCategories(\n categoriesFilter: ValuesFilter<FilterablePOICategory> | undefined,\n updateConfig = true,\n ): void {\n if (categoriesFilter) {\n if (this.tomtomMap.mapReady) {\n const poiFilter = buildMappedValuesFilter(\n 'category',\n categoriesFilter.show,\n getStyleCategories(categoriesFilter.values),\n );\n\n this.mapLibreMap.setFilter('POI', getMergedAllFilter(poiFilter, this.originalFilter));\n }\n if (updateConfig) {\n this.config = {\n ...this.config,\n filters: {\n categories: categoriesFilter,\n },\n };\n }\n } else if (this.categoriesFilter) {\n if (updateConfig) {\n // reset categories config to default\n this.config = {\n ...this.config,\n filters: {\n categories: {\n show: 'all_except',\n values: [],\n },\n },\n };\n }\n if (this.tomtomMap.mapReady) {\n // Applies default:\n this.mapLibreMap.setFilter('POI', this.originalFilter);\n }\n }\n\n this.categoriesFilter = categoriesFilter;\n if (updateConfig) {\n this.emitConfigChange();\n }\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible POI features with properly typed properties.\n *\n * @example\n * ```typescript\n * const shown = poisModule.getShown();\n * console.log(`Visible POIs: ${shown.poi.length}`);\n * shown.poi.forEach(poi => {\n * console.log(poi.properties.name, poi.properties.category);\n * });\n * ```\n */\n getShown(): { poi: POIsModuleFeature[] } {\n return {\n poi: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.poi.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(poisMapping),\n };\n }\n\n /**\n * Gets the events interface for handling user interactions with POIs.\n *\n * @returns A `UserEvents` instance for registering event handlers.\n *\n * @remarks\n * **Supported Events:**\n * - `click`: User clicks on a POI\n * - `contextmenu`: User right-clicks on a POI\n * - `hover`: Mouse enters a POI\n * - `long-hover`: Mouse hovers over POI for extended time\n *\n * **Event Feature Properties:**\n * - `id`: Unique POI identifier\n * - `name`: POI name in native language\n * - `category`: POI category\n * - `iconID`: Icon sprite ID\n * - `group`: Category group\n * - `priority`: Importance level (lower = more important)\n *\n * @example\n * ```typescript\n * pois.events.on('click', (feature, lngLat) => {\n * console.log('POI:', feature.properties.name);\n * console.log('Category:', feature.properties.category);\n * console.log('ID:', feature.properties.id);\n * });\n *\n * pois.events.on('hover', (feature) => {\n * showTooltip(feature.properties.name);\n * });\n * ```\n */\n get events(): CombinedEvents<POIsModuleFeature, POIsModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents<POIsModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.poi,\n this.config?.events,\n poisMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { LayerSpecification } from 'maplibre-gl';\nimport { poiLayerIDs } from '../pois';\nimport type { LayerSpecFilter } from '../shared';\nimport type { BaseMapLayerGroupName, BaseMapLayerGroups } from './types/baseMapModuleConfig';\n\ntype LayerGroupMapping = {\n layerIDMatches: string[];\n layerTypes: LayerSpecification['type'][];\n};\n\nconst layerGroupMappings: Record<BaseMapLayerGroupName, LayerGroupMapping> = {\n land: {\n layerIDMatches: ['lulc'],\n layerTypes: ['fill'],\n },\n borders: {\n layerIDMatches: ['borders'],\n layerTypes: ['line'],\n },\n water: {\n layerIDMatches: ['water'],\n layerTypes: ['fill', 'line'],\n },\n buildings2D: {\n layerIDMatches: ['building'],\n layerTypes: ['fill', 'line'],\n },\n buildings3D: {\n layerIDMatches: ['building'],\n layerTypes: ['fill-extrusion'],\n },\n houseNumbers: {\n layerIDMatches: ['house number'],\n layerTypes: ['symbol'],\n },\n roadLines: {\n layerIDMatches: ['road', 'tunnel', 'bridge', 'surface'],\n layerTypes: ['fill', 'line'],\n },\n roadLabels: {\n layerIDMatches: ['road', 'tunnel', 'bridge', 'surface'],\n layerTypes: ['symbol'],\n },\n roadShields: {\n layerIDMatches: ['shield'],\n layerTypes: ['symbol'],\n },\n placeLabels: {\n layerIDMatches: ['places'],\n layerTypes: ['symbol'],\n },\n smallerTownLabels: {\n layerIDMatches: ['town', 'village', 'neighbourhood'],\n layerTypes: ['symbol'],\n },\n cityLabels: {\n layerIDMatches: ['city', 'capital'],\n layerTypes: ['symbol'],\n },\n capitalLabels: {\n layerIDMatches: ['capital'],\n layerTypes: ['symbol'],\n },\n stateLabels: {\n layerIDMatches: ['state'],\n layerTypes: ['symbol'],\n },\n countryLabels: {\n layerIDMatches: ['places - country'],\n layerTypes: ['symbol'],\n },\n};\n\nconst isMatching = (group: BaseMapLayerGroupName, layer: LayerSpecification) => {\n const mapping = layerGroupMappings[group];\n return (\n mapping.layerIDMatches.some((part) => layer.id.toLowerCase().includes(part)) &&\n mapping.layerTypes.includes(layer.type)\n );\n};\n\n/**\n * @ignore\n */\nexport const buildLayerGroupFilter = (layerGroups: BaseMapLayerGroups): LayerSpecFilter => {\n const mode = layerGroups.mode;\n const groups = layerGroups.names;\n if (mode === 'include') {\n return (layer) => groups.some((group) => isMatching(group, layer));\n }\n if (mode === 'exclude') {\n return (layer) => !groups.some((group) => isMatching(group, layer));\n }\n // No filtering if we don't recognize the mode:\n console.error('Unrecognized layer group mode:', mode);\n return () => true;\n};\n\n/**\n * @ignore\n */\nexport const filterLayerByGroups = (layer: LayerSpecification, layerGroups?: BaseMapLayerGroups): boolean => {\n const mode = layerGroups?.mode;\n const groups = layerGroups?.names;\n if (mode && groups?.length) {\n if (mode === 'include') {\n return groups.some((group) => isMatching(group, layer));\n }\n if (mode === 'exclude') {\n return !groups.some((group) => isMatching(group, layer));\n }\n }\n return true;\n};\n\n/**\n * @ignore\n */\nexport const buildBaseMapLayerGroupFilter =\n (layerGroupsFilter?: BaseMapLayerGroups): LayerSpecFilter =>\n (layer: LayerSpecification): boolean =>\n (!layerGroupsFilter || filterLayerByGroups(layer, layerGroupsFilter)) && !poiLayerIDs.includes(layer.id);\n","import { isNil } from 'lodash-es';\n\nimport {\n AbstractMapModule,\n BASE_MAP_SOURCE_ID,\n CombinedEvents,\n ModuleEvents,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport { TomTomMap } from '../TomTomMap';\nimport { buildBaseMapLayerGroupFilter, buildLayerGroupFilter } from './layerGroups';\nimport type { BaseMapLayerGroups, BaseMapModuleConfig, BaseMapModuleInitConfig } from './types/baseMapModuleConfig';\n\ntype BaseSourceAndLayers = {\n vectorTiles: StyleSourceWithLayers;\n};\n\n/**\n * Base Map Module for controlling standard map layers and their visibility.\n *\n * This module manages the fundamental map layers including background, water, land, roads,\n * buildings, labels, and other vector tile layers from the base map style.\n *\n * @remarks\n * **Managed Layers:**\n * - Background and terrain\n * - Water bodies and coastlines\n * - Country and administrative borders\n * - Buildings (2D and 3D)\n * - Road lines, labels, and shields\n * - Place labels at various zoom levels\n * - House numbers\n *\n * **Does NOT Include:**\n * - Traffic flow/incidents (use {@link TrafficFlowModule} or {@link TrafficIncidentsModule})\n * - Points of Interest/POIs (use {@link POIsModule})\n * - Hillshade/terrain shading (use {@link HillshadeModule})\n *\n * **Use Cases:**\n * - Toggle base map visibility on/off\n * - Show only specific layer groups (e.g., roads only)\n * - Create custom map appearances by hiding certain elements\n * - Build overlay maps with selective base layers\n *\n * @example\n * Basic usage:\n * ```typescript\n * // Get module with default configuration\n * const baseMap = await BaseMapModule.get(map);\n *\n * // Toggle visibility\n * baseMap.setVisible(false); // Hide all base layers\n * baseMap.setVisible(true); // Show all base layers\n *\n * // Check current state\n * if (baseMap.isVisible()) {\n * console.log('Base map is visible');\n * }\n * ```\n *\n * @example\n * Working with layer groups:\n * ```typescript\n * // Show only roads and borders\n * const baseMap = await BaseMapModule.get(map, {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'borders']\n * }\n * });\n *\n * // Hide buildings and labels\n * baseMap.setVisible(false, {\n * layerGroups: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D', 'placeLabels']\n * }\n * });\n *\n * // Show only water and land\n * baseMap.setVisible(true, {\n * layerGroups: {\n * mode: 'include',\n * names: ['water', 'land']\n * }\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map);\n *\n * // Listen for clicks on base map features\n * baseMap.events.on('click', (feature, lngLat) => {\n * console.log('Clicked base map feature:', feature);\n * });\n *\n * // Remove event listeners\n * baseMap.events.off('click');\n * ```\n *\n * @see [Base Map Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/base-map)\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n *\n * @group Base Map\n */\nexport class BaseMapModule extends AbstractMapModule<BaseSourceAndLayers, BaseMapModuleConfig> {\n /**\n * Asynchronously retrieves a BaseMapModule instance for the given map.\n *\n * This is the recommended way to create a BaseMapModule. It ensures the map\n * is fully loaded before initializing the module.\n *\n * @param tomtomMap - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for module initialization.\n *\n * @returns A promise that resolves to the initialized BaseMapModule.\n *\n * @remarks\n * **Initialization:**\n * - Waits for map to be ready before creating module\n * - Validates that required sources exist in the map style\n * - Applies initial configuration if provided\n *\n * **Configuration Options:**\n * - `visible`: Initial visibility state\n * - `layerGroupsFilter`: Which layer groups to include/exclude\n * - `layerGroupsVisibility`: Fine-grained visibility per group\n *\n * @throws Error if the base map source is not found in the style\n *\n * @example\n * Default initialization:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map);\n * ```\n *\n * @example\n * With configuration:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map, {\n * visible: true,\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings3D', 'houseNumbers']\n * }\n * });\n * ```\n *\n * @example\n * Show only specific groups:\n * ```typescript\n * const baseMap = await BaseMapModule.get(map, {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land', 'borders']\n * },\n * visible: true\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: BaseMapModuleInitConfig): Promise<BaseMapModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new BaseMapModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: BaseMapModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config: BaseMapModuleInitConfig | undefined) {\n const source = this.mapLibreMap.getSource(BASE_MAP_SOURCE_ID);\n if (!source) {\n throw notInTheStyle(`init ${BaseMapModule.name} with source ID ${BASE_MAP_SOURCE_ID}`);\n }\n\n return {\n vectorTiles: new StyleSourceWithLayers(\n this.mapLibreMap,\n source,\n buildBaseMapLayerGroupFilter(config?.layerGroupsFilter),\n ),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: BaseMapModuleConfig | undefined) {\n if (config && !isNil(config.visible)) {\n this._setVisible(config.visible, undefined, false);\n } else if (!this._initializing && !this.isVisible()) {\n // applying default:\n this._setVisible(true, undefined, false);\n }\n\n if (config?.layerGroupsVisibility) {\n this._setVisible(\n config.layerGroupsVisibility.visible,\n { layerGroups: config.layerGroupsVisibility },\n false,\n );\n }\n\n // We merge the given config with the previous one to ensure init config parameters are kept:\n // (the init config can have more parameters than the runtime one)\n return this.config || config ? { ...this.config, ...config } : undefined;\n }\n\n /**\n * Checks if any base map layers are currently visible.\n *\n * @returns `true` if at least one base map layer is visible, `false` if all are hidden.\n *\n * @remarks\n * This checks the actual visibility state of layers in the map, not just the\n * module's configuration setting.\n *\n * @example\n * ```typescript\n * if (baseMap.isVisible()) {\n * console.log('Base map is rendered');\n * } else {\n * console.log('Base map is hidden');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.vectorTiles.isAnyLayerVisible();\n }\n\n /**\n * Sets the visibility of base map layers.\n *\n * @param visible - `true` to show layers, `false` to hide them.\n * @param options - Optional settings for fine-grained control.\n * @param options.layerGroups - Target specific layer groups instead of all layers.\n *\n * @remarks\n * **Behavior:**\n * - Without `options.layerGroups`: Affects all base map layers\n * - With `options.layerGroups`: Affects only specified layer groups\n * - Changes are applied immediately if map is ready\n *\n * **Layer Groups:**\n * Available groups: `land`, `water`, `borders`, `buildings2D`, `buildings3D`,\n * `houseNumbers`, `roadLines`, `roadLabels`, `roadShields`, `placeLabels`,\n * `smallerTownLabels`, `cityLabels`, `capitalLabels`, `stateLabels`, `countryLabels`\n *\n * @example\n * Show/hide all layers:\n * ```typescript\n * baseMap.setVisible(false); // Hide everything\n * baseMap.setVisible(true); // Show everything\n * ```\n *\n * @example\n * Control specific groups:\n * ```typescript\n * // Hide only buildings\n * baseMap.setVisible(false, {\n * layerGroups: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D']\n * }\n * });\n *\n * // Show everything except labels\n * baseMap.setVisible(true, {\n * layerGroups: {\n * mode: 'exclude',\n * names: ['placeLabels', 'cityLabels', 'countryLabels']\n * }\n * });\n * ```\n *\n * @example\n * Toggle visibility:\n * ```typescript\n * const isVisible = baseMap.isVisible();\n * baseMap.setVisible(!isVisible); // Toggle\n * ```\n */\n setVisible(visible: boolean, options?: { layerGroups?: BaseMapLayerGroups }): void {\n this._setVisible(visible, options);\n }\n\n private _setVisible(visible: boolean, options?: { layerGroups?: BaseMapLayerGroups }, updateConfig = true): void {\n if (updateConfig) {\n if (!options?.layerGroups) {\n // We remove the layer groups visibility from the config if it was there:\n delete this.config?.layerGroupsVisibility;\n this.config = { ...this.config, visible };\n } else {\n this.config = { ...this.config, layerGroupsVisibility: { ...options.layerGroups, visible } };\n }\n }\n\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.vectorTiles.setLayersVisible(\n visible,\n options?.layerGroups && buildLayerGroupFilter(options.layerGroups),\n );\n }\n\n if (updateConfig) {\n this.emitConfigChange();\n }\n }\n\n /**\n * Gets the unified events interface for this module, covering both user interactions\n * and module lifecycle events.\n *\n * **User interaction events** (`click`, `contextmenu`, `hover`, `long-hover`):\n * ```typescript\n * const unsub = baseMap.events.on('click', (feature, lngLat) => {\n * console.log('Clicked on:', feature.properties);\n * });\n * baseMap.events.off('hover'); // remove by type\n * ```\n *\n * **Module lifecycle events** (`config-change`):\n * ```typescript\n * const unsub = baseMap.events.on('config-change', (config) => {\n * console.log('Config changed:', config);\n * });\n * unsub(); // remove by returned function\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, BaseMapModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.vectorTiles, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { MapModuleCommonConfig } from '../../shared';\n\n/**\n * Available base map layer group identifiers.\n *\n * @remarks\n * Use these names with {@link BaseMapModule} to control layer visibility.\n *\n * @see {@link BaseMapLayerGroupName}\n * @see {@link BaseMapModuleInitConfig.layerGroupsFilter}\n *\n * @group Base Map\n */\nexport const baseMapLayerGroupNames = [\n 'land',\n 'water',\n 'borders',\n 'buildings2D',\n 'buildings3D',\n 'houseNumbers',\n 'roadLines',\n 'roadLabels',\n 'roadShields',\n 'placeLabels',\n 'smallerTownLabels',\n 'cityLabels',\n 'capitalLabels',\n 'stateLabels',\n 'countryLabels',\n] as const;\n\n/**\n * Name of a base map layer group.\n *\n * Identifies specific categories of base map layers that can be controlled together.\n *\n * @remarks\n * **Available Layer Groups:**\n * - `land` - Land areas and terrain\n * - `water` - Water bodies (oceans, lakes, rivers)\n * - `borders` - Country and administrative boundaries\n * - `buildings2D` - 2D building footprints\n * - `buildings3D` - 3D building models\n * - `houseNumbers` - House number labels\n * - `roadLines` - Road line geometries\n * - `roadLabels` - Street name labels\n * - `roadShields` - Highway shields (e.g., I-95, A1)\n * - `placeLabels` - General place labels\n * - `smallerTownLabels` - Small town/village labels\n * - `cityLabels` - City labels\n * - `capitalLabels` - Capital city labels\n * - `stateLabels` - State/province labels\n * - `countryLabels` - Country name labels\n *\n * @example\n * ```typescript\n * const group: BaseMapLayerGroupName = 'roadLines';\n * const labels: BaseMapLayerGroupName[] = ['cityLabels', 'countryLabels'];\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroupName = (typeof baseMapLayerGroupNames)[number];\n\n/**\n * Layer group visibility configuration with explicit visible state.\n *\n * Extends {@link BaseMapLayerGroups} to include a visibility flag, allowing\n * you to show or hide specific groups of base map layers.\n *\n * @example\n * ```typescript\n * // Hide all buildings\n * const config: BaseMapLayerGroupsVisibility = {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * };\n *\n * // Show only roads\n * const roadsOnly: BaseMapLayerGroupsVisibility = {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels'],\n * visible: true\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroupsVisibility = BaseMapLayerGroups & { visible: boolean };\n\n/**\n * Layer group filter for selective base map display.\n *\n * Defines which layer groups to include or exclude from the base map module.\n * Can be expressed as explicit inclusions (show only these) or exclusions\n * (show all except these).\n *\n * @remarks\n * **Filter Modes:**\n * - `include`: Only the specified groups are shown, all others are hidden\n * - `exclude`: All groups are shown except the specified ones\n *\n * **Common Use Cases:**\n * - Show only roads and labels (minimal map)\n * - Hide buildings for cleaner appearance\n * - Show only water and land (base terrain)\n * - Remove labels for overlay maps\n *\n * @example\n * ```typescript\n * // Show only roads and borders\n * const roadsOnly: BaseMapLayerGroups = {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'borders']\n * };\n *\n * // Show everything except buildings\n * const noBuildings: BaseMapLayerGroups = {\n * mode: 'exclude',\n * names: ['buildings2D', 'buildings3D']\n * };\n *\n * // Show only terrain (no labels, no roads)\n * const terrainOnly: BaseMapLayerGroups = {\n * mode: 'include',\n * names: ['land', 'water']\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroups = {\n /**\n * Filter mode determining whether groups are included or excluded.\n *\n * @remarks\n * - `include`: Only the specified groups are considered, all others are ignored\n * - `exclude`: All base map groups except the specified ones are considered\n *\n * @example\n * ```typescript\n * mode: 'include' // Whitelist approach\n * mode: 'exclude' // Blacklist approach\n * ```\n */\n mode: 'include' | 'exclude';\n\n /**\n * Names of the layer groups to include or exclude.\n *\n * @remarks\n * The meaning depends on the `mode`:\n * - In `include` mode: Only these groups will be shown\n * - In `exclude` mode: These groups will be hidden, all others shown\n *\n * @example\n * ```typescript\n * // Show only these\n * names: ['roadLines', 'roadLabels', 'water']\n *\n * // Hide these\n * names: ['buildings2D', 'buildings3D', 'houseNumbers']\n * ```\n */\n names: BaseMapLayerGroupName[];\n};\n\n/**\n * Configuration for the BaseMapModule (initialization or runtime).\n *\n * Controls visibility and behavior of base map layer groups. Can be used both\n * during module initialization and for runtime updates.\n *\n * @remarks\n * This configuration allows fine-grained control over which base map elements\n * are displayed, enabling you to create custom map appearances for different\n * use cases.\n *\n * @example\n * ```typescript\n * // Hide specific layer groups\n * const config: BaseMapModuleConfig = {\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * }\n * };\n *\n * // Show only certain groups\n * const minimalConfig: BaseMapModuleConfig = {\n * visible: true,\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['roadLines', 'water', 'land'],\n * visible: true\n * }\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapModuleConfig = MapModuleCommonConfig & {\n /**\n * Controls the visibility of all layers associated with this module.\n *\n * @default true\n */\n visible?: boolean;\n\n /**\n * Optional visibility configuration for specific layer groups.\n *\n * @remarks\n * **Important:** The layer groups specified here must be included in the\n * module (not excluded by `layerGroupsFilter` during initialization).\n *\n * Use this to control visibility of layer groups at runtime without\n * reinitializing the module.\n *\n * @example\n * ```typescript\n * // Hide all building layers\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['buildings2D', 'buildings3D'],\n * visible: false\n * }\n *\n * // Show only labels\n * layerGroupsVisibility: {\n * mode: 'include',\n * names: ['placeLabels', 'cityLabels', 'countryLabels'],\n * visible: true\n * }\n * ```\n */\n layerGroupsVisibility?: BaseMapLayerGroupsVisibility;\n};\n\n/**\n * Initialization configuration for the BaseMapModule.\n *\n * Extends {@link BaseMapModuleConfig} with additional options available only\n * during module initialization.\n *\n * @remarks\n * **Initialization vs Runtime:**\n * - `layerGroupsFilter`: Only available at init - determines which groups to load\n * - `layerGroupsVisibility`: Available at init and runtime - controls visibility\n *\n * **Use Cases:**\n * - Create minimal maps with only essential layers\n * - Build custom map styles by excluding certain features\n * - Optimize performance by not loading unnecessary layers\n *\n * @example\n * ```typescript\n * // Initialize with only roads and water\n * const initConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['roadLines', 'roadLabels', 'water', 'land']\n * },\n * visible: true\n * };\n *\n * // Exclude buildings from initialization\n * const noBuildingsConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings2D', 'buildings3D', 'houseNumbers']\n * }\n * };\n *\n * // Minimal map for data overlay\n * const overlayBaseConfig: BaseMapModuleInitConfig = {\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land', 'borders']\n * },\n * visible: true\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapModuleInitConfig = BaseMapModuleConfig & {\n /**\n * Layer groups to include/exclude during module initialization.\n *\n * @remarks\n * **One-time configuration:** This can only be set during initialization.\n * Once the module is created, you cannot change which groups are loaded,\n * only their visibility via `layerGroupsVisibility`.\n *\n * @example\n * ```typescript\n * // Load only essential layers\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['land', 'water', 'roadLines', 'borders']\n * }\n *\n * // Load everything except 3D buildings\n * layerGroupsFilter: {\n * mode: 'exclude',\n * names: ['buildings3D']\n * }\n *\n * // Minimal overlay map\n * layerGroupsFilter: {\n * mode: 'include',\n * names: ['water', 'land']\n * }\n * ```\n */\n layerGroupsFilter?: BaseMapLayerGroups;\n};\n","import type { FeatureCollection } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { ToBeAddedLayerSpec, ToBeAddedLayerSpecWithoutSource } from '../shared';\nimport { AbstractMapModule, CombinedEvents, GeoJSONSourceWithLayers, ModuleEvents, UserEvents } from '../shared';\nimport { addLayers, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { CustomGeoJSONModuleConfig } from './types/customGeoJSONModuleConfig';\n\ntype CustomGeoJSONSourcesWithLayers<TSources extends Record<string, FeatureCollection>> = {\n [K in keyof TSources]: GeoJSONSourceWithLayers<TSources[K]>;\n};\n\n/**\n * Per-source {@link CombinedEvents} surface keyed by the source name supplied in\n * {@link CustomGeoJSONModuleConfig.sources}.\n *\n * @group Custom\n */\nexport type CustomGeoJSONEvents<TSources extends Record<string, FeatureCollection>> = {\n [K in keyof TSources]: CombinedEvents<MapGeoJSONFeature, CustomGeoJSONModuleConfig<TSources>, TSources[K]>;\n};\n\n/**\n * Module for displaying customer-owned GeoJSON data with TomTom SDK support.\n *\n * Wraps one or more MapLibre GeoJSON sources together with caller-supplied layer specs,\n * and adds the usual TomTom SDK lifecycle behaviour on top: style-change restoration,\n * `config-change` / `shown-features` events, and per-source user interaction events\n * (`click`, `hover`, `long-hover`, `contextmenu`).\n *\n * Unlike opinionated modules such as {@link PlacesModule} or {@link GeometriesModule},\n * this module makes no assumptions about how data is rendered — the caller passes raw\n * MapLibre layer specs (`circle`, `heatmap`, `fill`, `line`, `symbol`, …).\n *\n * @typeParam TSources - A record mapping source names to the {@link FeatureCollection}\n * type each source carries. Pass this generic when you want type-safe `show` /\n * `getShown` per source.\n *\n * @example\n * Single source, default generic:\n * ```typescript\n * const module = await CustomGeoJSONModule.get(map, {\n * sources: {\n * points: {\n * layers: [{ type: 'circle', paint: { 'circle-radius': 4, 'circle-color': '#0a3653' } }],\n * },\n * },\n * });\n *\n * await module.show(myFeatureCollection, 'points');\n * module.events.points.on('click', (feature, lngLat) => console.log(feature, lngLat));\n * ```\n *\n * @example\n * Multiple sources with typed payloads:\n * ```typescript\n * type Sources = {\n * heatmap: FeatureCollection<Point>;\n * buildings: FeatureCollection<Polygon, { name: string }>;\n * };\n *\n * const module = await CustomGeoJSONModule.get<Sources>(map, {\n * sources: {\n * heatmap: { layers: [{ type: 'heatmap', paint: { 'heatmap-radius': 12 } }] },\n * buildings: { layers: [{ type: 'fill', paint: { 'fill-color': '#5a5' } }] },\n * },\n * });\n *\n * await module.show(heatmapData, 'heatmap');\n * await module.show(buildingData, 'buildings');\n * ```\n *\n * @example\n * Same data, multiple renderings — call `show` once without a source name:\n * ```typescript\n * type Buildings = FeatureCollection<Point, { Name: string }>;\n *\n * const module = await CustomGeoJSONModule.get<{ heatmap: Buildings; markers: Buildings }>(map, {\n * sources: {\n * heatmap: { layers: heatmapLayers },\n * markers: { layers: markerLayers },\n * },\n * });\n *\n * await module.show(buildingData); // applied to both sources\n * ```\n *\n * @group Custom\n */\nexport class CustomGeoJSONModule<\n TSources extends Record<string, FeatureCollection> = Record<string, FeatureCollection>,\n> extends AbstractMapModule<CustomGeoJSONSourcesWithLayers<TSources>, CustomGeoJSONModuleConfig<TSources>> {\n // Resolved (post-auto-gen) IDs and layer specs, indexed by source name.\n // Stable across style changes because they are derived deterministically from instanceIndex + config.\n private resolvedSourceIDs!: { [K in keyof TSources]: string };\n private resolvedLayerSpecs!: { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] };\n\n // Cached last-shown data per source so style-change restore can replay show() calls.\n // Lazy-initialised because the base-class constructor invokes _initSourcesWithLayers\n // before subclass field initialisers run.\n private lastShown!: Partial<{ [K in keyof TSources]: TSources[K] }>;\n\n // Per-source shown-features handler arrays. References are kept stable so that handlers\n // registered through one events getter call survive subsequent getter calls.\n // Lazy-initialised for the same reason as lastShown.\n private shownFeaturesHandlersBySource!: Map<keyof TSources, ((data: TSources[keyof TSources]) => void)[]>;\n\n /**\n * Creates a new {@link CustomGeoJSONModule} once the map is ready.\n *\n * @param tomtomMap The TomTomMap instance.\n * @param config Module configuration. `sources` is required and must contain at\n * least one entry; each entry must have at least one layer.\n */\n static async get<TSources extends Record<string, FeatureCollection> = Record<string, FeatureCollection>>(\n tomtomMap: TomTomMap,\n config: CustomGeoJSONModuleConfig<TSources>,\n ): Promise<CustomGeoJSONModule<TSources>> {\n await waitUntilMapIsReady(tomtomMap);\n validateConfig(config);\n return new CustomGeoJSONModule<TSources>(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config: CustomGeoJSONModuleConfig<TSources>) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(\n config: CustomGeoJSONModuleConfig<TSources> | undefined,\n restore?: boolean,\n ): CustomGeoJSONSourcesWithLayers<TSources> {\n // config is required by the public API; AbstractMapModule's signature allows undefined.\n const effectiveConfig = config ?? (this.config as CustomGeoJSONModuleConfig<TSources>);\n if (!restore) {\n this.lastShown = {};\n this.shownFeaturesHandlersBySource = new Map();\n }\n this.resolveIDs(effectiveConfig);\n // Images must be registered BEFORE the GeoJSONSourceWithLayers instances are\n // created, so symbol layers referencing them via `icon-image: '<id>'` never\n // render against a missing image. This also covers the style-change restore\n // path because `_initSourcesWithLayers` is called both on init and on restore.\n this.registerImages(effectiveConfig);\n\n const sources = {} as CustomGeoJSONSourcesWithLayers<TSources>;\n for (const sourceName of Object.keys(effectiveConfig.sources) as Array<keyof TSources>) {\n const sourceID = this.resolvedSourceIDs[sourceName];\n const layerSpecs = this.resolvedLayerSpecs[sourceName];\n const cluster = effectiveConfig.sources[sourceName].cluster;\n sources[sourceName] = new GeoJSONSourceWithLayers<TSources[typeof sourceName]>(\n this.mapLibreMap,\n sourceID,\n layerSpecs,\n true,\n cluster,\n );\n\n if (!this.shownFeaturesHandlersBySource.has(sourceName)) {\n this.shownFeaturesHandlersBySource.set(sourceName, []);\n }\n }\n return sources;\n }\n\n private registerImages(config: CustomGeoJSONModuleConfig<TSources>): void {\n if (!config.images) return;\n\n for (const [imageID, spec] of Object.entries(config.images)) {\n if (this.mapLibreMap.hasImage(imageID)) continue;\n this.mapLibreMap.addImage(imageID, spec.image, spec.options);\n }\n }\n\n private resolveIDs(config: CustomGeoJSONModuleConfig<TSources>): void {\n const sourceIDs = {} as { [K in keyof TSources]: string };\n const layerSpecs = {} as { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] };\n for (const sourceName of Object.keys(config.sources) as Array<keyof TSources>) {\n const sourceSpec = config.sources[sourceName];\n const resolvedSourceID =\n sourceSpec.sourceID ?? `custom-geojson-${this.instanceIndex}-${String(sourceName)}`;\n sourceIDs[sourceName] = resolvedSourceID;\n layerSpecs[sourceName] = sourceSpec.layers.map(\n (layer, layerIndex) =>\n ({\n ...layer,\n id: layer.id ?? `${resolvedSourceID}-layer-${layerIndex}`,\n }) as ToBeAddedLayerSpecWithoutSource,\n );\n }\n this.resolvedSourceIDs = sourceIDs;\n this.resolvedLayerSpecs = layerSpecs;\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(\n config: CustomGeoJSONModuleConfig<TSources> | undefined,\n ): CustomGeoJSONModuleConfig<TSources> | undefined {\n if (!config) {\n // Resetting the config to undefined is not supported — the module requires\n // a sources structure to remain operational. Keep the existing config.\n return this.config;\n }\n\n const previousConfig = this.config;\n const previousLayerSpecs = previousConfig ? this.resolvedLayerSpecs : undefined;\n this.resolveIDs(config);\n\n // Register any new images before layers are diffed in, so symbol layers added\n // by this applyConfig never render against a missing image. Additive only:\n // images already on the map are skipped; removing an entry from config.images\n // does not removeImage it (other layers or future show() data may still need it).\n this.registerImages(config);\n\n // On the very first apply (called by the base-class constructor right after\n // _initSourcesWithLayers), there's no previous layer set to diff against — the\n // layers were just added by _initSourcesWithLayers.\n if (previousConfig && previousLayerSpecs) {\n this.updateLayersForExistingSources(previousLayerSpecs);\n }\n\n if (config.visible !== undefined) {\n this.applyVisibility(config.visible);\n }\n\n return config;\n }\n\n private updateLayersForExistingSources(\n previousLayerSpecs: { [K in keyof TSources]: ToBeAddedLayerSpecWithoutSource[] },\n ): void {\n for (const sourceName of Object.keys(this.resolvedLayerSpecs) as Array<keyof TSources>) {\n const previous = previousLayerSpecs[sourceName];\n const next = this.resolvedLayerSpecs[sourceName];\n const sourceWithLayers = this.sourcesWithLayers[sourceName];\n if (!previous || !sourceWithLayers) continue;\n\n updateLayersAndSource(next, previous, sourceWithLayers, this.mapLibreMap);\n\n const layersToAdd: ToBeAddedLayerSpec[] = [];\n for (const layerSpec of sourceWithLayers._layerSpecs) {\n if (!this.mapLibreMap.getLayer(layerSpec.id)) {\n layersToAdd.push(layerSpec);\n }\n }\n if (layersToAdd.length > 0) {\n addLayers(layersToAdd, this.mapLibreMap);\n // addLayers always adds with visibility 'none'. If the source already has\n // features shown, reveal the new layers to keep them in sync.\n if (sourceWithLayers.shownFeatures.features.length > 0) {\n sourceWithLayers.setLayersVisible(true);\n }\n }\n }\n }\n\n private applyVisibility(visible: boolean): void {\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.sourcesWithLayers[sourceName].setLayersVisible(visible);\n }\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n const sourceNames = Object.keys(this.lastShown) as Array<keyof TSources>;\n this.initSourcesWithLayers(this.config, true);\n if (this.config) this._applyConfig(this.config);\n // Call showOnSource (sync) directly rather than show() (async) — the module is\n // already ready at this point so the waitUntilModuleReady() wrapper is a no-op,\n // and using the sync path keeps restore fully synchronous and avoids leaking\n // unhandled rejections if a shown-features handler throws.\n for (const sourceName of sourceNames) {\n const data = this.lastShown[sourceName];\n if (data) this.showOnSource(sourceName, data);\n }\n }\n\n /**\n * Displays the given GeoJSON data on the named source — or on every source when\n * no name is supplied.\n *\n * Replaces any previously shown data. Layer visibility is set automatically based\n * on whether the feature collection is non-empty.\n *\n * @param data The GeoJSON feature collection to display.\n * @param sourceName The source name (must match a key from\n * {@link CustomGeoJSONModuleConfig.sources}). When omitted, `data` is shown on\n * every source — best used when every source shares the same `FeatureCollection`\n * shape.\n */\n async show<K extends keyof TSources>(data: TSources[K], sourceName?: K): Promise<void> {\n await this.waitUntilModuleReady();\n if (sourceName === undefined) {\n for (const name of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.showOnSource(name, data as TSources[typeof name]);\n }\n return;\n }\n this.showOnSource(sourceName, data);\n }\n\n private showOnSource<K extends keyof TSources>(sourceName: K, data: TSources[K]): void {\n const normalized = ensureFeatureIDs(data);\n this.lastShown[sourceName] = normalized;\n this.sourcesWithLayers[sourceName].show(normalized);\n const handlers = this.shownFeaturesHandlersBySource.get(sourceName);\n if (handlers) {\n for (const handler of handlers) {\n handler(normalized);\n }\n }\n }\n\n /**\n * Clears data from the given source — or from all sources when no name is supplied.\n */\n async clear(sourceName?: keyof TSources): Promise<void> {\n await this.waitUntilModuleReady();\n if (sourceName !== undefined) {\n this.sourcesWithLayers[sourceName].clear();\n delete this.lastShown[sourceName];\n return;\n }\n for (const name of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.sourcesWithLayers[name].clear();\n delete this.lastShown[name];\n }\n }\n\n /**\n * Returns the feature collection currently shown on each source.\n *\n * Sources that have never been shown (or have been cleared) return the empty\n * feature collection that `GeoJSONSourceWithLayers` keeps as initial state.\n */\n getShown(): { [K in keyof TSources]: TSources[K] } {\n const result = {} as { [K in keyof TSources]: TSources[K] };\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n result[sourceName] = this.sourcesWithLayers[sourceName].shownFeatures;\n }\n return result;\n }\n\n /**\n * Toggles visibility of every layer across every source.\n *\n * @remarks\n * Calling {@link show} on a source after `setVisible(false)` will re-reveal that\n * source's layers via `GeoJSONSourceWithLayers`' automatic visibility behaviour.\n * To keep a source hidden, call `setVisible(false)` after the next `show`.\n */\n setVisible(visible: boolean): void {\n this.config = { ...(this.config as CustomGeoJSONModuleConfig<TSources>), visible };\n this.applyVisibility(visible);\n this.emitConfigChange();\n }\n\n /**\n * Per-source events surface.\n *\n * Each key on the returned object is one of the source names declared in\n * {@link CustomGeoJSONModuleConfig.sources}. Each value is a {@link CombinedEvents}\n * instance covering both user interactions (`click`, `hover`, `long-hover`,\n * `contextmenu`) and module lifecycle events (`config-change`, `shown-features`).\n *\n * `config-change` handlers are module-wide (the same handler array is shared\n * across every source's `CombinedEvents`). `shown-features` handlers fire only for\n * their own source.\n *\n * @example\n * ```typescript\n * module.events.buildings.on('click', (feature, lngLat) => { ... });\n * module.events.heatmap.on('shown-features', (data) => { ... });\n * module.events.buildings.on('config-change', (config) => { ... });\n * ```\n */\n get events(): CustomGeoJSONEvents<TSources> {\n const result = {} as CustomGeoJSONEvents<TSources>;\n for (const sourceName of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n const sourceWithLayers = this.sourcesWithLayers[sourceName];\n const shownHandlers = this.shownFeaturesHandlersBySource.get(sourceName) ?? [];\n result[sourceName] = new CombinedEvents(\n new UserEvents<MapGeoJSONFeature>(this.eventsProxy, sourceWithLayers, this.config?.events),\n new ModuleEvents<CustomGeoJSONModuleConfig<TSources>, TSources[typeof sourceName]>(\n this.configChangeHandlers,\n shownHandlers as ((data: TSources[typeof sourceName]) => void)[],\n ),\n );\n }\n return result;\n }\n}\n\nconst validateConfig = <TSources extends Record<string, FeatureCollection>>(\n config: CustomGeoJSONModuleConfig<TSources>,\n): void => {\n if (!config.sources || Object.keys(config.sources).length === 0) {\n throw new Error('CustomGeoJSONModule requires at least one source in config.sources.');\n }\n for (const [sourceName, sourceSpec] of Object.entries(config.sources)) {\n if (!sourceSpec.layers || sourceSpec.layers.length === 0) {\n throw new Error(`CustomGeoJSONModule source \"${sourceName}\" requires at least one layer.`);\n }\n }\n};\n\n// Guarantees every feature has matching top-level `feature.id` and `properties.id`,\n// generating an index-based ID when neither is set. This makes feature lookup reliable\n// across both cluster mode (no `promoteId`, where MapLibre uses the top-level\n// `feature.id`) and non-cluster mode (where `promoteId: 'id'` makes MapLibre treat\n// `properties.id` as the feature id). Without it, features missing IDs collapse onto\n// `feature.id === undefined` and event handlers can't distinguish between them.\nconst ensureFeatureIDs = <T extends FeatureCollection>(data: T): T => {\n let needsRewrite = false;\n for (const feature of data.features) {\n if (feature.id == null || feature.properties?.id !== feature.id) {\n needsRewrite = true;\n break;\n }\n }\n if (!needsRewrite) return data;\n return {\n ...data,\n features: data.features.map((feature, index) => {\n const id = feature.id ?? feature.properties?.id ?? index;\n return {\n ...feature,\n id,\n properties: { ...feature.properties, id },\n };\n }),\n } as T;\n};\n","// ─── Geometry (utility) ──────────────────────────────────────────────────────\n\n/** The world ring produced by turf.mask */\nexport const TURF_MASK_WORLD_RING = [\n [180, 90],\n [-180, 90],\n [-180, -90],\n [180, -90],\n [180, 90],\n];\n\n// ─── Default geometry styling ─────────────────────────────────────────────────\n\n/** Default fill/line color for geometries. */\nexport const DEFAULT_COLOR = '#0A3653';\n\n/** Default fill opacity. */\nexport const DEFAULT_FILL_OPACITY = 0.15;\n\n/** Default line color. */\nexport const DEFAULT_LINE_OPACITY = 1;\n\n/** Default line width in pixels. */\nexport const DEFAULT_LINE_WIDTH = 2;\n\n// ─── Themed geometry styling (outline / filled / inverted) ───────────────────\n\n/** Fill opacity for the outline theme (transparent fill). */\nexport const OUTLINE_THEME_FILL_OPACITY = 0;\n\n/** Line color for the outline theme. */\nexport const OUTLINE_THEME_LINE_COLOR = '#555555';\n\n/** Line opacity for the outline theme. */\nexport const OUTLINE_THEME_LINE_OPACITY = 0.9;\n\n/** Line width for the outline theme. */\nexport const OUTLINE_THEME_LINE_WIDTH = 5;\n\n/** Fill opacity for the filled/inverted theme. */\nexport const FILLED_THEME_FILL_OPACITY = 0.6;\n\n/** Line color for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_COLOR = 'grey';\n\n/** Line opacity for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_OPACITY = 1;\n\n/** Line width for the filled/inverted theme. */\nexport const FILLED_THEME_LINE_WIDTH = 1;\n\n// ─── Label styling ────────────────────────────────────────────────────────────\n\n/** Text color shared by title and border labels. */\nexport const TITLE_COLOR = '#333333';\n\n/** Text halo color shared by title and border labels. */\nexport const TITLE_HALO_COLOR = '#FFFFFF';\n\n/** Text size for geometry title (point) labels. */\nexport const TITLE_SIZE = 15;\n\n/** Text padding for geometry title (point) labels. */\nexport const TITLE_PADDING = 5;\n\n/** Minimum zoom level for border labels. */\nexport const BORDER_LABEL_MIN_ZOOM = 3;\n\n/** Text size for border labels. */\nexport const BORDER_LABEL_TEXT_SIZE = 15;\n\n/** Symbol spacing for border labels, in pixels. */\nexport const BORDER_LABEL_SYMBOL_SPACING = 200;\n\n/** Text halo width for border labels, in pixels. */\nexport const BORDER_LABEL_TEXT_HALO_WIDTH = 2;\n","import { isNil } from 'lodash-es';\nimport type { FillLayerSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, SymbolLayerSpecWithoutSource } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport type { GeometriesModuleConfig } from '../types/geometriesModuleConfig';\nimport {\n BORDER_LABEL_MIN_ZOOM,\n BORDER_LABEL_SYMBOL_SPACING,\n BORDER_LABEL_TEXT_HALO_WIDTH,\n BORDER_LABEL_TEXT_SIZE,\n DEFAULT_COLOR,\n DEFAULT_FILL_OPACITY,\n DEFAULT_LINE_OPACITY,\n DEFAULT_LINE_WIDTH,\n OUTLINE_THEME_FILL_OPACITY,\n OUTLINE_THEME_LINE_COLOR,\n OUTLINE_THEME_LINE_OPACITY,\n OUTLINE_THEME_LINE_WIDTH,\n TITLE_COLOR,\n TITLE_HALO_COLOR,\n TITLE_PADDING,\n TITLE_SIZE,\n} from './constants';\n\n/**\n * @ignore\n */\nexport const geometryFillSpec: LayerSpecTemplate<FillLayerSpecification> = {\n type: 'fill',\n paint: {\n 'fill-color': ['coalesce', ['get', 'color'], DEFAULT_COLOR],\n 'fill-opacity': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_FILL_OPACITY, DEFAULT_FILL_OPACITY],\n 'fill-antialias': false,\n },\n};\n\n/**\n * @ignore\n */\nexport const geometryOutlineSpec: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n paint: {\n 'line-color': [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n ['coalesce', ['get', 'color'], OUTLINE_THEME_LINE_COLOR],\n ['coalesce', ['get', 'color'], DEFAULT_COLOR],\n ],\n 'line-opacity': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_OPACITY, DEFAULT_LINE_OPACITY],\n 'line-width': ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_WIDTH, DEFAULT_LINE_WIDTH],\n },\n};\n\n/**\n * Builds Geometry layer specifications for fill and outline layers.\n * @ignore\n */\nexport const buildGeometryLayerSpecs = (\n fillLayerId: string,\n outlineLayerId: string,\n config?: GeometriesModuleConfig,\n): [SymbolLayerSpecWithoutSource, SymbolLayerSpecWithoutSource] => {\n const fill = config?.fill;\n const line = config?.line;\n\n const fillLayerSpec = {\n ...geometryFillSpec,\n id: fillLayerId,\n paint: {\n ...geometryFillSpec.paint,\n ...(!isNil(fill?.opacity) && { 'fill-opacity': fill?.opacity }),\n ...(fill?.color && { 'fill-color': ['get', 'color'] }),\n },\n } as unknown as SymbolLayerSpecWithoutSource;\n\n // `line.layer` is a full-spec escape hatch: merge it onto the outline layer first\n // (top-level, then `layout`/`paint` per property so base defaults survive), then let the\n // curated named fields below win over any paint property it sets.\n const linePassthrough = line?.layer;\n\n const outlineLayerSpec = {\n ...geometryOutlineSpec,\n ...linePassthrough,\n id: outlineLayerId,\n ...(linePassthrough?.layout && { layout: { ...geometryOutlineSpec.layout, ...linePassthrough.layout } }),\n paint: {\n ...geometryOutlineSpec.paint,\n ...linePassthrough?.paint,\n ...(!isNil(line?.color) && { 'line-color': line?.color }),\n ...(!isNil(line?.width) && { 'line-width': line?.width }),\n ...(!isNil(line?.opacity) && { 'line-opacity': line?.opacity }),\n },\n } as unknown as SymbolLayerSpecWithoutSource;\n\n return [fillLayerSpec, outlineLayerSpec];\n};\n\n/**\n * Build geometry layer specification for title.\n * @ignore\n */\nexport const buildGeometryTitleLayerSpec = (\n layerId: string,\n config?: GeometriesModuleConfig,\n): Omit<SymbolLayerSpecification, 'source'> => {\n const textConfig = config?.textConfig;\n\n return {\n type: 'symbol',\n id: layerId,\n layout: {\n 'text-field': ['get', 'title'],\n ...(textConfig?.textField && { 'text-field': textConfig.textField }),\n 'text-padding': TITLE_PADDING,\n 'text-size': TITLE_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n 'symbol-placement': 'point',\n },\n paint: {\n 'text-color': TITLE_COLOR,\n 'text-halo-color': TITLE_HALO_COLOR,\n 'text-halo-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 10, 1.5],\n 'text-translate-anchor': 'viewport',\n },\n };\n};\n\n/**\n * Build geometry layer specification for line labels.\n * Adds labels along the polygon border.\n * @ignore\n */\nexport const buildGeometryLineLabelLayerSpec = (\n layerId: string,\n config?: GeometriesModuleConfig,\n): Omit<SymbolLayerSpecification, 'source'> => {\n const lineLabelConfig = config?.lineLabelConfig;\n const minzoom = lineLabelConfig?.minZoom ?? BORDER_LABEL_MIN_ZOOM;\n\n return {\n type: 'symbol',\n id: layerId,\n minzoom,\n layout: {\n 'text-field': ['get', 'title'],\n 'symbol-placement': 'line',\n 'text-size': lineLabelConfig?.textSize ?? BORDER_LABEL_TEXT_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n 'symbol-spacing': lineLabelConfig?.symbolSpacing ?? BORDER_LABEL_SYMBOL_SPACING,\n 'text-keep-upright': true,\n 'text-offset': lineLabelConfig?.textOffset ?? [0, 1],\n },\n paint: {\n 'text-color': lineLabelConfig?.textColor ?? TITLE_COLOR,\n 'text-halo-color': lineLabelConfig?.textHaloColor ?? TITLE_HALO_COLOR,\n 'text-halo-width': lineLabelConfig?.textHaloWidth ?? BORDER_LABEL_TEXT_HALO_WIDTH,\n ...(!isNil(lineLabelConfig?.textOpacity) && { 'text-opacity': lineLabelConfig?.textOpacity }),\n },\n };\n};\n","export const colorPalettes = {\n warm: [\n '#793F0D',\n '#AC703D',\n '#C38E63',\n '#E49969',\n '#E5AE86',\n '#EEC5A9',\n '#6E7649',\n '#9D9754',\n '#C7C397',\n '#B4A851',\n '#DFD27C',\n '#E7E3B5',\n '#846D74',\n '#B7A6AD',\n '#D3C9CE',\n ],\n browns: [\n '#edc4b3',\n '#e6b8a2',\n '#deab90',\n '#d69f7e',\n '#cd9777',\n '#c38e70',\n '#b07d62',\n '#9d6b53',\n '#8a5a44',\n '#774936',\n ],\n cold: ['#344464', '#548ca4', '#549cac', '#2c445c', '#a4ccd4', '#acbccc', '#b4c4d4', '#acd4cc', '#5c8ca4'],\n fadedBlues: [\n '#152033',\n '#1d2d44',\n '#2e455d',\n '#3e5c76',\n '#4c6884',\n '#597491',\n '#67809e',\n '#6e86a5',\n '#7b91ad',\n '#879bb4',\n ],\n blues: ['#03045e', '#023e8a', '#0077b6', '#0096c7', '#00b4d8', '#48cae4', '#90e0ef', '#ade8f4', '#caf0f8'],\n greens: ['#004b23', '#006400', '#007200', '#008000', '#38b000', '#70e000', '#9ef01a', '#ccff33'],\n fadedGreenToBlue: [\n '#d9ed92',\n '#b5e48c',\n '#99d98c',\n '#76c893',\n '#52b69a',\n '#34a0a4',\n '#168aad',\n '#1a759f',\n '#1e6091',\n '#184e77',\n ],\n blueToRed: [\n '#033270',\n '#1368aa',\n '#4091c9',\n '#9dcee2',\n '#fedfd4',\n '#f29479',\n '#f26a4f',\n '#ef3c2d',\n '#cb1b16',\n '#65010c',\n ],\n greenToYellow: [\n '#007f5f',\n '#2b9348',\n '#55a630',\n '#80b918',\n '#aacc00',\n '#bfd200',\n '#d4d700',\n '#dddf00',\n '#eeef20',\n '#ffff3f',\n ],\n pastel: [\n '#fec5bb',\n '#fcd5ce',\n '#fae1dd',\n '#f8edeb',\n '#e8e8e4',\n '#d8e2dc',\n '#ece4db',\n '#ffe5d9',\n '#ffd7ba',\n '#fec89a',\n ],\n retro: [\n '#f94144',\n '#f3722c',\n '#f8961e',\n '#f9844a',\n '#f9c74f',\n '#90be6d',\n '#43aa8b',\n '#4d908e',\n '#577590',\n '#277da1',\n ],\n contrastRetro: [\n '#001219',\n '#005f73',\n '#0a9396',\n '#94d2bd',\n '#e9d8a6',\n '#ee9b00',\n '#ca6702',\n '#bb3e03',\n '#ae2012',\n '#9b2226',\n ],\n fadedRainbow: ['#ffadad', '#ffd6a5', '#fdffb6', '#caffbf', '#9bf6ff', '#a0c4ff', '#bdb2ff', '#ffc6ff', '#fffffc'],\n pastelRainbow: [\n '#54478c',\n '#2c699a',\n '#048ba8',\n '#0db39e',\n '#16db93',\n '#83e377',\n '#b9e769',\n '#efea5a',\n '#f1c453',\n '#f29e4c',\n ],\n};\n\nexport type ColorPaletteOptions = keyof typeof colorPalettes;\nexport const colorPaletteIDs = Object.keys(colorPalettes) as ColorPaletteOptions[];\n","import {\n bboxCenter,\n bboxFromCoordsArray,\n generateId,\n type PolygonFeature,\n PolygonFeatures,\n} from '@tomtom-org/maps-sdk/core';\nimport { mask } from '@turf/turf';\nimport type { Feature, FeatureCollection, GeoJsonProperties, MultiPolygon, Point, Polygon, Position } from 'geojson';\nimport type { DataDrivenPropertyValueSpecification } from 'maplibre-gl';\nimport { type ColorPaletteOptions, colorPalettes } from './layers/colorPalettes';\nimport type { GeometriesModuleConfig } from './types/geometriesModuleConfig';\nimport type { DisplayGeometryProps, ExtraGeometryDisplayProps } from './types/geometryDisplayProps';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/**\n * Build geometry Title. The type can be a string or a Maplibre expression.\n * @param feature - Geometry\n * @param config - Geometry module configuration\n * @returns\n */\nconst buildTitle = (\n feature: Feature<Polygon | MultiPolygon, DisplayGeometryProps | GeoJsonProperties>,\n config: GeometriesModuleConfig,\n): DataDrivenPropertyValueSpecification<string> | string | undefined => {\n if (config.textConfig?.textField) {\n return config.textConfig.textField;\n }\n return feature.properties?.address?.freeformAddress;\n};\n\n/**\n * Builds a geometry color string or MapLibre expression.\n * @param config - Geometry module configuration\n * @param index - Number to use as index to pick color from palette option\n */\nconst buildColor = (\n config: GeometriesModuleConfig,\n index: number,\n): DataDrivenPropertyValueSpecification<string> | string | undefined => {\n const color = config?.fill?.color;\n if (typeof color === 'string' && colorPalettes[color as ColorPaletteOptions]) {\n const palette = colorPalettes[color as ColorPaletteOptions];\n return palette[index % palette.length];\n }\n return color;\n};\n\n/** Finds the longest coordinate array in a MultiPolygon for title placement. */\nconst getLongestArray = (coordinates: Position[][][]) =>\n coordinates.flat().reduce((result, coord) => (coord.length > result.length ? coord : result), []);\n\n/**\n * Converts a polygon into a world-minus-polygon donut for the `'inverted'` theme.\n *\n * Uses `turf.mask` to punch the polygon as a hole in a world bounding box.\n *\n * @remarks\n * Called automatically when a feature or config has `theme: 'inverted'`.\n *\n * @group Geometries\n */\nexport const invertFeature = (feature: PolygonFeature): PolygonFeature => {\n const masked = mask(feature);\n return {\n ...feature,\n geometry: masked.geometry,\n } as PolygonFeature;\n};\n\ntype InputFeatureProps = {\n theme?: GeometryTheme;\n title?: string;\n color?: string;\n id?: string;\n};\n/**\n * Prepares polygon features for display by applying theme, colors, and titles.\n *\n * @param geometry - Polygon features to prepare.\n * @param config - Module configuration for styling.\n * @returns Processed features ready for MapLibre rendering.\n * @ignore\n */\nexport const prepareGeometryForDisplay = (\n geometry: PolygonFeatures<GeoJsonProperties | DisplayGeometryProps>,\n config: GeometriesModuleConfig = {},\n): PolygonFeatures<ExtraGeometryDisplayProps> => ({\n ...geometry,\n features: geometry.features.map((feature, index) => {\n const props = (feature.properties ?? {}) as InputFeatureProps;\n const effectiveTheme = props.theme ?? config.theme;\n const processedFeature = effectiveTheme === 'inverted' ? invertFeature(feature as PolygonFeature) : feature;\n const processedProps = (processedFeature.properties ?? {}) as InputFeatureProps;\n // buildTitle/buildColor may return MapLibre expressions; cast since output type expects string\n const title = (processedProps.title ?? buildTitle(processedFeature, config)) as string | undefined;\n const color = (processedProps.color ?? buildColor(config, index)) as string | undefined;\n return {\n ...processedFeature,\n properties: {\n ...processedFeature.properties,\n ...(effectiveTheme && { theme: effectiveTheme }),\n ...(title && { title }),\n ...(color && { color }),\n id: processedProps.id ?? generateId(),\n },\n };\n }),\n});\n\n/**\n * Create a Feature<Point> with coordinates where title will be placed.\n * If feature properties contains a coordinates value, it will use it.\n * In case there is not coordinates value, it will get the biggest Polygon inside a feature and calculate\n * the bounding box for those coordinates and finally calculate the bounding box center to place the title.\n * @param geometries\n * @returns\n * @ignore\n */\nexport const prepareTitleForDisplay = (geometries: PolygonFeatures): FeatureCollection<Point> => {\n const features = geometries.features.map((feature) => {\n let coordinates: Position[] | Position | null;\n\n if (feature.properties?.placeCoordinates) {\n coordinates = feature.properties?.placeCoordinates;\n } else if (feature.geometry.type === 'MultiPolygon') {\n const biggestPolygon = getLongestArray(feature.geometry.coordinates);\n const bbox = bboxFromCoordsArray(biggestPolygon);\n coordinates = (bbox && bboxCenter(bbox)) || null;\n } else {\n coordinates = feature.geometry.coordinates.flat();\n }\n\n const id = feature.id ?? feature.properties?.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: { type: 'Point', coordinates },\n properties: {\n ...feature.properties,\n id, // we need id in properties due to promoteId feature\n },\n } as Feature<Point>;\n });\n\n return { type: 'FeatureCollection', bbox: geometries.bbox, features };\n};\n","import type { PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection, Point } from 'geojson';\nimport type { SymbolLayerSpecification } from 'maplibre-gl';\nimport type { BeforeLayerConfig, SymbolLayerSpecWithoutSource, ToBeAddedLayerSpec } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { changeLayerProps, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildGeometryLayerSpecs,\n buildGeometryLineLabelLayerSpec,\n buildGeometryTitleLayerSpec,\n} from './layers/geometryLayers';\nimport { prepareGeometryForDisplay, prepareTitleForDisplay } from './prepareGeometryForDisplay';\nimport type {\n GeometriesModuleConfig,\n GeometryBeforeLayerConfig,\n GeometryTextConfig,\n} from './types/geometriesModuleConfig';\n\n/**\n * IDs of sources and layers from a geometry module.\n */\ntype GeometrySourcesWithLayers = {\n geometry: GeoJSONSourceWithLayers<PolygonFeatures>;\n geometryLabel: GeoJSONSourceWithLayers<FeatureCollection<Point>>;\n};\n\n/**\n * Geometries Module for displaying polygon areas with custom styling on the map.\n *\n * This module enables visualization of geographic areas (polygons) with customizable\n * colors, borders, and labels. Ideal for displaying search results, administrative\n * boundaries, service areas, or any polygon-based geographic data.\n *\n * @remarks\n * **Features:**\n * - Display single or multiple polygon geometries\n * - Customizable fill colors and opacity\n * - Configurable borders (color, width, opacity)\n * - Optional text labels for geometries\n * - Support for data-driven styling via MapLibre expressions\n * - Layer ordering control\n * - Event handling for user interactions\n *\n * **Data Format:**\n * - Accepts GeoJSON Polygon and MultiPolygon features\n * - Supports FeatureCollection for multiple geometries\n * - Compatible with TomTom Search API geometry results\n *\n * **Styling:**\n * - Use predefined color palettes or custom colors\n * - Apply MapLibre expressions for dynamic styling\n * - Per-feature styling via feature properties\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { GeometriesModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Initialize module\n * const geometriesModule = await GeometriesModule.get(map);\n *\n * // Display a polygon\n * await geometriesModule.show({\n * type: 'Feature',\n * geometry: {\n * type: 'Polygon',\n * coordinates: [[[4.88, 52.37], [4.89, 52.37], [4.89, 52.38], [4.88, 52.38], [4.88, 52.37]]]\n * },\n * properties: {\n * title: 'Area of Interest'\n * }\n * });\n * ```\n *\n * @example\n * Custom styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * fill: { color: '#FF5733', opacity: 0.3 },\n * line: { color: '#C70039', width: 3 },\n * textConfig: {\n * textField: ['get', 'name']\n * }\n * });\n *\n * await geometriesModule.show(polygonFeatures);\n * ```\n *\n * @example\n * Multiple geometries with different colors:\n * ```typescript\n * await geometriesModule.show({\n * type: 'FeatureCollection',\n * features: [\n * {\n * type: 'Feature',\n * geometry: { type: 'Polygon', coordinates: [...] },\n * properties: { color: '#FF0000', title: 'Red Zone' }\n * },\n * {\n * type: 'Feature',\n * geometry: { type: 'Polygon', coordinates: [...] },\n * properties: { color: '#00FF00', title: 'Green Zone' }\n * }\n * ]\n * });\n * ```\n *\n * @example\n * Event handling:\n * ```typescript\n * geometriesModule.events.on('click', (feature, lngLat) => {\n * console.log('Clicked geometry:', feature.properties.title);\n * console.log('At coordinates:', lngLat);\n * });\n *\n * geometriesModule.events.on('hover', (feature) => {\n * showTooltip(feature.properties.title);\n * });\n * ```\n *\n * @see [Geometries Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/geometries)\n *\n * @group Geometries\n */\nexport class GeometriesModule extends AbstractMapModule<GeometrySourcesWithLayers, GeometriesModuleConfig> {\n private titleLayerSpecs!: SymbolLayerSpecWithoutSource;\n private geometryFillLayerSpecs!: SymbolLayerSpecWithoutSource;\n private geometryOutlineLayerSpecs!: SymbolLayerSpecWithoutSource;\n private lineLabelLayerSpecs: SymbolLayerSpecWithoutSource | null = null;\n\n private sourceID!: string;\n private fillLayerID!: string;\n private outlineLayerID!: string;\n private lineLabelLayerID!: string;\n\n private titleSourceID!: string;\n private titleLayerID!: string;\n\n // Cached for restoreDataAndConfigImpl on style change.\n private lastRawInput: PolygonFeatures | null = null;\n private readonly shownFeaturesHandlers: ((features: PolygonFeatures) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n *\n * @remarks\n * **Configuration Options:**\n * - `fill`: Fill color, opacity, and optional fill-layer positioning\n * - `line`: Border/outline styling and optional border-layer positioning\n * - `textConfig`: Label display configuration\n * - `beforeLayerConfig`: Layer ordering — a single target, or `{ all, fill, line }` to split them\n *\n * **Multiple Instances:**\n * You can create multiple GeometriesModule instances on the same map,\n * each managing different sets of geometries with different styles.\n *\n * @example\n * Default initialization:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map);\n * ```\n *\n * @example\n * With custom styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * fill: { color: 'blue', opacity: 0.25 },\n * line: { color: 'darkblue', width: 2, opacity: 0.8 },\n * textConfig: {\n * textField: ['get', 'title']\n * },\n * beforeLayerConfig: 'top'\n * });\n * ```\n *\n * @example\n * Data-driven styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.get(map, {\n * fill: {\n * // Color based on feature properties\n * color: [\n * 'match',\n * ['get', 'type'],\n * 'residential', '#FFEB3B',\n * 'commercial', '#2196F3',\n * 'industrial', '#9E9E9E',\n * '#E0E0E0' // default\n * ],\n * opacity: 0.4\n * }\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: GeometriesModuleConfig): Promise<GeometriesModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new GeometriesModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: GeometriesModuleConfig) {\n super('geojson', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: GeometriesModuleConfig, restore?: boolean): GeometrySourcesWithLayers {\n if (!restore) {\n this.sourceID = `geometry-${this.instanceIndex}`;\n this.titleSourceID = `geometryTitle-${this.instanceIndex}`;\n const layerIdPrefix = `geometry-${this.instanceIndex}`;\n this.fillLayerID = `${layerIdPrefix}_Fill`;\n this.outlineLayerID = `${layerIdPrefix}_Outline`;\n this.lineLabelLayerID = `${layerIdPrefix}_LineLabel`;\n this.titleLayerID = `${layerIdPrefix}_Title`;\n }\n\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n config,\n );\n const titleLayerSpec = buildGeometryTitleLayerSpec(this.titleLayerID, config);\n this.titleLayerSpecs = titleLayerSpec;\n this.geometryFillLayerSpecs = geometryFillSpec;\n this.geometryOutlineLayerSpecs = geometryOutlineSpec;\n\n const lineLabelSpec =\n config?.lineLabelConfig === undefined\n ? null\n : buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, config);\n this.lineLabelLayerSpecs = lineLabelSpec as SymbolLayerSpecWithoutSource | null;\n\n return {\n geometry: new GeoJSONSourceWithLayers(this.mapLibreMap, this.sourceID, [\n { ...geometryFillSpec },\n { ...geometryOutlineSpec },\n ...(lineLabelSpec ? [lineLabelSpec as ToBeAddedLayerSpec<SymbolLayerSpecification>] : []),\n ]),\n geometryLabel: new GeoJSONSourceWithLayers(this.mapLibreMap, this.titleSourceID, [\n titleLayerSpec as ToBeAddedLayerSpec<SymbolLayerSpecification>,\n ]),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: GeometriesModuleConfig | undefined) {\n if (config?.textConfig || config?.fill || config?.line) {\n this.updateLayerAndData(config);\n }\n if (config?.beforeLayerConfig || config?.fill?.beforeLayerConfig || config?.line?.beforeLayerConfig) {\n this.applyBeforeLayerConfig(config);\n }\n return config;\n }\n\n /** Resolve a positioning target to the map-style layer id the geometry layers should sit before. */\n private resolveBeforeID(target: BeforeLayerConfig): string {\n return target === 'top' ? this.titleLayerID : mapStyleLayerIDs[target];\n }\n\n private moveLayersBefore(layerIDs: string[], beforeLayerID: string) {\n for (const layer of layerIDs) {\n this.mapLibreMap.moveLayer(layer, beforeLayerID);\n }\n }\n\n // The fill is its own layer; the border/outline and its optional line labels move together as the\n // \"line\" group. When fill and line target the same layer, move fill first so the border ends up\n // above it (matching the source's fill-under-outline z-order).\n private get fillLayerIDs(): string[] {\n return [this.fillLayerID];\n }\n private get lineLayerIDs(): string[] {\n return this.lineLabelLayerSpecs ? [this.outlineLayerID, this.lineLabelLayerID] : [this.outlineLayerID];\n }\n\n /**\n * Positions the geometry layers relative to other map layers.\n *\n * @param layerConfig - Layer positioning. A single {@link BeforeLayerConfig} (`'top'` or a layer\n * id) moves all geometry layers together; the object form (`{ all?, fill?, line? }`) positions the\n * fill and border independently.\n *\n * @remarks\n * A per-layer `beforeLayerConfig` set inside the `fill` / `line` config sections takes precedence\n * over the value passed here.\n *\n * **Available Layer IDs:** predefined ids from `mapStyleLayerIDs`, or a custom layer id.\n *\n * @example\n * ```typescript\n * import { mapStyleLayerIDs } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Place all geometry layers below labels\n * geometries.moveBeforeLayer(mapStyleLayerIDs.lowestLabel);\n *\n * // Fill below the road network, border on top\n * geometries.moveBeforeLayer({ fill: 'lowestRoadLine', line: 'top' });\n * ```\n */\n moveBeforeLayer(layerConfig: GeometryBeforeLayerConfig) {\n this.config = { ...this.config, beforeLayerConfig: layerConfig };\n this.applyBeforeLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n // Compute the effective before-target for each layer group and move it. Precedence per group: the\n // fill/line section's own `beforeLayerConfig`, else the top-level object's matching key (falling\n // back to `all`), else the top-level scalar. Undefined leaves that group where it is.\n private applyBeforeLayerConfig(config: GeometriesModuleConfig | undefined) {\n const top = config?.beforeLayerConfig;\n const topFor = (kind: 'fill' | 'line'): BeforeLayerConfig | undefined => {\n if (top === undefined) return undefined;\n if (typeof top === 'string') return top;\n return top[kind] ?? top.all;\n };\n const fillTarget = config?.fill?.beforeLayerConfig ?? topFor('fill');\n const lineTarget = config?.line?.beforeLayerConfig ?? topFor('line');\n if (fillTarget !== undefined) this.moveLayersBefore(this.fillLayerIDs, this.resolveBeforeID(fillTarget));\n if (lineTarget !== undefined) this.moveLayersBefore(this.lineLayerIDs, this.resolveBeforeID(lineTarget));\n }\n\n /**\n * Updates the text/label configuration for displayed geometries.\n *\n * @param textConfig - New text configuration settings.\n *\n * @remarks\n * **Configuration:**\n * - `textField`: MapLibre expression for label text content\n * - Supports dynamic text based on feature properties\n * - Changes apply to currently shown and future geometries\n *\n * @example\n * ```typescript\n * // Show feature property as label\n * geometries.applyTextConfig({\n * textField: ['get', 'name']\n * });\n *\n * // Conditional labels\n * geometries.applyTextConfig({\n * textField: [\n * 'case',\n * ['has', 'label'],\n * ['get', 'label'],\n * ['get', 'title']\n * ]\n * });\n * ```\n */\n applyTextConfig(textConfig: GeometryTextConfig) {\n const config = { ...this.config, textConfig };\n this.updateLayerAndData(config);\n // TODO: is this consistent with _applyConfig?\n this.sourcesWithLayers.geometryLabel.show(\n prepareTitleForDisplay(this.sourcesWithLayers.geometry.shownFeatures),\n );\n this.config = config;\n this.emitConfigChange();\n }\n\n private updateLayerAndData(config: GeometriesModuleConfig) {\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n config,\n );\n const newTitleLayerSpecs = buildGeometryTitleLayerSpec(this.titleLayerID, config);\n\n changeLayerProps(geometryFillSpec, this.geometryFillLayerSpecs, this.mapLibreMap);\n changeLayerProps(geometryOutlineSpec, this.geometryOutlineLayerSpecs, this.mapLibreMap);\n changeLayerProps(newTitleLayerSpecs, this.titleLayerSpecs, this.mapLibreMap);\n\n this.geometryFillLayerSpecs = geometryFillSpec;\n this.geometryOutlineLayerSpecs = geometryOutlineSpec;\n this.titleLayerSpecs = newTitleLayerSpecs;\n\n if (this.lineLabelLayerSpecs) {\n const newLineLabelSpec = buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, config);\n changeLayerProps(\n newLineLabelSpec as SymbolLayerSpecWithoutSource,\n this.lineLabelLayerSpecs,\n this.mapLibreMap,\n );\n this.lineLabelLayerSpecs = newLineLabelSpec as SymbolLayerSpecWithoutSource;\n }\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previousInput = this.lastRawInput;\n this.initSourcesWithLayers(this.config, true);\n this.config && this._applyConfig(this.config);\n if (previousInput) {\n // Fire-and-forget: base class requires sync, but show() is async.\n void this.show(previousInput);\n }\n }\n\n /**\n * Displays the given polygon geometries on the map.\n *\n * @param geometries - Polygon features to display. Can be a single Feature,\n * array of Features, or a FeatureCollection.\n *\n * @remarks\n * **Behavior:**\n * - Replaces any previously shown geometries\n * - Applies current module styling configuration\n * - Waits for module to be ready before displaying\n * - Automatically handles both Polygon and MultiPolygon types\n *\n * **Feature Properties:**\n * - `title`: Used for labels if text config is set\n * - `color`: Override fill color per feature\n * - Custom properties accessible in styling expressions\n *\n * @example\n * Single polygon:\n * ```typescript\n * await geometries.show({\n * type: 'Feature',\n * geometry: {\n * type: 'Polygon',\n * coordinates: [[[4.88, 52.37], [4.89, 52.37], [4.89, 52.38], [4.88, 52.37]]]\n * },\n * properties: {\n * title: 'Amsterdam Center',\n * color: '#FF5733'\n * }\n * });\n * ```\n *\n * @example\n * Multiple polygons:\n * ```typescript\n * await geometries.show({\n * type: 'FeatureCollection',\n * features: [\n * { type: 'Feature', geometry: {...}, properties: {...} },\n * { type: 'Feature', geometry: {...}, properties: {...} }\n * ]\n * });\n * ```\n *\n * @example\n * From search API response:\n * ```typescript\n * import { search } from '@tomtom-international/maps-sdk-js/services';\n *\n * const result = await search.geometrySearch({\n * query: 'Amsterdam',\n * geometryList: [{ type: 'CIRCLE', position: [52.37, 4.89], radius: 5000 }]\n * });\n *\n * if (result.results[0].dataSources?.geometry) {\n * await geometries.show(result.results[0].dataSources.geometry);\n * }\n * ```\n */\n async show(geometries: PolygonFeatures) {\n await this.waitUntilModuleReady();\n this.lastRawInput = geometries;\n const { transformFeaturesForDisplay } = this.config ?? {};\n const transformed = transformFeaturesForDisplay ? transformFeaturesForDisplay(geometries) : geometries;\n const geometry = this.sourcesWithLayers.geometry;\n geometry.show(prepareGeometryForDisplay(transformed, this.config));\n this.sourcesWithLayers.geometryLabel.show(prepareTitleForDisplay(geometry.shownFeatures));\n for (const handler of this.shownFeaturesHandlers) {\n handler(geometries);\n }\n }\n\n /**\n * Removes all geometries from the map.\n *\n * @remarks\n * - Clears both geometry layers and labels\n * - Does not reset styling configuration\n * - Module remains initialized and ready for new data\n *\n * @example\n * ```typescript\n * // Clear displayed geometries\n * await geometries.clear();\n *\n * // Show new geometries\n * await geometries.show(newGeometries);\n * ```\n */\n async clear() {\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.geometry.clear();\n this.sourcesWithLayers.geometryLabel.clear();\n }\n\n /**\n * Returns the currently shown geometries.\n *\n * @returns The geometries currently displayed on the map.\n *\n * @remarks\n * Returns the exact data that was passed to the `show()` method.\n *\n * @example\n * ```typescript\n * const shown = geometriesModule.getShown();\n * console.log(`Geometries: ${shown.geometry.features.length}`);\n * ```\n */\n getShown() {\n return {\n geometry: this.sourcesWithLayers.geometry.shownFeatures,\n geometryLabel: this.sourcesWithLayers.geometryLabel.shownFeatures,\n };\n }\n\n /**\n * Gets the events interface for handling user interactions with geometries.\n *\n * @returns A `UserEvents` instance for registering event handlers.\n *\n * @remarks\n * **Supported Events:**\n * - `click`: User clicks on a geometry\n * - `contextmenu`: User right-clicks on a geometry\n * - `hover`: Mouse enters a geometry\n * - `long-hover`: Mouse hovers over geometry for extended time\n *\n * **Event Features:**\n * - Receive the original feature data passed to `show()`\n * - Access feature properties and geometry\n * - Get click/hover coordinates\n *\n * @example\n * Basic event handling:\n * ```typescript\n * geometries.events.on('click', (feature, lngLat) => {\n * console.log('Clicked:', feature.properties);\n * console.log('Location:', lngLat);\n * });\n * ```\n *\n * @example\n * Multiple handlers:\n * ```typescript\n * // Highlight on hover\n * geometries.events.on('hover', (feature) => {\n * highlightGeometry(feature.id);\n * });\n *\n * // Show details on click\n * geometries.events.on('click', (feature) => {\n * showDetailPanel(feature.properties);\n * });\n *\n * // Context menu\n * geometries.events.on('contextmenu', (feature, lngLat) => {\n * showContextMenu(lngLat, feature);\n * });\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, GeometriesModuleConfig, PolygonFeatures> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.geometry, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n}\n","import { type PolygonFeature, type PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport { difference, featureCollection, mask } from '@turf/turf';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/** Custom label generator for reachable range features. */\nexport type ReachableRangeLabelFn = (feature: PolygonFeature, index: number) => string;\n\n/** Unit suffix for each budget type, used for auto-generated labels. */\nconst BUDGET_UNITS: Readonly<Record<string, string>> = {\n timeMinutes: 'min',\n distanceKM: 'km',\n remainingChargeCPT: '% remaining',\n spentChargePCT: '% spent',\n spentFuelLiters: 'L',\n};\n\n/**\n * Splits each reachable-range polygon into two features for the inverted theme:\n * - Donut fill (no `theme` prop) — pre-inverted geometry via `mask`/`difference`; no border or label.\n * - Border polygon (`theme: 'filled'`) — original geometry; fill suppressed; carries border and line label.\n *\n * Separating fills and borders prevents the line-label layer from tracing inner donut holes.\n *\n * Donut fills intentionally have `theme` stripped. `prepareGeometryForDisplay` inverts any feature\n * whose resolved theme is `'inverted'` (`props.theme ?? config.theme`). Since donut geometry is already\n * pre-inverted via `mask`/`difference`, stripping `theme` prevents double inversion without the need of\n * a flag or marker leaking into the user-facing API.\n */\nconst buildDonutFeatures = (features: PolygonFeature[]): PolygonFeature[] => {\n const donutFills = features.map((feature, i) => {\n const donutGeo = i === 0 ? mask(feature) : difference(featureCollection([features[i - 1], feature]));\n const { title: _title, theme: _theme, ...rest } = feature.properties ?? {};\n return { ...feature, geometry: donutGeo?.geometry ?? feature.geometry, properties: rest } as PolygonFeature;\n });\n\n const borderPolygons = features.map((feature) => ({\n ...feature,\n id: undefined,\n properties: { ...feature.properties, theme: 'filled' as GeometryTheme },\n })) as PolygonFeature[];\n\n return [...donutFills, ...borderPolygons];\n};\n\n/**\n * Attaches labels and applies theme-specific geometry to reachable range polygon features.\n *\n * Features must be ordered **largest budget first** (e.g. 30 min, 20 min, 10 min).\n * For the `inverted` theme, polygons are converted to donuts and one extra innermost feature is appended.\n *\n * @param features Polygon features from `calculateReachableRange`, ordered largest budget first.\n * @param labels Label per feature in matching order (e.g. `['30 min', '20 min', '10 min']`).\n * @param theme Visual theme. Defaults to `'filled'`.\n *\n * @example\n * ```typescript\n * const features = [range30min, range20min, range10min];\n * const labels = ['30 min', '20 min', '10 min'];\n *\n * module.show({\n * type: 'FeatureCollection',\n * features: buildReachableRangeFeatures(features, labels, 'inverted'),\n * });\n * ```\n *\n * @group Geometries\n */\nexport const buildReachableRangeFeatures = (\n features: PolygonFeature[],\n labels: string[],\n theme: GeometryTheme = 'filled',\n): PolygonFeature[] => {\n const labelled = features.map((f, i) => ({\n ...f,\n properties: { ...f.properties, title: labels[i], theme },\n }));\n if (theme === 'inverted') {\n return buildDonutFeatures(labelled);\n }\n return labelled;\n};\n\n/**\n * Prepares the output of `calculateReachableRanges` for display with {@link GeometriesModule}.\n *\n * Derives labels from each feature's `budget` property by default, applies theme-specific\n * geometry, and preserves the bounding box. Pass the returned value directly to `module.show()`.\n *\n * Note: when using {@link reachableRangeGeometryConfig} this is called automatically — you only\n * need this function for explicit control over labels or theme.\n *\n * Features must be ordered **largest budget first** (e.g. 30 min, 20 min, 10 min).\n *\n * @param result Raw output of `calculateReachableRanges`.\n * @param theme Visual theme. Defaults to `'filled'`.\n * @param label Custom label generator; when omitted, labels derived from `budget` property.\n *\n * @example\n * ```typescript\n * // explicit control:\n * module.show(prepareReachableRangesForDisplay(result, 'inverted'));\n * module.show(prepareReachableRangesForDisplay(result, 'filled', (f) => myLabel(f)));\n * ```\n *\n * @group Geometries\n */\nexport const prepareReachableRangesForDisplay = (\n result: PolygonFeatures,\n theme: GeometryTheme = 'filled',\n label?: ReachableRangeLabelFn,\n): PolygonFeatures => {\n const features = result.features as PolygonFeature[];\n const labels = features.map((f, i) => {\n if (label) {\n return label(f, i);\n }\n const budget = f.properties?.['budget'] as { type?: string; value?: number } | undefined;\n if (budget?.value !== undefined && budget?.type) {\n return `${budget.value} ${BUDGET_UNITS[budget.type] ?? budget.type}`;\n }\n return String(i + 1);\n });\n\n return {\n type: 'FeatureCollection',\n ...(result.bbox && { bbox: result.bbox }),\n features: buildReachableRangeFeatures(features, labels, theme),\n };\n};\n","import type { ColorPaletteOptions } from './layers/colorPalettes';\nimport {\n FILLED_THEME_FILL_OPACITY,\n FILLED_THEME_LINE_COLOR,\n FILLED_THEME_LINE_OPACITY,\n FILLED_THEME_LINE_WIDTH,\n OUTLINE_THEME_FILL_OPACITY,\n OUTLINE_THEME_LINE_COLOR,\n OUTLINE_THEME_LINE_OPACITY,\n OUTLINE_THEME_LINE_WIDTH,\n} from './layers/constants';\nimport { prepareReachableRangesForDisplay, type ReachableRangeLabelFn } from './prepareReachableRangesForDisplay';\nimport type { GeometriesModuleConfig, GeometryBeforeLayerConfig } from './types/geometriesModuleConfig';\nimport type { GeometryTheme } from './types/geometryTheme';\n\n/**\n * Config for themed polygon display with palette-based coloring.\n *\n * Includes data-driven expressions so each feature's `theme` property controls its\n * styling, line labels config, and positions layers below map labels.\n *\n * For reachable ranges, use {@link reachableRangeGeometryConfig} which extends this\n * with auto-generated labels.\n *\n * @param palette Color palette for polygon fills. Defaults to `'fadedRainbow'`.\n * @param beforeLayerConfig Layer positioning. Defaults to `'lowestLabel'`.\n *\n * @group Geometries\n */\nexport const themedGeometryConfig = (\n palette: ColorPaletteOptions = 'fadedRainbow',\n beforeLayerConfig: GeometryBeforeLayerConfig = 'lowestLabel',\n): GeometriesModuleConfig => ({\n beforeLayerConfig,\n line: {\n // outline theme: thick colored line; filled/inverted: thin grey line\n width: ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_WIDTH, FILLED_THEME_LINE_WIDTH],\n color: [\n 'case',\n ['==', ['get', 'theme'], 'outline'],\n ['coalesce', ['get', 'color'], OUTLINE_THEME_LINE_COLOR],\n FILLED_THEME_LINE_COLOR,\n ],\n opacity: ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_LINE_OPACITY, FILLED_THEME_LINE_OPACITY],\n },\n fill: {\n color: palette,\n // outline theme: transparent fill; filled/inverted: semi-transparent fill\n opacity: ['case', ['==', ['get', 'theme'], 'outline'], OUTLINE_THEME_FILL_OPACITY, FILLED_THEME_FILL_OPACITY],\n },\n // Non-undefined lineLabelConfig is required for GeometriesModule to always create the line label layer.\n lineLabelConfig: {},\n});\n\n/**\n * Returns a {@link GeometriesModuleConfig} for reachable ranges.\n *\n * Builds on {@link themedGeometryConfig} and overrides `transformFeaturesForDisplay` so the\n * raw output of `calculateReachableRanges` can be passed directly to {@link GeometriesModule.show}\n * — labels are derived from each feature's `budget` property.\n *\n * @param palette Color palette. Defaults to `'fadedRainbow'`.\n * @param theme Visual theme. Defaults to `'filled'`.\n * @param beforeLayerConfig Layer positioning. Defaults to `'lowestLabel'`.\n * @param label Custom label generator; when omitted, labels are derived from `budget` property.\n *\n * @example\n * ```typescript\n * const module = await GeometriesModule.get(map, reachableRangeGeometryConfig());\n * const result = await calculateReachableRanges([...]);\n * module.show(result); // labels and theme applied automatically\n * ```\n *\n * @group Geometries\n */\nexport const reachableRangeGeometryConfig = (\n palette: ColorPaletteOptions = 'fadedRainbow',\n theme?: GeometryTheme,\n beforeLayerConfig?: GeometryBeforeLayerConfig,\n label?: ReachableRangeLabelFn,\n): GeometriesModuleConfig => {\n const base = themedGeometryConfig(palette, beforeLayerConfig);\n const transformFeaturesForDisplay: NonNullable<GeometriesModuleConfig['transformFeaturesForDisplay']> = (result) =>\n prepareReachableRangesForDisplay(result, theme, label);\n\n // For filled/outline themes the base layer config is sufficient — no geometry pre-processing needed.\n if (theme !== 'inverted') {\n return { ...base, transformFeaturesForDisplay };\n }\n\n // Inverted theme: features are pre-processed into donut/border pairs. Border polygons carry\n // theme: 'filled'; donut fills have no theme and fall through to the default case.\n return {\n ...base,\n line: {\n ...base.line,\n opacity: ['case', ['==', ['get', 'theme'], 'filled'], FILLED_THEME_LINE_OPACITY, 0],\n },\n fill: {\n ...base.fill,\n opacity: ['case', ['==', ['get', 'theme'], 'filled'], 0, FILLED_THEME_FILL_OPACITY],\n },\n lineLabelConfig: {\n textOpacity: ['case', ['==', ['get', 'theme'], 'filled'], 1, 0],\n textOffset: ['case', ['==', ['get', 'theme'], 'filled'], ['literal', [0, -1]], ['literal', [0, 1]]],\n },\n transformFeaturesForDisplay,\n };\n};\n","/**\n * Visual theme for polygon display with {@link GeometriesModule}.\n *\n * - `'filled'` — Colored fill with thin border (default)\n * - `'outline'` — Transparent fill with thick colored border\n * - `'inverted'` — Colors the area **outside** the polygon (donut geometry)\n *\n * Set via {@link GeometriesModuleConfig.theme} at config level, or per-feature in\n * `properties.theme`.\n *\n * @group Geometries\n */\nexport type GeometryTheme = 'filled' | 'outline' | 'inverted';\n\n/**\n * All valid {@link GeometryTheme} values.\n *\n * @group Geometries\n */\nexport const geometryThemes = ['filled', 'outline', 'inverted'] as const;\n","import {\n AbstractMapModule,\n CombinedEvents,\n HILLSHADE_SOURCE_ID,\n ModuleEvents,\n StyleSourceWithLayers,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport type { HillshadeModuleConfig } from '.';\n\n/**\n * IDs of sources and layers for hillshade module.\n */\ntype HillshadeSourcesWithLayers = {\n hillshade: StyleSourceWithLayers;\n};\n\n/**\n * Map module for displaying terrain shading (hillshade).\n *\n * The HillshadeModule adds realistic terrain depth perception to the map by rendering\n * shadow and highlight effects based on elevation data. This enhances the 3D appearance\n * of mountainous and hilly terrain.\n *\n * @remarks\n * **Features:**\n * - Realistic terrain shading\n * - Uses vector tile elevation data\n * - Lightweight performance impact\n * - Seamlessly integrates with other map layers\n * - Toggle visibility on/off\n *\n * **Common Use Cases:**\n * - Outdoor recreation maps (hiking, skiing)\n * - Geographic/topographic applications\n * - Environmental visualization\n * - Landscape planning tools\n *\n * @example\n * ```typescript\n * // Create the module with hillshade visible\n * const hillshade = await HillshadeModule.getInstance(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * hillshade.setVisible(false);\n *\n * // Listen to events\n * hillshade.events.on('click', (feature, lngLat) => {\n * console.log('Clicked hillshade at:', lngLat);\n * });\n * ```\n *\n * @see [Hillshade Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/hillshade)\n *\n * @group Hillshade\n */\nexport class HillshadeModule extends AbstractMapModule<HillshadeSourcesWithLayers, HillshadeModuleConfig> {\n /**\n * Retrieves a HillshadeModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization and visibility.\n *\n * @returns A promise that resolves to the initialized HillshadeModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `ensureAddedToStyle`: Auto-add hillshade to style if missing\n *\n * **Style Requirement:**\n * - Hillshade must be included in the map style or added via `ensureAddedToStyle`\n * - Some styles may not support hillshade (e.g., satellite)\n *\n * @throws Error if hillshade source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map);\n * ```\n *\n * @example\n * Auto-add to style if missing:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map, {\n * visible: true\n * });\n * ```\n *\n * @example\n * Start hidden:\n * ```typescript\n * const hillshadeModule = await HillshadeModule.get(map, {\n * visible: false\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: HillshadeModuleConfig): Promise<HillshadeModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, HILLSHADE_SOURCE_ID, 'hillshade');\n return new HillshadeModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: HillshadeModuleConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const hillshadeSource = this.mapLibreMap.getSource(HILLSHADE_SOURCE_ID);\n if (!hillshadeSource) {\n throw notInTheStyle(`init ${HillshadeModule.name} with source ID ${HILLSHADE_SOURCE_ID}`);\n }\n return { hillshade: new StyleSourceWithLayers(this.mapLibreMap, hillshadeSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: HillshadeModuleConfig | undefined) {\n this._setVisible(config?.visible ?? false, false);\n return config;\n }\n\n /**\n * Sets the visibility of the hillshade layer.\n *\n * @param visible - `true` to show hillshade, `false` to hide it.\n *\n * @remarks\n * Changes are applied immediately if the map is ready.\n *\n * @example\n * ```typescript\n * hillshade.setVisible(true); // Show terrain shading\n * hillshade.setVisible(false); // Hide terrain shading\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.hillshade.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Checks if the hillshade layer is currently visible.\n *\n * @returns `true` if visible, `false` if hidden.\n *\n * @example\n * ```typescript\n * if (hillshade.isVisible()) {\n * console.log('Terrain shading is active');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.hillshade.isAnyLayerVisible();\n }\n\n /**\n * Gets the unified events interface for this module, covering both user interactions\n * and module lifecycle events.\n *\n * **User interaction events** (`click`, `contextmenu`, `hover`, `long-hover`):\n * ```typescript\n * hillshade.events.on('click', (feature, lngLat) => {\n * console.log('Clicked terrain at:', lngLat);\n * });\n * ```\n *\n * **Module lifecycle events** (`config-change`):\n * ```typescript\n * const unsub = hillshade.events.on('config-change', (config) => {\n * console.log('Hillshade config changed:', config);\n * });\n * unsub();\n * ```\n */\n get events(): CombinedEvents<import('maplibre-gl').MapGeoJSONFeature, HillshadeModuleConfig, never> {\n return new CombinedEvents(\n new UserEvents(this.tomtomMap._eventsProxy, this.sourcesWithLayers.hillshade, this.config?.events),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport type { MapOptions, StyleSpecification } from 'maplibre-gl';\nimport type { MapEventsConfig } from './mapEventsConfig';\n\n/**\n * Array of all available standard style identifiers.\n *\n * This constant provides the complete list of TomTom-hosted standard map styles\n * that can be used when initializing a map. It serves as the source of truth for\n * valid style IDs and is used to derive the {@link StandardStyleID} type.\n *\n * @remarks\n * Use this array when you need to:\n * - Iterate over all available styles (e.g., building a style picker UI)\n * - Validate if a style ID is a standard style\n * - Display available options to users\n *\n * The array is defined as `const` with `as const` assertion to ensure type safety\n * and prevent modifications at runtime.\n *\n * @example\n * ```typescript\n * import { standardStyleIDs } from '@tomtom-org/maps-sdk/map';\n *\n * // Build a dropdown menu with all standard styles\n * const styleSelector = document.getElementById('style-selector');\n * standardStyleIDs.forEach((styleId) => {\n * const option = new Option(styleId);\n * styleSelector.add(option);\n * });\n *\n * // Check if a style ID is valid\n * const isValidStyle = (id: string): boolean => {\n * return standardStyleIDs.includes(id as any);\n * };\n * ```\n *\n * @see {@link StandardStyleID} - The type derived from this array\n *\n * @group Map Style\n */\nexport const standardStyleIDs = [\n 'standardLight',\n 'standardDark',\n // TODO: driving styles not supported in Orbis for now\n 'drivingLight',\n 'drivingDark',\n 'monoLight',\n 'monoDark',\n 'satellite',\n] as const;\n\n/**\n * Identifier for a TomTom-hosted standard map style.\n *\n * Standard styles are officially maintained by TomTom and provide consistent,\n * professionally designed map appearances.\n *\n * @remarks\n * Available styles:\n * - `standardLight`: Default light theme with full detail\n * - `standardDark`: Dark theme for low-light environments\n * - `drivingLight`: Optimized for in-car navigation (light)\n * - `drivingDark`: Optimized for in-car navigation (dark)\n * - `monoLight`: Minimalist monochrome light theme\n * - `monoDark`: Minimalist monochrome dark theme\n * - `satellite`: Satellite imagery basemap\n *\n * @example\n * ```typescript\n * const styleId: StandardStyleID = 'standardLight';\n * ```\n *\n * @group Map Style\n */\nexport type StandardStyleID = (typeof standardStyleIDs)[number];\n\n/**\n * Configuration for a TomTom standard map style.\n *\n * Provides options to customize which modules are included and which style version to use.\n *\n * @example\n * ```typescript\n * // Standard style with all default modules\n * const style: StandardStyle = {\n * id: 'standardLight'\n * };\n *\n * // Exclude traffic modules\n * const styleNoTraffic: StandardStyle = {\n * id: 'standardLight',\n * include: ['hillshade'] // Only include hillshade, exclude traffic\n * };\n *\n * // Use specific style version\n * const versionedStyle: StandardStyle = {\n * id: 'standardDark',\n * version: '1.0.0'\n * };\n * ```\n *\n * @group Map Style\n */\nexport type StandardStyle = {\n /**\n * Standard style identifier.\n *\n * Determines the visual appearance of the map.\n */\n id?: StandardStyleID;\n /**\n * Modules to include when loading the style.\n * * Use this to selectively enable only needed modules for better performance.\n *\n * If not specified, all available modules are included by default.\n * If an empty array is provided, no optional modules will be included.\n *\n * @default All available modules\n *\n * @remarks\n * Available modules:\n * - `trafficIncidents`: Real-time traffic incidents (accidents, closures)\n * - `trafficFlow`: Real-time traffic flow visualization\n * - `hillshade`: Terrain elevation shading\n *\n * @example\n * ```typescript\n * // Include only traffic modules\n * include: ['trafficIncidents', 'trafficFlow']\n *\n * // Include only hillshade\n * include: ['hillshade']\n *\n * // Include no optional modules\n * include: []\n * ```\n */\n include?: StyleModule[];\n /**\n * Style version to load.\n *\n * Allows pinning to a specific style version for consistency.\n * If not specified, uses the latest SDK-supported version.\n *\n * @default Latest SDK-supported version\n *\n * @example\n * ```typescript\n * version: '1.0.0'\n * ```\n */\n version?: string;\n};\n\n/**\n * Configuration for a custom map style.\n *\n * Allows using your own map style either via URL or direct JSON specification.\n *\n * @remarks\n * Use custom styles for:\n * - Branded map appearances\n * - Specialized use cases (indoor maps, thematic maps)\n * - Integration with custom tile servers\n *\n * @example\n * ```typescript\n * // Load from URL\n * const urlStyle: CustomStyle = {\n * url: 'https://example.com/my-custom-style.json'\n * };\n *\n * // Direct JSON specification\n * const jsonStyle: CustomStyle = {\n * json: {\n * version: 8,\n * sources: { ... },\n * layers: [ ... ]\n * }\n * };\n * ```\n *\n * @group Map Style\n */\nexport type CustomStyle = {\n /**\n * URL to a MapLibre/Mapbox style JSON.\n *\n * The URL should not include the API key - it will be automatically added.\n * Mutually exclusive with the `json` property.\n *\n * @example\n * ```typescript\n * url: 'https://api.tomtom.com/style/1/style/my-custom-style'\n * ```\n */\n url?: string;\n /**\n * Direct style specification as JSON.\n *\n * Provide the complete MapLibre Style Specification object.\n * Mutually exclusive with the `url` property.\n *\n * @see [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/)\n *\n * @example\n * ```typescript\n * json: {\n * version: 8,\n * sources: {\n * 'my-source': { type: 'vector', url: '...' }\n * },\n * layers: [\n * { id: 'background', type: 'background', paint: { 'background-color': '#f0f0f0' } }\n * ]\n * }\n * ```\n */\n json?: StyleSpecification;\n};\n\n/**\n * Array of all available style modules.\n *\n * @group Map Style\n */\nexport const styleModules = ['trafficIncidents', 'trafficFlow', 'hillshade'] as const;\n\n/**\n * Optional map modules that can be included with a style.\n *\n * @remarks\n * - `trafficIncidents`: Shows real-time traffic incidents on the map\n * - `trafficFlow`: Shows real-time traffic flow with color-coded speeds\n * - `hillshade`: Adds terrain elevation shading for topographic context\n *\n * @group Map Style\n */\nexport type StyleModule = (typeof styleModules)[number];\n\n/**\n * Map style specification for initialization.\n *\n * Defines which map style to load and how it should be configured.\n * Supports standard TomTom styles or custom styles.\n *\n * @remarks\n * Three input formats:\n * 1. **Simple ID**: Just pass a standard style ID string\n * 2. **Standard style object**: Use a TomTom style with custom configuration\n * 3. **Custom style object**: Load your own style from URL or JSON\n *\n * @example\n * ```typescript\n * // 1. Simple standard style\n * style: 'standardLight'\n *\n * // 2. Standard style with configuration\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * }\n *\n * // 3. Custom style from URL\n * style: {\n * type: 'custom',\n * url: 'https://example.com/style.json'\n * }\n *\n * // 4. Custom style from JSON\n * style: {\n * type: 'custom',\n * json: { version: 8, sources: {...}, layers: [...] }\n * }\n * ```\n *\n * @group Map Style\n */\nexport type StyleInput = StandardStyleID | (StandardStyle & { type: 'standard' }) | (CustomStyle & { type: 'custom' });\n\n/**\n * MapLibre-specific options for advanced map configuration.\n *\n * Extends MapLibre GL JS MapOptions, excluding style and attribution control\n * which are handled by the TomTom SDK.\n *\n * @remarks\n * Includes options for:\n * - Initial viewport (center, zoom, bearing, pitch)\n * - Interaction controls (zoom, rotation, drag)\n * - Rendering options (antialiasing, terrain)\n * - Localization and accessibility\n *\n * @see [MapLibre MapOptions](https://maplibre.org/maplibre-gl-js/docs/API/type-aliases/MapOptions/)\n *\n * @group Map\n */\nexport type MapLibreOptions = Omit<MapOptions, 'style' | 'attributionControl'>;\n\n/**\n * Parameters for initializing a TomTom map instance.\n *\n * Combines global SDK configuration with map-specific settings like style, events, and MapLibre options.\n * All GlobalConfig properties (key, baseURL, etc.) are optional and will be merged from global configuration.\n * Only mapLibre.container is strictly required.\n *\n * @example\n * ```typescript\n * const mapParams: TomTomMapParams = {\n * key: 'your-api-key',\n * style: 'standardLight',\n * events: {\n * onClick: (event) => console.log('Map clicked', event)\n * },\n * mapLibre: {\n * container: 'map-container',\n * center: [4.9041, 52.3676],\n * zoom: 12\n * }\n * };\n * ```\n *\n * @group Map\n */\nexport type TomTomMapParams = Partial<GlobalConfig> & {\n /**\n * Map style to load.\n *\n * If not specified, defaults to 'standardLight'.\n *\n * @default 'standardLight'\n *\n * @example\n * ```typescript\n * // Use dark theme\n * style: 'standardDark'\n *\n * // Custom configuration\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow']\n * }\n * ```\n */\n style?: StyleInput;\n\n /**\n * Event handler configuration for map interactions.\n *\n * Define callbacks for various map events like clicks, hovers, and movements.\n *\n * @example\n * ```typescript\n * events: {\n * onClick: (event) => {\n * console.log('Clicked at', event.lngLat);\n * },\n * onMoveEnd: () => {\n * console.log('Map moved to', map.getCenter());\n * }\n * }\n * ```\n */\n events?: MapEventsConfig;\n\n /**\n * MapLibre-specific options for map configuration.\n *\n * Includes options for viewport settings, interaction controls, rendering options, and more.\n *\n * @example\n * ```typescript\n * mapLibre: {\n * container: 'map',\n * center: [4.9041, 52.3676],\n * zoom: 12,\n * pitch: 45,\n * bearing: -17.6,\n * antialias: true,\n * maxZoom: 18,\n * minZoom: 8\n * }\n * ```\n */\n mapLibre: MapLibreOptions;\n};\n\n/**\n * Complete TomTom map parameters after merging with global configuration.\n * This type represents the fully resolved configuration used internally by the SDK.\n *\n * @ignore\n */\nexport type InternalTomTomMapParams = GlobalConfig & TomTomMapParams;\n","type RGBColor = [number, number, number, number];\ntype HSLColor = [number, number, number, number];\n\nconst normalizeAngle = (angle: number): number => {\n const normalized = angle % 360;\n return normalized < 0 ? normalized + 360 : normalized;\n};\n\nconst hslToRgb = ([h, s, l, alpha]: HSLColor): RGBColor => {\n const hue = normalizeAngle(h);\n const sat = s / 100;\n const lig = l / 100;\n const f = (n: number) => {\n const k = (n + hue / 30) % 12;\n const a = sat * Math.min(lig, 1 - lig);\n return lig - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));\n };\n return [f(0), f(8), f(4), alpha];\n};\n\nconst clamp = (n: number, min: number, max: number): number => Math.min(Math.max(min, n), max);\n\nconst validateNumbers = (array: number[]): boolean => !array.some(Number.isNaN);\n\nconst parseHex = (hex: string): number => Number.parseInt(hex.padEnd(2, hex), 16) / 255;\n\nconst parseAlpha = (a: number, asPercentage: string | undefined): number => clamp(asPercentage ? a / 100 : a, 0, 1);\n\nconst parseHexColor = (input: string): RGBColor | undefined => {\n const hexRegexp = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/;\n if (!hexRegexp.test(input)) {\n return undefined;\n }\n const step = input.length < 6 ? 1 : 2;\n const r = parseHex(input.slice(1, 1 + step));\n const g = parseHex(input.slice(1 + step, 1 + step * 2));\n const b = parseHex(input.slice(1 + step * 2, 1 + step * 3));\n const a = parseHex(input.slice(1 + step * 3) || 'ff');\n return [r, g, b, a];\n};\n\nconst RGB_REGEXP =\n /^rgba?\\(\\s*([\\de.+-]+)(%)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)(%)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)(%)?(?:\\s*([,/])\\s*([\\de.+-]+)(%)?)?\\s*\\)$/;\n\nconst parseRgbColor = (input: string): RGBColor | undefined => {\n const rgbMatch = RGB_REGEXP.exec(input);\n if (!rgbMatch) {\n return undefined;\n }\n const [, r, rp, f1, g, gp, f2, b, bp, f3, a, ap] = rgbMatch;\n const argFormat = [f1 || ' ', f2 || ' ', f3].join('');\n if (argFormat !== ' ' && argFormat !== ' /' && argFormat !== ',,' && argFormat !== ',,,') {\n return undefined;\n }\n const valFormat = [rp, gp, bp].join('');\n const maxValue = valFormat === '%%%' ? 100 : valFormat === '' ? 255 : 0;\n if (!maxValue) {\n return undefined;\n }\n const rgba: RGBColor = [\n clamp(+r / maxValue, 0, 1),\n clamp(+g / maxValue, 0, 1),\n clamp(+b / maxValue, 0, 1),\n a ? parseAlpha(+a, ap) : 1,\n ];\n return validateNumbers(rgba) ? rgba : undefined;\n};\n\nconst HSL_REGEXP =\n /^hsla?\\(\\s*([\\de.+-]+)(?:deg)?(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)%(?:\\s+|\\s*(,)\\s*)([\\de.+-]+)%(?:\\s*([,/])\\s*([\\de.+-]+)(%)?)?\\s*\\)$/;\n\nconst parseHslColor = (input: string): RGBColor | undefined => {\n const hslMatch = HSL_REGEXP.exec(input);\n if (!hslMatch) {\n return undefined;\n }\n const [, h, f1, s, f2, l, f3, a, ap] = hslMatch;\n const argFormat = [f1 || ' ', f2 || ' ', f3].join('');\n if (argFormat !== ' ' && argFormat !== ' /' && argFormat !== ',,' && argFormat !== ',,,') {\n return undefined;\n }\n const hsla: HSLColor = [+h, clamp(+s, 0, 100), clamp(+l, 0, 100), a ? parseAlpha(+a, ap) : 1];\n return validateNumbers(hsla) ? hslToRgb(hsla) : undefined;\n};\n\nconst parseCssColor = (cssColor: string): RGBColor | undefined => {\n const input = cssColor.toLowerCase().trim();\n\n if (input === 'transparent') {\n return [0, 0, 0, 0];\n }\n\n const namedColor = namedColors[input];\n if (namedColor) {\n const [r, g, b] = namedColor;\n return [r / 255, g / 255, b / 255, 1];\n }\n\n if (input.startsWith('#')) {\n return parseHexColor(input);\n }\n\n if (input.startsWith('rgb')) {\n return parseRgbColor(input);\n }\n\n return parseHslColor(input);\n};\n\n/**\n * Darkens a CSS color by a given factor (0 = unchanged, 1 = black).\n * Accepts hex (#rgb, #rrggbb, #rrggbbaa), rgb(), hsl(), and CSS named colors.\n * Returns undefined if the color cannot be parsed.\n * @ignore\n */\nexport const darkenColor = (cssColor: string, darkenFactor: number): string | undefined => {\n const parsed = parseCssColor(cssColor);\n if (!parsed) {\n return undefined;\n }\n const brightnessScale = 1 - darkenFactor;\n const red = Math.round(parsed[0] * 255 * brightnessScale);\n const green = Math.round(parsed[1] * 255 * brightnessScale);\n const blue = Math.round(parsed[2] * 255 * brightnessScale);\n return `#${[red, green, blue].map((channel) => channel.toString(16).padStart(2, '0')).join('')}`;\n};\n\nconst namedColors: Record<string, [number, number, number]> = {\n aliceblue: [240, 248, 255],\n antiquewhite: [250, 235, 215],\n aqua: [0, 255, 255],\n aquamarine: [127, 255, 212],\n azure: [240, 255, 255],\n beige: [245, 245, 220],\n bisque: [255, 228, 196],\n black: [0, 0, 0],\n blanchedalmond: [255, 235, 205],\n blue: [0, 0, 255],\n blueviolet: [138, 43, 226],\n brown: [165, 42, 42],\n burlywood: [222, 184, 135],\n cadetblue: [95, 158, 160],\n chartreuse: [127, 255, 0],\n chocolate: [210, 105, 30],\n coral: [255, 127, 80],\n cornflowerblue: [100, 149, 237],\n cornsilk: [255, 248, 220],\n crimson: [220, 20, 60],\n cyan: [0, 255, 255],\n darkblue: [0, 0, 139],\n darkcyan: [0, 139, 139],\n darkgoldenrod: [184, 134, 11],\n darkgray: [169, 169, 169],\n darkgreen: [0, 100, 0],\n darkgrey: [169, 169, 169],\n darkkhaki: [189, 183, 107],\n darkmagenta: [139, 0, 139],\n darkolivegreen: [85, 107, 47],\n darkorange: [255, 140, 0],\n darkorchid: [153, 50, 204],\n darkred: [139, 0, 0],\n darksalmon: [233, 150, 122],\n darkseagreen: [143, 188, 143],\n darkslateblue: [72, 61, 139],\n darkslategray: [47, 79, 79],\n darkslategrey: [47, 79, 79],\n darkturquoise: [0, 206, 209],\n darkviolet: [148, 0, 211],\n deeppink: [255, 20, 147],\n deepskyblue: [0, 191, 255],\n dimgray: [105, 105, 105],\n dimgrey: [105, 105, 105],\n dodgerblue: [30, 144, 255],\n firebrick: [178, 34, 34],\n floralwhite: [255, 250, 240],\n forestgreen: [34, 139, 34],\n fuchsia: [255, 0, 255],\n gainsboro: [220, 220, 220],\n ghostwhite: [248, 248, 255],\n gold: [255, 215, 0],\n goldenrod: [218, 165, 32],\n gray: [128, 128, 128],\n green: [0, 128, 0],\n greenyellow: [173, 255, 47],\n grey: [128, 128, 128],\n honeydew: [240, 255, 240],\n hotpink: [255, 105, 180],\n indianred: [205, 92, 92],\n indigo: [75, 0, 130],\n ivory: [255, 255, 240],\n khaki: [240, 230, 140],\n lavender: [230, 230, 250],\n lavenderblush: [255, 240, 245],\n lawngreen: [124, 252, 0],\n lemonchiffon: [255, 250, 205],\n lightblue: [173, 216, 230],\n lightcoral: [240, 128, 128],\n lightcyan: [224, 255, 255],\n lightgoldenrodyellow: [250, 250, 210],\n lightgray: [211, 211, 211],\n lightgreen: [144, 238, 144],\n lightgrey: [211, 211, 211],\n lightpink: [255, 182, 193],\n lightsalmon: [255, 160, 122],\n lightseagreen: [32, 178, 170],\n lightskyblue: [135, 206, 250],\n lightslategray: [119, 136, 153],\n lightslategrey: [119, 136, 153],\n lightsteelblue: [176, 196, 222],\n lightyellow: [255, 255, 224],\n lime: [0, 255, 0],\n limegreen: [50, 205, 50],\n linen: [250, 240, 230],\n magenta: [255, 0, 255],\n maroon: [128, 0, 0],\n mediumaquamarine: [102, 205, 170],\n mediumblue: [0, 0, 205],\n mediumorchid: [186, 85, 211],\n mediumpurple: [147, 112, 219],\n mediumseagreen: [60, 179, 113],\n mediumslateblue: [123, 104, 238],\n mediumspringgreen: [0, 250, 154],\n mediumturquoise: [72, 209, 204],\n mediumvioletred: [199, 21, 133],\n midnightblue: [25, 25, 112],\n mintcream: [245, 255, 250],\n mistyrose: [255, 228, 225],\n moccasin: [255, 228, 181],\n navajowhite: [255, 222, 173],\n navy: [0, 0, 128],\n oldlace: [253, 245, 230],\n olive: [128, 128, 0],\n olivedrab: [107, 142, 35],\n orange: [255, 165, 0],\n orangered: [255, 69, 0],\n orchid: [218, 112, 214],\n palegoldenrod: [238, 232, 170],\n palegreen: [152, 251, 152],\n paleturquoise: [175, 238, 238],\n palevioletred: [219, 112, 147],\n papayawhip: [255, 239, 213],\n peachpuff: [255, 218, 185],\n peru: [205, 133, 63],\n pink: [255, 192, 203],\n plum: [221, 160, 221],\n powderblue: [176, 224, 230],\n purple: [128, 0, 128],\n rebeccapurple: [102, 51, 153],\n red: [255, 0, 0],\n rosybrown: [188, 143, 143],\n royalblue: [65, 105, 225],\n saddlebrown: [139, 69, 19],\n salmon: [250, 128, 114],\n sandybrown: [244, 164, 96],\n seagreen: [46, 139, 87],\n seashell: [255, 245, 238],\n sienna: [160, 82, 45],\n silver: [192, 192, 192],\n skyblue: [135, 206, 235],\n slateblue: [106, 90, 205],\n slategray: [112, 128, 144],\n slategrey: [112, 128, 144],\n snow: [255, 250, 250],\n springgreen: [0, 255, 127],\n steelblue: [70, 130, 180],\n tan: [210, 180, 140],\n teal: [0, 128, 128],\n thistle: [216, 191, 216],\n tomato: [255, 99, 71],\n turquoise: [64, 224, 208],\n violet: [238, 130, 238],\n wheat: [245, 222, 179],\n white: [255, 255, 255],\n whitesmoke: [245, 245, 245],\n yellow: [255, 255, 0],\n yellowgreen: [154, 205, 50],\n};\n","import type { ExpressionSpecification } from 'maplibre-gl';\nimport type { RouteWaypointSize, RouteWidth } from '../types/routeModuleConfig';\n\n/**\n * Main route line foreground color.\n * @ignore\n */\nexport const ROUTE_LINE_FOREGROUND_COLOR = '#36A8F0';\n\n/**\n * Main route outline color.\n * @ignore\n */\nexport const ROUTE_LINE_OUTLINE_COLOR = '#105287';\n\n/**\n * Deselected route line foreground color.\n * @ignore\n */\nexport const DESELECTED_FOREGROUND_COLOR = '#ABAFB3';\n\n/**\n * Deselected route line outline color.\n * @ignore\n */\nexport const DESELECTED_OUTLINE_COLOR = '#3C4956';\n\n/**\n * @ignore\n */\nexport const DESELECTED_SECONDARY_COLOR = '#727C85';\n\n/**\n * Main route line width based on zoom level.\n * @ignore\n */\nexport const ROUTE_LINE_FOREGROUND_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 1,\n 3,\n 5,\n 4,\n 10,\n 7,\n 18,\n 10,\n];\n\n/**\n * Used for showing/hiding layer depending on layer being part of selected route or not.\n *\n * @remarks\n * Add this to layers that depend on whether they are part of the selected route or not.\n *\n * @example:\n * filter: SELECTED_ROUTE_FILTER\n\n * @group Routing\n */\nexport const SELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'selected'];\n\n/**\n * Used for showing/hiding layer depending on layer being part of deselected route or not.\n *\n * @remarks\n * Add this to layers that depend on whether they are part of the deselected route or not.\n *\n * @example:\n * filter: DESELECTED_ROUTE_FILTER\n\n * @group Routing\n */\nexport const DESELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'deselected'];\n\n/**\n * Main route line outline width (medium) based on zoom level.\n * @ignore\n */\nexport const ROUTE_LINE_OUTLINE_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['linear'],\n ['zoom'],\n 1,\n 5,\n 5,\n 6,\n 10,\n 10,\n 18,\n 14,\n];\n\nconst LINE_FOREGROUND_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 2, 5, 3, 10, 5, 18, 7],\n m: ROUTE_LINE_FOREGROUND_WIDTH,\n l: ['interpolate', ['linear'], ['zoom'], 1, 5, 5, 6, 10, 11, 18, 15],\n};\n\nconst LINE_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 4, 5, 5, 10, 8, 18, 11],\n m: ROUTE_LINE_OUTLINE_WIDTH,\n l: ['interpolate', ['linear'], ['zoom'], 1, 7, 5, 9, 10, 14, 18, 19],\n};\n\nconst WAYPOINT_ICON_SIZES: Record<RouteWaypointSize, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 8, 0.45, 22, 0.6],\n m: ['interpolate', ['linear'], ['zoom'], 8, 0.6, 22, 0.8],\n l: ['interpolate', ['linear'], ['zoom'], 8, 0.75, 22, 1],\n};\n\n/**\n * Returns the foreground line width expression for the given size preset.\n * @ignore\n */\nexport const getLineForegroundWidth = (size?: RouteWidth): ExpressionSpecification =>\n LINE_FOREGROUND_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the outline line width expression for the given size preset.\n * @ignore\n */\nexport const getLineOutlineWidth = (size?: RouteWidth): ExpressionSpecification => LINE_OUTLINE_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the waypoint icon size expression for the given size preset.\n * @ignore\n */\nexport const getWaypointIconSize = (size?: RouteWaypointSize): ExpressionSpecification =>\n WAYPOINT_ICON_SIZES[size ?? 'm'];\n\nconst TOLL_ROAD_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 1, 8, 5, 9, 10, 12, 18, 15],\n m: ['interpolate', ['linear'], ['zoom'], 1, 9, 5, 11, 10, 15, 18, 20],\n l: ['interpolate', ['linear'], ['zoom'], 1, 12, 5, 14, 10, 19, 18, 24],\n};\n\n/**\n * Returns the toll road outline width expression for the given size preset.\n * @ignore\n */\nexport const getTollRoadOutlineWidth = (size?: RouteWidth): ExpressionSpecification =>\n TOLL_ROAD_OUTLINE_WIDTHS[size ?? 'm'];\n\nconst INSTRUCTION_LINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 16, 8, 22, 12],\n m: ['interpolate', ['linear'], ['zoom'], 16, 12, 22, 17],\n l: ['interpolate', ['linear'], ['zoom'], 16, 17, 22, 24],\n};\n\nconst INSTRUCTION_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: ['interpolate', ['linear'], ['zoom'], 16, 10, 22, 14],\n m: ['interpolate', ['linear'], ['zoom'], 16, 14, 22, 20],\n l: ['interpolate', ['linear'], ['zoom'], 16, 20, 22, 28],\n};\n\n/**\n * Returns the instruction line width expression for the given size preset.\n * @ignore\n */\nexport const getInstructionLineWidth = (size?: RouteWidth): ExpressionSpecification =>\n INSTRUCTION_LINE_WIDTHS[size ?? 'm'];\n\n/**\n * Returns the instruction outline width expression for the given size preset.\n * @ignore\n */\nexport const getInstructionOutlineWidth = (size?: RouteWidth): ExpressionSpecification =>\n INSTRUCTION_OUTLINE_WIDTHS[size ?? 'm'];\n\n/**\n * @ignore\n */\nexport const MAJOR_DELAY_COLOR = '#AD0000';\n/**\n * @ignore\n */\nexport const MODERATE_DELAY_COLOR = '#FB2D09';\n/**\n * @ignore\n */\nexport const MINOR_DELAY_COLOR = '#FFC105';\n/**\n * @ignore\n */\nexport const MINOR_DELAY_LABEL_COLOR = '#f58240';\n/**\n * @ignore\n */\nexport const UNKNOWN_DELAY_COLOR = '#000000';\n/**\n * @ignore\n */\nexport const UNKNOWN_DELAY_BG_COLOR = '#C7D2D8';\n/**\n * Dashed-line color for `magnitudeOfDelay === 'unknown'` (rendered as a red dashed overlay).\n * @ignore\n */\nexport const UNKNOWN_DELAY_DASH_COLOR = 'rgba(190, 39, 27, 1)';\n/**\n * Dashed-line color for `magnitudeOfDelay === 'indefinite'` (rendered as a gray dashed overlay).\n * @ignore\n */\nexport const INDEFINITE_DELAY_DASH_COLOR = 'rgba(137, 150, 168, 1)';\n","import type {\n DataDrivenPropertyValueSpecification,\n FormattedSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport { ChargingStopsConfig, ChargingStopTextConfig } from '../types/routeModuleConfig';\nimport { SELECTED_ROUTE_FILTER } from './shared';\n\nconst chargingStopTextField = (\n config: ChargingStopTextConfig | undefined,\n): DataDrivenPropertyValueSpecification<FormattedSpecification> => {\n if (config?.visible === false) {\n return '';\n }\n\n return (\n config?.title ?? [\n 'format',\n ['get', 'title'],\n '\\n',\n ['get', 'chargingPower'],\n ' • ',\n ['get', 'chargingDuration'],\n ]\n );\n};\n\n/**\n * @ignore\n * @see toDisplayChargingStops\n */\nexport const chargingStopSymbol = (\n config: ChargingStopsConfig | undefined,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n return {\n ...pinLayerBaseSpec,\n filter: SELECTED_ROUTE_FILTER,\n minzoom: 4,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'text-field': chargingStopTextField(config?.text),\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n },\n };\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getInstructionLineWidth, getInstructionOutlineWidth, SELECTED_ROUTE_FILTER } from './shared';\n\nconst commonProps = {\n filter: SELECTED_ROUTE_FILTER,\n minzoom: 16,\n};\n\nconst commonLineLayout: LayerSpecTemplate<LineLayerSpecification>['layout'] = {\n 'line-cap': 'round',\n};\n\n/**\n * @ignore\n */\nexport const instructionOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionOutlineWidth(routeWidth),\n 'line-color': 'grey',\n },\n});\n\n/**\n * @ignore\n */\nexport const instructionLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionLineWidth(routeWidth),\n 'line-color': 'white',\n },\n});\n\n/**\n * @ignore\n */\nexport const INSTRUCTION_ARROW_IMAGE_ID = 'instruction-arrow';\n\n/**\n * @ignore\n */\nexport const instructionArrow: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...commonProps,\n type: 'symbol',\n layout: {\n 'icon-allow-overlap': true,\n 'icon-image': INSTRUCTION_ARROW_IMAGE_ID, // Will be updated with instance suffix in config\n 'icon-rotation-alignment': 'map',\n 'icon-rotate': ['get', 'lastPointBearingDegrees'],\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 16, 1, 22, 2],\n },\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getLineForegroundWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeFerriesLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': '#6dc4ed',\n },\n});\n\n/**\n * @ignore\n */\nexport const routeFerriesSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 6,\n // zoom where the map POI naturally appears:\n maxzoom: 16.5,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-image': 'poi-ferry_terminal',\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 6, 0.8, 16.5, 1],\n // helps smooth the transition from along-route to map-poi, which also has a label in it:\n 'icon-ignore-placement': true,\n },\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n DESELECTED_FOREGROUND_COLOR,\n DESELECTED_OUTLINE_COLOR,\n DESELECTED_ROUTE_FILTER,\n getLineForegroundWidth,\n getLineOutlineWidth,\n ROUTE_LINE_FOREGROUND_COLOR,\n ROUTE_LINE_OUTLINE_COLOR,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const routeLineBaseTemplate: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n 'line-sort-key': ['get', 'index'],\n },\n};\n\n/**\n * @ignore\n */\nexport const routeDeselectedOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_OUTLINE_COLOR,\n 'line-width': getLineOutlineWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeDeselectedLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeOutline = (\n routeWidth?: RouteWidth,\n outlineColor?: string,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: SELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': outlineColor ?? ROUTE_LINE_OUTLINE_COLOR,\n 'line-width': getLineOutlineWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeMainLine = (\n routeWidth?: RouteWidth,\n routeColor?: string,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: SELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': routeColor ?? ROUTE_LINE_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeLineArrows: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line',\n 'icon-image': 'roads-arrow-white',\n // The current arrow icon seems to point backwards otherwise. Check with caution!\n 'icon-rotate': 180,\n },\n};\n\n/**\n * @ignore\n */\nexport const SELECTED_SUMMARY_POPUP_IMAGE_ID = 'selected-route-summary-popup';\n/**\n * @ignore\n */\nexport const DESELECTED_SUMMARY_POPUP_IMAGE_ID = 'deselected-route-summary-popup';\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getTollRoadOutlineWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTollRoadsOutline = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-width': getTollRoadOutlineWidth(routeWidth),\n 'line-color': '#BEBFFA',\n },\n});\n\n/**\n * @ignore\n */\nexport const routeTollRoadsSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 4,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-image': 'poi-toll_plaza',\n 'icon-size': ['interpolate', ['linear'], ['zoom'], 4, 0.8, 16.5, 1],\n },\n};\n","import type { TrafficIncidentCategory } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport { INDEFINITE_DELAY_DASH_COLOR, UNKNOWN_DELAY_DASH_COLOR } from '../../routing/layers/shared';\n\n/**\n * Maps a traffic incident category to the sprite-image ID used by the map style.\n * Returns `null` for categories that have no dedicated icon (e.g. `jam`, which\n * uses a dynamic `traffic-jam-<magnitude>-<size>` image instead).\n *\n * @ignore\n */\nexport const incidentIconID = (category: TrafficIncidentCategory): string | null => {\n switch (category) {\n case 'accident':\n return 'traffic-incidents-accident';\n case 'roadworks':\n return 'traffic-incidents-roadworks';\n case 'road-closed':\n return 'traffic-incidents-road_closed';\n case 'lane-closed':\n case 'narrow-lanes':\n return 'traffic-incidents-lane_closed';\n case 'danger':\n case 'animals-on-road':\n return 'traffic-incidents-danger';\n case 'broken-down-vehicle':\n return 'traffic-incidents-broken_down_vehicle';\n case 'wind':\n return 'traffic-incidents-wind';\n case 'fog':\n return 'traffic-incidents-fog';\n case 'rain':\n return 'traffic-incidents-rain';\n case 'frost':\n return 'traffic-incidents-frost';\n case 'flooding':\n return 'traffic-incidents-flooding';\n default:\n return null;\n }\n};\n\n/**\n * MapLibre filter selecting incident features whose `magnitudeOfDelay` should be\n * drawn with the dashed overlay (i.e. no concrete severity). Used by the routing\n * module's traffic-section overlay — the canonical-style\n * {@link TrafficIncidentOverlayModule} renders unknown/indefinite magnitudes with\n * `line-pattern` sprites instead of dashed overlays.\n *\n * @ignore\n */\nexport const severityDashedFilter: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['unknown', 'indefinite']],\n];\n\n/**\n * MapLibre paint expression for the dashed overlay's color. `'unknown'` is\n * rendered red (indicating reported-but-unconfirmed severity); `'indefinite'`\n * is rendered gray (indicating open-ended delay with no end time).\n *\n * @ignore\n */\nexport const severityLineDashColor: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n UNKNOWN_DELAY_DASH_COLOR,\n // other (indefinite):\n INDEFINITE_DELAY_DASH_COLOR,\n];\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { severityDashedFilter, severityLineDashColor } from '../../traffic/util/trafficIncidentStyle';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n getLineForegroundWidth,\n MAJOR_DELAY_COLOR,\n MINOR_DELAY_COLOR,\n MINOR_DELAY_LABEL_COLOR,\n MODERATE_DELAY_COLOR,\n SELECTED_ROUTE_FILTER,\n UNKNOWN_DELAY_BG_COLOR,\n UNKNOWN_DELAY_COLOR,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const magnitudeOfDelayTextColor: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'minor',\n MINOR_DELAY_LABEL_COLOR,\n 'moderate',\n MODERATE_DELAY_COLOR,\n 'major',\n MAJOR_DELAY_COLOR,\n 'indefinite',\n '#666666',\n // other\n UNKNOWN_DELAY_COLOR,\n];\n\n/**\n * @ignore\n */\nexport const routeIncidentsBGLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n layout: { 'line-cap': 'round' },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'minor',\n MINOR_DELAY_COLOR,\n 'moderate',\n MODERATE_DELAY_COLOR,\n 'major',\n MAJOR_DELAY_COLOR,\n // other\n UNKNOWN_DELAY_BG_COLOR,\n ],\n },\n});\n\n/**\n * @ignore\n */\nexport const routeIncidentsDashedLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n filter: severityDashedFilter,\n layout: { 'line-join': 'round' },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': severityLineDashColor,\n 'line-dasharray': [1.5, 1],\n },\n});\n\nconst routeIncidentsSymbolBase: LayerSpecTemplate<SymbolLayerSpecification> = {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: 6,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-ignore-placement': true,\n },\n};\n\n/**\n * @ignore\n */\nexport const routeIncidentsJamSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...routeIncidentsSymbolBase,\n filter: ['all', ['has', 'jamIconID'], routeIncidentsSymbolBase.filter as ExpressionSpecification],\n layout: {\n ...routeIncidentsSymbolBase.layout,\n 'icon-image': ['get', 'jamIconID'],\n 'icon-anchor': 'bottom-left',\n 'text-anchor': 'bottom-left',\n // Jam symbols have delay labels in them:\n 'text-field': ['get', 'title'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-offset': [3.9, -1.4],\n 'text-size': 13,\n },\n paint: {\n ...routeIncidentsSymbolBase.paint,\n 'text-color': magnitudeOfDelayTextColor,\n 'text-halo-color': '#FFFFFF',\n 'text-halo-width': 1,\n },\n};\n\n/**\n * @ignore\n */\nexport const routeIncidentsCauseSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...routeIncidentsSymbolBase,\n filter: ['all', ['has', 'causeIconID'], routeIncidentsSymbolBase.filter as ExpressionSpecification],\n layout: {\n ...routeIncidentsSymbolBase.layout,\n 'icon-image': ['get', 'causeIconID'],\n 'icon-anchor': 'bottom-right',\n // Cause symbols have no label in them.\n },\n paint: { ...routeIncidentsSymbolBase.paint },\n};\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport { getLineForegroundWidth, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTunnelsLine = (routeWidth?: RouteWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-color': '#000000',\n 'line-opacity': 0.3,\n },\n});\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport type { RouteWidth } from '../types/routeModuleConfig';\nimport {\n getLineForegroundWidth,\n ROUTE_LINE_FOREGROUND_COLOR,\n ROUTE_LINE_OUTLINE_COLOR,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedBackgroundLine = (\n routeWidth?: RouteWidth,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n },\n paint: {\n 'line-color': ROUTE_LINE_OUTLINE_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedDottedLine = (\n routeWidth?: RouteWidth,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: {\n 'line-join': 'round',\n 'line-cap': 'round',\n },\n paint: {\n 'line-color': ROUTE_LINE_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(routeWidth),\n 'line-dasharray': [0, 1.5],\n },\n});\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT, MAP_MEDIUM_FONT } from '../../shared/layers/commonLayerProps';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './routeMainLineLayers';\nimport { magnitudeOfDelayTextColor } from './routeTrafficSectionLayers';\nimport { DESELECTED_SECONDARY_COLOR, SELECTED_ROUTE_FILTER } from './shared';\n\n/**\n * @ignore\n */\nexport const TRAFFIC_CLEAR_IMAGE_ID = 'traffic-clear';\n/**\n * @ignore\n */\nexport const TRAFFIC_MAJOR_IMAGE_ID = 'traffic-major';\n/**\n * @ignore\n */\nexport const TRAFFIC_MODERATE_IMAGE_ID = 'traffic-moderate';\n/**\n * @ignore\n */\nexport const TRAFFIC_MINOR_IMAGE_ID = 'traffic-minor';\n\nconst hasFormattedTraffic: ExpressionSpecification = ['has', 'formattedTraffic'];\n\n/**\n * Builds the summary bubble symbol layer specification with instance-specific image IDs.\n * @param instanceIndex - Optional instance index for supporting multiple RoutingModule instances\n * @ignore\n */\nexport const buildSummaryBubbleSymbolPoint = (instanceIndex?: number): LayerSpecTemplate<SymbolLayerSpecification> => {\n const selectedImageID =\n instanceIndex !== undefined\n ? suffixNumber(SELECTED_SUMMARY_POPUP_IMAGE_ID, instanceIndex)\n : SELECTED_SUMMARY_POPUP_IMAGE_ID;\n const deselectedImageID =\n instanceIndex !== undefined\n ? suffixNumber(DESELECTED_SUMMARY_POPUP_IMAGE_ID, instanceIndex)\n : DESELECTED_SUMMARY_POPUP_IMAGE_ID;\n const trafficClearID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_CLEAR_IMAGE_ID, instanceIndex) : TRAFFIC_CLEAR_IMAGE_ID;\n const trafficMajorID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_MAJOR_IMAGE_ID, instanceIndex) : TRAFFIC_MAJOR_IMAGE_ID;\n const trafficModerateID =\n instanceIndex !== undefined\n ? suffixNumber(TRAFFIC_MODERATE_IMAGE_ID, instanceIndex)\n : TRAFFIC_MODERATE_IMAGE_ID;\n const trafficMinorID =\n instanceIndex !== undefined ? suffixNumber(TRAFFIC_MINOR_IMAGE_ID, instanceIndex) : TRAFFIC_MINOR_IMAGE_ID;\n\n return {\n type: 'symbol',\n layout: {\n 'icon-image': ['case', SELECTED_ROUTE_FILTER, selectedImageID, deselectedImageID],\n 'symbol-placement': 'point',\n 'icon-rotation-alignment': 'viewport',\n 'text-rotation-alignment': 'viewport',\n 'symbol-sort-key': ['case', SELECTED_ROUTE_FILTER, 0, 1],\n 'icon-text-fit': 'both',\n 'icon-text-fit-padding': [10, 5, 5, 10],\n 'text-font': [MAP_MEDIUM_FONT],\n 'text-size': 13,\n 'icon-padding': 0,\n 'text-justify': 'left',\n 'text-line-height': 1.5,\n 'text-field': [\n 'format',\n ['get', 'formattedDuration'],\n {\n 'text-font': ['literal', [MAP_BOLD_FONT]],\n 'text-color': ['case', SELECTED_ROUTE_FILTER, 'black', DESELECTED_SECONDARY_COLOR],\n },\n ['concat', '\\t\\t', ['get', 'formattedDistance']],\n { 'text-color': DESELECTED_SECONDARY_COLOR },\n ['case', hasFormattedTraffic, '\\n', ''],\n {},\n [\n 'image',\n [\n 'case',\n hasFormattedTraffic,\n [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'major',\n trafficMajorID,\n 'moderate',\n trafficModerateID,\n 'minor',\n trafficMinorID,\n trafficClearID,\n ],\n '',\n ],\n ],\n {},\n ['case', hasFormattedTraffic, ['concat', ' ', ['get', 'formattedTraffic']], ''],\n {\n 'text-font': ['literal', [MAP_BOLD_FONT]],\n 'text-color': magnitudeOfDelayTextColor,\n },\n ],\n },\n paint: {\n 'icon-translate': [0, -35],\n 'text-translate': [0, -35],\n },\n };\n};\n\n/**\n * Default summary bubble symbol layer (without instance suffix)\n * @ignore\n */\nexport const summaryBubbleSymbolPoint: LayerSpecTemplate<SymbolLayerSpecification> = buildSummaryBubbleSymbolPoint();\n","import type { PlaceDisplayProps } from '../../places';\n\n/**\n * @group Routing\n */\nexport const START_INDEX = 'start';\n/**\n * @group Routing\n */\nexport const MIDDLE_INDEX = 'middle';\n/**\n * @group Routing\n */\nexport const FINISH_INDEX = 'finish';\n/**\n * @group Routing\n */\nexport type WaypointIndexType = typeof START_INDEX | typeof MIDDLE_INDEX | typeof FINISH_INDEX;\n\n/**\n * Display properties for a waypoint marker on the map.\n *\n * Extends location display properties with waypoint-specific information\n * including position in the route and stop numbering.\n *\n * @remarks\n * Waypoints are displayed differently based on their position:\n * - **Start**: Origin marker (often \"A\" or green pin)\n * - **Middle**: Numbered stop markers (1, 2, 3, etc.)\n * - **Finish**: Destination marker (often \"B\" or red pin)\n *\n * @example\n * ```typescript\n * // Start waypoint\n * const start: WaypointDisplayProps = {\n * id: 'waypoint-0',\n * iconID: 'waypoint-start',\n * index: 0,\n * indexType: 'start',\n * title: 'Amsterdam Central Station'\n * };\n *\n * // Intermediate stop\n * const stop: WaypointDisplayProps = {\n * id: 'waypoint-1',\n * iconID: 'waypoint-stop',\n * index: 1,\n * indexType: 'middle',\n * stopDisplayIndex: 1,\n * title: 'Schiphol Airport'\n * };\n *\n * // Destination\n * const finish: WaypointDisplayProps = {\n * id: 'waypoint-2',\n * iconID: 'waypoint-finish',\n * index: 2,\n * indexType: 'finish',\n * title: 'Rotterdam Central Station'\n * };\n * ```\n *\n * @group Routing\n */\nexport type WaypointDisplayProps = PlaceDisplayProps & {\n /**\n * The index of the waypoint in relation to the other waypoints.\n *\n * @remarks\n * Zero-based index of this waypoint in the complete waypoints array,\n * including start, all stops, and finish.\n *\n * @example\n * ```typescript\n * index: 0 // First waypoint (start)\n * index: 1 // Second waypoint (first stop or finish)\n * index: 2 // Third waypoint\n * ```\n */\n index: number;\n\n /**\n * The type associated to the index, describing how the waypoint sits in the list of waypoints.\n *\n * @remarks\n * Determines the waypoint's role and visual representation:\n * - `start`: Origin point\n * - `middle`: Intermediate stop\n * - `finish`: Final destination\n *\n * This affects icon selection and labeling behavior.\n */\n indexType: WaypointIndexType;\n\n /**\n * The stop index to be displayed.\n *\n * @remarks\n * Stops are the non-soft waypoints added between origin and destination,\n * numbered starting from 1. Only present for middle waypoints.\n *\n * **Display Behavior:**\n * - Start waypoint: undefined\n * - First stop: 1\n * - Second stop: 2\n * - Finish waypoint: undefined\n *\n * Used for displaying stop numbers (e.g., \"Stop 1\", \"Stop 2\") in the UI.\n *\n * @example\n * ```typescript\n * stopDisplayIndex: 1 // First intermediate stop\n * stopDisplayIndex: 2 // Second intermediate stop\n * stopDisplayIndex: undefined // Start or finish waypoint\n * ```\n */\n stopDisplayIndex?: number;\n};\n\n/**\n * @ignore\n */\nexport const INDEX_TYPE = 'indexType';\n\n/**\n * @ignore\n */\nexport const STOP_DISPLAY_INDEX = 'stopDisplayIndex';\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport {\n ICON_ID,\n pinIconBaseLayout,\n pinIconBasePaint,\n pinTextBaseLayout,\n pinTextBasePaint,\n} from '../../shared/layers/symbolLayers';\nimport { INDEX_TYPE, MIDDLE_INDEX, STOP_DISPLAY_INDEX } from '../types/waypointDisplayProps';\n\n/**\n * Waypoint start image ID.\n * @group Routing\n */\nexport const WAYPOINT_START_IMAGE_ID = 'waypointStart';\n/**\n * Waypoint stop image ID.\n *\n * @remarks\n * Used for intermediate waypoints in a route.\n *\n * @group Routing.\n */\nexport const WAYPOINT_STOP_IMAGE_ID = 'waypointStop';\n/**\n * Soft waypoint image ID.\n *\n * @remarks\n * This is currently unsupported in Orbis maps.\n *\n * @group Routing\n */\nexport const WAYPOINT_SOFT_IMAGE_ID = 'waypointSoft';\n/**\n * Waypoint finish image ID.\n *\n * @group Routing\n */\nexport const WAYPOINT_FINISH_IMAGE_ID = 'waypointFinish';\n\nconst isSoftWaypoint: ExpressionSpecification = [\n 'all',\n ['==', ['get', INDEX_TYPE], MIDDLE_INDEX],\n ['!', ['has', STOP_DISPLAY_INDEX]],\n];\n\nconst pinIndexLabelPaint: SymbolLayerSpecification['paint'] = {\n 'text-color': '#ffffff',\n};\n\nconst pinIndexLabelLayout: SymbolLayerSpecification['layout'] = {\n // optional centered text to indicate stop numbers (1, 2 ...):\n 'text-field': ['get', STOP_DISPLAY_INDEX],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': ['interpolate', ['linear'], ['zoom'], 13, 14, 18, 16],\n 'text-offset': [0, -1.6],\n // pin vs circle:\n 'icon-anchor': [\n 'case',\n isSoftWaypoint,\n 'center',\n // else\n 'bottom',\n ],\n 'text-allow-overlap': true,\n};\n\n// TODO: reusable display of pins with indexes, not just waypoints\n/**\n * @ignore\n */\nexport const waypointSymbols: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n paint: {\n ...pinIconBasePaint,\n ...pinIndexLabelPaint,\n },\n layout: {\n ...pinIconBaseLayout,\n ...pinIndexLabelLayout,\n 'symbol-sort-key': [\n 'case',\n ['==', ['get', ICON_ID], WAYPOINT_SOFT_IMAGE_ID],\n 0,\n ['abs', ['-', ['get', 'index'], 1000]],\n ],\n },\n};\n\n/**\n * @ignore\n */\nexport const waypointLabels: LayerSpecTemplate<SymbolLayerSpecification> = {\n type: 'symbol',\n paint: {\n ...pinTextBasePaint,\n 'text-color': 'black',\n 'text-halo-width': 1.5,\n 'text-halo-color': '#ffffff',\n },\n layout: {\n ...pinTextBaseLayout,\n 'text-anchor': 'top',\n 'text-offset': [0, 0.4],\n },\n};\n","import { mapStyleLayerIDs } from '../../shared';\nimport { darkenColor } from '../../utils/colorUtils';\nimport type { RouteLayersConfig, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport { chargingStopSymbol } from './chargingStopLayers';\nimport { instructionArrow, instructionLine, instructionOutline } from './guidanceLayers';\nimport { routeFerriesLine, routeFerriesSymbol } from './routeFerrySectionLayers';\nimport {\n routeDeselectedLine,\n routeDeselectedOutline,\n routeLineArrows,\n routeMainLine,\n routeOutline,\n} from './routeMainLineLayers';\nimport { routeTollRoadsOutline, routeTollRoadsSymbol } from './routeTollRoadLayers';\nimport {\n routeIncidentsBGLine,\n routeIncidentsCauseSymbol,\n routeIncidentsDashedLine,\n routeIncidentsJamSymbol,\n} from './routeTrafficSectionLayers';\nimport { routeTunnelsLine } from './routeTunnelSectionLayers';\nimport { routeVehicleRestrictedBackgroundLine, routeVehicleRestrictedDottedLine } from './routeVehicleRestrictedLayers';\nimport { getWaypointIconSize } from './shared';\nimport { buildSummaryBubbleSymbolPoint, summaryBubbleSymbolPoint } from './summaryBubbleLayers';\nimport { waypointLabels, waypointSymbols } from './waypointLayers';\n\n/**\n * Helper function to add layer ID prefix to beforeID references, but only for internal routing layer IDs\n * @ignore\n */\nconst prefixBeforeID = (beforeID: string | undefined, layerIDPrefix: string | undefined): string | undefined => {\n if (!beforeID || !layerIDPrefix) {\n return beforeID;\n }\n // Don't prefix map style layer IDs (they start with capital letters or contain specific prefixes)\n if (beforeID.startsWith('route') || beforeID.startsWith('waypoint')) {\n return `${layerIDPrefix}-${beforeID}`;\n }\n return beforeID;\n};\n\n/**\n * Helper function to process additional layers and prefix their beforeID fields\n * @ignore\n */\nconst prefixBeforeIDs = (\n additional: Record<string, any> | undefined,\n layerIDPrefix: string | undefined,\n): Record<string, any> | undefined => {\n if (!additional || !layerIDPrefix) {\n return additional;\n }\n\n return Object.fromEntries(\n Object.entries(additional).map(([key, layer]) => [\n key,\n layer?.beforeID ? { ...layer, beforeID: prefixBeforeID(layer.beforeID, layerIDPrefix) } : layer,\n ]),\n );\n};\n\n/**\n * Merges a base layer spec with a user override, deep-merging paint and layout instead of replacing them.\n * @ignore\n */\nconst mergeLayer = <T extends Record<string, any>>(base: T, override: Record<string, any> | undefined): T => {\n if (!override) return base;\n return {\n ...base,\n ...override,\n ...(override.paint && { paint: { ...base.paint, ...override.paint } }),\n ...(override.layout && { layout: { ...base.layout, ...override.layout } }),\n };\n};\n\n/**\n * Helper function to add instance suffix to image IDs for supporting multiple RoutingModule instances\n * @ignore\n */\nconst suffixImageID = (imageID: string | undefined, instanceIndex: number | undefined): string | undefined => {\n if (!imageID || instanceIndex === undefined) {\n return imageID;\n }\n return `${imageID}-${instanceIndex}`;\n};\n\n/**\n * Generates the routing layers configuration for route visualization on the map.\n * @param config - Optional routing module configuration to customize layer properties.\n * @param layerIDPrefix - Optional prefix to add to layer IDs for supporting multiple instances.\n * @param instanceIndex - Optional instance index for image ID suffixes.\n * @ignore\n */\nexport const buildRoutingLayers = (\n config: RoutingModuleConfig = {},\n layerIDPrefix?: string,\n instanceIndex?: number,\n): Required<RouteLayersConfig> => {\n const configLayers = config.layers;\n const configSectionLayers = configLayers?.sections;\n const routeColor = config.theme?.mainColor;\n const outlineColor = routeColor ? darkenColor(routeColor, 0.4) : undefined;\n const routeWidth = config.theme?.routeWidth;\n const waypointSize = config.theme?.waypointSize;\n const waypointIconSize = getWaypointIconSize(waypointSize);\n\n return {\n mainLines: {\n routeLineArrows: mergeLayer(\n { ...routeLineArrows, beforeID: mapStyleLayerIDs.lowestLabel },\n configLayers?.mainLines?.routeLineArrows,\n ),\n routeLine: mergeLayer(\n {\n ...routeMainLine(routeWidth, routeColor),\n beforeID: prefixBeforeID('routeIncidentBackgroundLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeLine,\n ),\n routeOutline: mergeLayer(\n { ...routeOutline(routeWidth, outlineColor), beforeID: prefixBeforeID('routeLine', layerIDPrefix) },\n configLayers?.mainLines?.routeOutline,\n ),\n routeDeselectedLine: mergeLayer(\n { ...routeDeselectedLine(routeWidth), beforeID: prefixBeforeID('routeOutline', layerIDPrefix) },\n configLayers?.mainLines?.routeDeselectedLine,\n ),\n routeDeselectedOutline: mergeLayer(\n {\n ...routeDeselectedOutline(routeWidth),\n beforeID: prefixBeforeID('routeDeselectedLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeDeselectedOutline,\n ),\n ...prefixBeforeIDs(configLayers?.mainLines?.additional, layerIDPrefix),\n },\n waypoints: {\n routeWaypointSymbol: mergeLayer(\n {\n ...waypointSymbols,\n layout: { ...waypointSymbols.layout, 'icon-size': waypointIconSize },\n beforeID: prefixBeforeID('routeSummaryBubbleSymbol', layerIDPrefix),\n },\n configLayers?.waypoints?.routeWaypointSymbol,\n ),\n routeWaypointLabel: mergeLayer(\n { ...waypointLabels, beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix) },\n configLayers?.waypoints?.routeWaypointLabel,\n ),\n ...prefixBeforeIDs(configLayers?.waypoints?.additional, layerIDPrefix),\n },\n chargingStops: {\n routeChargingStopSymbol: mergeLayer(\n {\n ...chargingStopSymbol(config.chargingStops),\n beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix),\n },\n configLayers?.chargingStops?.routeChargingStopSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.chargingStops?.additional, layerIDPrefix),\n },\n sections: {\n incident: {\n routeIncidentJamSymbol: mergeLayer(\n {\n ...routeIncidentsJamSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentJamSymbol,\n ),\n routeIncidentCauseSymbol: mergeLayer(\n {\n ...routeIncidentsCauseSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentCauseSymbol,\n ),\n routeIncidentBackgroundLine: mergeLayer(\n {\n ...routeIncidentsBGLine(routeWidth),\n beforeID: prefixBeforeID('routeIncidentDashedLine', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentBackgroundLine,\n ),\n routeIncidentDashedLine: mergeLayer(\n {\n ...routeIncidentsDashedLine(routeWidth),\n beforeID: prefixBeforeID('routeTunnelLine', layerIDPrefix),\n },\n configSectionLayers?.incident?.routeIncidentDashedLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.incident?.additional, layerIDPrefix),\n },\n ferry: {\n routeFerryLine: mergeLayer(\n { ...routeFerriesLine(routeWidth), beforeID: prefixBeforeID('routeLineArrows', layerIDPrefix) },\n configSectionLayers?.ferry?.routeFerryLine,\n ),\n routeFerrySymbol: mergeLayer(\n {\n ...routeFerriesSymbol,\n beforeID: prefixBeforeID('routeIncidentJamSymbol', layerIDPrefix),\n },\n configSectionLayers?.ferry?.routeFerrySymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.ferry?.additional, layerIDPrefix),\n },\n tollRoad: {\n routeTollRoadOutline: mergeLayer(\n {\n ...routeTollRoadsOutline(routeWidth),\n beforeID: prefixBeforeID('routeDeselectedOutline', layerIDPrefix),\n },\n configSectionLayers?.tollRoad?.routeTollRoadOutline,\n ),\n routeTollRoadSymbol: mergeLayer(\n {\n ...routeTollRoadsSymbol,\n beforeID: prefixBeforeID('routeChargingStopSymbol', layerIDPrefix),\n },\n configSectionLayers?.tollRoad?.routeTollRoadSymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tollRoad?.additional, layerIDPrefix),\n },\n tunnel: {\n routeTunnelLine: mergeLayer(\n { ...routeTunnelsLine(routeWidth), beforeID: prefixBeforeID('routeLineArrows', layerIDPrefix) },\n configSectionLayers?.tunnel?.routeTunnelLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tunnel?.additional, layerIDPrefix),\n },\n vehicleRestricted: {\n routeVehicleRestrictedBackgroundLine: mergeLayer(\n {\n ...routeVehicleRestrictedBackgroundLine(routeWidth),\n beforeID: prefixBeforeID('routeVehicleRestrictedForegroundLine', layerIDPrefix),\n },\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedBackgroundLine,\n ),\n routeVehicleRestrictedForegroundLine: mergeLayer(\n { ...routeVehicleRestrictedDottedLine(routeWidth), beforeID: mapStyleLayerIDs.lowestLabel },\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedForegroundLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.vehicleRestricted?.additional, layerIDPrefix),\n },\n },\n instructionLines: {\n routeInstructionLine: mergeLayer(\n { ...instructionLine(routeWidth), beforeID: mapStyleLayerIDs.lowestLabel },\n configLayers?.instructionLines?.routeInstructionLine,\n ),\n routeInstructionOutline: mergeLayer(\n {\n ...instructionOutline(routeWidth),\n beforeID: prefixBeforeID('routeInstructionLine', layerIDPrefix),\n },\n configLayers?.instructionLines?.routeInstructionOutline,\n ),\n ...prefixBeforeIDs(configLayers?.instructionLines?.additional, layerIDPrefix),\n },\n instructionArrows: {\n routeInstructionArrowSymbol: mergeLayer(\n {\n ...instructionArrow,\n beforeID: prefixBeforeID('routeInstructionLine', layerIDPrefix),\n ...(instanceIndex !== undefined && {\n layout: {\n ...instructionArrow.layout,\n 'icon-image': suffixImageID(\n instructionArrow.layout?.['icon-image'] as string,\n instanceIndex,\n ),\n },\n }),\n },\n configLayers?.instructionArrows?.routeInstructionArrowSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.instructionArrows?.additional, layerIDPrefix),\n },\n summaryBubbles: {\n routeSummaryBubbleSymbol: mergeLayer(\n instanceIndex === undefined ? summaryBubbleSymbolPoint : buildSummaryBubbleSymbolPoint(instanceIndex),\n configLayers?.summaryBubbles?.routeSummaryBubbleSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.summaryBubbles?.additional, layerIDPrefix),\n },\n };\n};\n\n/**\n * Default routing layers configuration. Calls routingLayers with no parameters.\n *\n * @remarks\n * This configuration defines the complete visual styling for all route-related map layers,\n * including main route lines, waypoints, special road sections (ferries, tunnels, toll roads, etc.),\n * turn-by-turn guidance instructions, and route summary information.\n *\n * **Usage:**\n * - Automatically applied when initializing {@link RoutingModule} without custom layer configuration\n * - Can be used as a reference or starting point for creating custom layer configurations\n * - Individual properties can be selectively overridden while keeping defaults for others\n *\n * @see {@link buildRoutingLayers} for details.\n *\n * @see {@link RouteLayersConfig} for the configuration type definition\n * @see {@link RoutingModule.get} for initialization options\n * @see {@link RoutingModule.applyConfig} for runtime configuration updates\n *\n * @group Routing\n */\nexport const defaultRoutingLayers: Required<RouteLayersConfig> = buildRoutingLayers();\n","import type { StyleImageMetadata } from 'maplibre-gl';\nimport { SVGIconStyleOptions } from '../../shared';\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { parseSvg, pinSvg } from '../../shared/resources';\nimport circleSvgRaw from './circle.svg?raw';\nimport finishSvgRaw from './finish.svg?raw';\nimport instructionArrowSvgRaw from './instruction-line-arrow.svg?raw';\nimport startSvgRaw from './start.svg?raw';\nimport summaryMapBubbleSvgRaw from './summary-map-bubble.svg?raw';\nimport trafficSvgRaw from './traffic.svg?raw';\n\nlet instructionArrowIconImg: HTMLImageElement;\n\n// defensive check for SSR\nif (isDOMImageSupported()) {\n instructionArrowIconImg = svgToImg(parseSvg(instructionArrowSvgRaw));\n}\n\n/**\n * @ignore\n */\nexport { instructionArrowIconImg };\n\n/**\n * @ignore\n */\nexport const summaryMapBubbleImg = (color: string): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg: SVGElement = parseSvg(summaryMapBubbleSvgRaw);\n svg.querySelector('#bubble')?.setAttribute('fill', color);\n svg.querySelector('#pin')?.setAttribute('fill', color);\n return svgToImg(svg);\n};\n\n/**\n * Options to effectively stretch the summary bubble image to fit its text.\n * * They are tightly coupled with the SVG original dimensions.\n * @ignore\n */\nexport const summaryBubbleImageOptions: Partial<StyleImageMetadata> = {\n pixelRatio: 2,\n stretchX: [\n [20, 45],\n [100, 130],\n ],\n stretchY: [[20, 35]],\n content: [10, 10, 130, 45],\n};\n\n/**\n * @ignore\n * @param color\n */\nexport const trafficImg = (color: string): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg: SVGElement = parseSvg(trafficSvgRaw);\n const main = svg.querySelector('#main') as Element;\n main.setAttribute('transform', 'scale(2)');\n main.setAttribute('fill', color);\n return svgToImg(svg);\n};\n\n/**\n * @ignore\n */\nexport const waypointIcon = (\n foregroundSvg: SVGElement | undefined,\n svgOptions: SVGIconStyleOptions | undefined,\n): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const svg = pinSvg(svgOptions);\n if (foregroundSvg) {\n svg.appendChild(foregroundSvg);\n }\n return svgToImg(svg);\n};\n\n/**\n * @ignore\n */\nexport const waypointStartIcon = (svgOptions: SVGIconStyleOptions | undefined): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return waypointIcon(parseSvg(startSvgRaw), svgOptions);\n};\n\n/**\n * @ignore\n */\nexport const waypointFinishIcon = (svgOptions: SVGIconStyleOptions | undefined): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return waypointIcon(parseSvg(finishSvgRaw), svgOptions);\n};\n\n/**\n * @ignore\n */\nexport const softWaypointIcon = (): HTMLImageElement => {\n // defensive check for SSR and node-test environments:\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return svgToImg(parseSvg(circleSvgRaw));\n};\n","import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport type { ToBeAddedLayerSpecTemplate, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { buildRoutingLayers } from '../layers/routingLayers';\nimport type { RouteLayersConfig, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport type { RoutingLayersSpecs } from '../types/routingSourcesAndLayers';\n\n/**\n * @ignore\n */\nconst mapLayerSpecs = (\n layerSpecs: Record<string, Partial<ToBeAddedLayerSpecTemplate>> = {},\n layerIDPrefix?: string,\n): ToBeAddedLayerSpecWithoutSource[] =>\n // The key of the entry is the layer ID:\n Object.entries(layerSpecs).map(\n ([id, spec]) =>\n ({\n ...spec,\n id: layerIDPrefix ? `${layerIDPrefix}-${id}` : id,\n }) as ToBeAddedLayerSpecWithoutSource,\n );\n\n/**\n * @ignore\n */\nexport const createLayersSpecs = (\n layerConfigs: RouteLayersConfig = {},\n layerIDPrefix?: string,\n): RoutingLayersSpecs => ({\n mainLines: mapLayerSpecs(layerConfigs.mainLines, layerIDPrefix),\n waypoints: mapLayerSpecs(layerConfigs.waypoints, layerIDPrefix),\n chargingStops: mapLayerSpecs(layerConfigs?.chargingStops, layerIDPrefix),\n ferries: mapLayerSpecs(layerConfigs.sections?.ferry, layerIDPrefix),\n incidents: mapLayerSpecs(layerConfigs.sections?.incident, layerIDPrefix),\n tollRoads: mapLayerSpecs(layerConfigs.sections?.tollRoad, layerIDPrefix),\n tunnels: mapLayerSpecs(layerConfigs.sections?.tunnel, layerIDPrefix),\n vehicleRestricted: mapLayerSpecs(layerConfigs.sections?.vehicleRestricted, layerIDPrefix),\n instructionLines: mapLayerSpecs(layerConfigs.instructionLines, layerIDPrefix),\n instructionArrows: mapLayerSpecs(layerConfigs.instructionArrows, layerIDPrefix),\n summaryBubbles: mapLayerSpecs(layerConfigs.summaryBubbles, layerIDPrefix),\n});\n\n/**\n * @ignore\n */\nexport const routeModuleConfigWithDefaults = (\n config: RoutingModuleConfig | undefined,\n layerIDPrefix: string,\n instanceIndex: number,\n): RoutingModuleConfig => {\n const globalDisplayUnits = TomTomConfig.instance.get().displayUnits;\n const displayUnits = config?.displayUnits;\n return {\n // First apply the provided configuration not to lose any properties:\n ...config,\n ...(displayUnits ? {} : { displayUnits: globalDisplayUnits }),\n layers: buildRoutingLayers(config, layerIDPrefix, instanceIndex),\n };\n};\n","import {\n ChargingStop,\n ChargingStopProps,\n formatDuration,\n generateId,\n type Place,\n type Routes,\n} from '@tomtom-org/maps-sdk/core';\nimport { FeatureCollection, Point } from 'geojson';\nimport { PlaceDisplayProps } from '../../places';\nimport type { DisplayRouteProps, RouteStateProps } from '../types/displayRoutes';\nimport { RoutingModuleConfig } from '../types/routeModuleConfig';\n\nconst getIconID = (chargingStop: ChargingStop, config: RoutingModuleConfig | undefined): string => {\n const iconConfig = config?.chargingStops?.icon;\n if (iconConfig?.mapping) {\n const mapping = iconConfig.mapping;\n switch (mapping.basedOn) {\n case 'chargingSpeed':\n if (chargingStop.properties.chargingConnectionInfo?.chargingSpeed) {\n return mapping.value[chargingStop.properties.chargingConnectionInfo?.chargingSpeed];\n }\n break;\n case 'custom':\n return mapping.fn(chargingStop);\n }\n }\n\n // default: (genesis-like) categorySet ID for \"EV Charging Station\" based on search\n return '7309';\n};\n\nconst formatTitle = (chargingStop: ChargingStop): string => {\n const properties = chargingStop.properties;\n return properties.chargingParkName ?? (properties.chargingParkOperatorName as string);\n};\n\ntype DisplayChargingStopProps = PlaceDisplayProps &\n ChargingStopProps &\n RouteStateProps & {\n chargingDuration: string;\n chargingPower: string;\n };\n\ntype DisplayChargingStops = FeatureCollection<Point, DisplayChargingStopProps>;\n\n/**\n * Generates display-ready charging stations for the given planning context ones.\n * @param routes The routes return for ldEV.\n * @param config The charging stops display configuration.\n * @see chargingStopLayers\n * @ignore\n */\nexport const toDisplayChargingStops = (\n routes: Routes<DisplayRouteProps>,\n config: RoutingModuleConfig | undefined,\n): DisplayChargingStops => {\n const displayChargingStops: Place<DisplayChargingStopProps>[] = [];\n\n if (config?.chargingStops?.visible !== false) {\n for (const route of routes.features) {\n for (const leg of route.properties.sections.leg) {\n const chargingStop = leg.summary.chargingInformationAtEndOfLeg;\n\n if (chargingStop) {\n const properties = chargingStop.properties;\n displayChargingStops.push({\n ...chargingStop,\n properties: {\n ...chargingStop.properties,\n id: chargingStop.properties.chargingParkId ?? generateId(),\n iconID: getIconID(chargingStop, config),\n title: formatTitle(chargingStop),\n chargingPower: `${properties.chargingConnectionInfo?.chargingPowerInkW} kW`,\n chargingDuration: formatDuration(\n properties.chargingTimeInSeconds,\n config?.displayUnits?.time,\n ) as string,\n routeState: route.properties.routeState,\n },\n });\n }\n }\n }\n }\n return { type: 'FeatureCollection', features: displayChargingStops };\n};\n","import { formatDuration, type TrafficSectionProps } from '@tomtom-org/maps-sdk/core';\nimport { incidentIconID } from '../../traffic/util/trafficIncidentStyle';\nimport type { DisplayTrafficSectionProps } from '../types/routeSections';\n\nconst hasJam = (sectionProps: TrafficSectionProps): boolean => sectionProps.categories.includes('jam');\n\nconst buildTitle = (sectionProps: TrafficSectionProps): string | undefined => {\n if (hasJam(sectionProps)) {\n return formatDuration(sectionProps.delayInSeconds);\n }\n return undefined;\n};\n\nconst toTrafficJamIconSuffix = (title: string | undefined): 'collapsed' | 'small' | 'medium' | 'large' => {\n if (!title?.length) {\n return 'collapsed';\n }\n if (title.length < 6) {\n // 1 digit minutes\n return 'small';\n }\n if (title.length < 8) {\n // 2 digit minutes\n return 'medium';\n }\n // hours + minutes\n return 'large';\n};\n\nconst toJamIconID = (sectionProps: TrafficSectionProps, title: string | undefined): string | null => {\n if (!hasJam(sectionProps)) {\n return null;\n }\n const magnitude = sectionProps.magnitudeOfDelay ?? 'unknown';\n return `traffic-jam-${magnitude}-${toTrafficJamIconSuffix(title)}`;\n};\n\nconst toCauseIconID = (sectionProps: TrafficSectionProps): string | null => {\n const firstNonJamCategory = sectionProps.categories.find((category) => category !== 'jam');\n return firstNonJamCategory ? incidentIconID(firstNonJamCategory) : null;\n};\n\n/**\n * @ignore\n */\nexport const toDisplayTrafficSectionProps = (\n sectionProps: TrafficSectionProps,\n): Omit<DisplayTrafficSectionProps, 'routeState' | 'routeIndex'> => {\n const title = buildTitle(sectionProps);\n const jamIconID = toJamIconID(sectionProps, title);\n const causeIconID = toCauseIconID(sectionProps);\n return {\n ...sectionProps,\n ...(jamIconID && { jamIconID }),\n ...(causeIconID && { causeIconID }),\n ...(title && { title }),\n };\n};\n","import type { CommonPlaceProps, Waypoint, WaypointLike, Waypoints } from '@tomtom-org/maps-sdk/core';\nimport { generateId, getPosition } from '@tomtom-org/maps-sdk/core';\nimport type { Point, Position } from 'geojson';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport {\n WAYPOINT_FINISH_IMAGE_ID,\n WAYPOINT_SOFT_IMAGE_ID,\n WAYPOINT_START_IMAGE_ID,\n WAYPOINT_STOP_IMAGE_ID,\n} from '../layers/waypointLayers';\nimport type { PlanningWaypoint } from '../types/planningWaypoint';\nimport type { WaypointsConfig } from '../types/routeModuleConfig';\nimport type { WaypointDisplayProps, WaypointIndexType } from '../types/waypointDisplayProps';\nimport { FINISH_INDEX, MIDDLE_INDEX, START_INDEX } from '../types/waypointDisplayProps';\n\nconst indexTypeFor = (index: number, arrayLength: number): WaypointIndexType =>\n index === 0 ? START_INDEX : index < arrayLength - 1 ? MIDDLE_INDEX : FINISH_INDEX;\n\n/**\n * Builds the title of the given waypoint.\n * @param waypoint The waypoint for which to build the title.\n */\nexport const buildWaypointTitle = (waypoint: Waypoint): string | undefined => {\n const placeProperties = waypoint?.properties as CommonPlaceProps;\n return placeProperties?.poi?.name ?? placeProperties?.address?.freeformAddress ?? undefined;\n};\n\nexport const getImageIDForWaypoint = (\n waypoint: Waypoint,\n indexType: WaypointIndexType,\n instanceIndex?: number,\n): string => {\n if (waypoint.properties.radiusMeters) {\n return instanceIndex !== undefined\n ? suffixNumber(WAYPOINT_SOFT_IMAGE_ID, instanceIndex)\n : WAYPOINT_SOFT_IMAGE_ID;\n }\n let baseImageID: string;\n switch (indexType) {\n case 'start':\n baseImageID = WAYPOINT_START_IMAGE_ID;\n break;\n case 'finish':\n baseImageID = WAYPOINT_FINISH_IMAGE_ID;\n break;\n default:\n baseImageID = WAYPOINT_STOP_IMAGE_ID;\n break;\n }\n return instanceIndex !== undefined ? suffixNumber(baseImageID, instanceIndex) : baseImageID;\n};\n\nconst toWaypointFromPosition = (position: Position): Waypoint => ({\n type: 'Feature',\n geometry: {\n type: 'Point',\n coordinates: position,\n },\n properties: {},\n});\n\nconst toWaypointFromPoint = (point: Point): Waypoint => ({\n type: 'Feature',\n geometry: point,\n properties: {},\n ...(point.bbox && { bbox: point.bbox }),\n});\n\nconst asWaypoint = (waypointInput: WaypointLike): Waypoint => {\n if (Array.isArray(waypointInput)) {\n return toWaypointFromPosition(waypointInput);\n }\n if (waypointInput.type === 'Point') {\n return toWaypointFromPoint(waypointInput);\n }\n return waypointInput as Waypoint;\n};\n\n/**\n * Determines whether the given waypoint is a regular start/stop/destination with guidance attached,\n * as opposed to a circle (soft) waypoint.\n * @param waypoint The waypoint to verify.\n */\nexport const isHardWaypoint = (waypoint: Waypoint): boolean => !waypoint.properties.radiusMeters;\n\n/**\n * Generates display-ready waypoints for the given planning context ones.\n * @param waypoints The planning context waypoints.\n * @param options\n * @param instanceIndex Optional instance index for supporting multiple RoutingModule instances\n */\nexport const toDisplayWaypoints = (\n waypoints: PlanningWaypoint[],\n options: WaypointsConfig | undefined,\n instanceIndex?: number,\n): Waypoints<WaypointDisplayProps> => {\n // Since waypoints are of mixed types (hard and soft), we need to calculate the hard-only indexes\n // in case we have stops with numbered icons:\n let hardWaypointIndex = -1;\n return {\n type: 'FeatureCollection',\n features: waypoints\n .map((waypointInput, index) => {\n if (!waypointInput) {\n // (we consider placeholder waypoints to be \"hard\", since they typically occupy a position in a planner panel)\n hardWaypointIndex++;\n // (will be filtered out below - we don't pre-filter it to keep the original input index)\n return null as unknown as Waypoint<WaypointDisplayProps>;\n }\n const waypoint: Waypoint = asWaypoint(waypointInput);\n const indexType = indexTypeFor(index, waypoints.length);\n const hardWaypoint = isHardWaypoint(waypoint);\n if (hardWaypoint) {\n hardWaypointIndex++;\n }\n const title = buildWaypointTitle(waypoint);\n const id = (waypoint.id as string) ?? generateId();\n return {\n ...waypoint,\n ...(options?.entryPoints === 'main-when-available' && {\n geometry: {\n type: 'Point',\n // We replace the waypoint coordinates with their main entry point ones:\n coordinates: getPosition(waypoint, { useEntryPoint: 'main-when-available' }),\n } as Point,\n }),\n id,\n properties: {\n ...waypoint.properties,\n id,\n index,\n indexType,\n ...(title && { title }),\n iconID: getImageIDForWaypoint(waypoint, indexType, instanceIndex),\n ...(hardWaypoint && indexType === MIDDLE_INDEX && { stopDisplayIndex: hardWaypointIndex }),\n },\n };\n })\n .filter((feature) => feature),\n };\n};\n","import { generateId, Routes } from '@tomtom-org/maps-sdk/core';\nimport { bearing } from '@turf/turf';\nimport type { DisplayRouteProps } from '../types/displayRoutes';\nimport type {\n DisplayInstruction,\n DisplayInstructionArrow,\n DisplayInstructionArrows,\n DisplayInstructions,\n} from '../types/guidance';\n\n/**\n * @ignore\n */\nexport const toDisplayInstructions = (routes: Routes<DisplayRouteProps>): DisplayInstructions => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap(\n (route, routeIndex) =>\n route.properties.guidance?.instructions\n ?.filter((instruction) => instruction.routePath?.length)\n .map(\n (instruction): DisplayInstruction => ({\n type: 'Feature',\n geometry: {\n type: 'LineString',\n coordinates: instruction.routePath.map((pathPoint) => pathPoint.point),\n },\n properties: {\n ...instruction,\n id: generateId(),\n routeIndex,\n routeState: route.properties.routeState,\n },\n }),\n ) || [],\n ),\n});\n\n/**\n * @ignore\n */\nexport const toDisplayInstructionArrows = (routes: Routes<DisplayRouteProps>): DisplayInstructionArrows => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap(\n (route, routeIndex) =>\n route.properties.guidance?.instructions\n ?.filter((instruction) => instruction.routePath?.length && instruction.routePath.length > 1)\n .map((instruction): DisplayInstructionArrow => {\n const instructionLastSegment = [\n instruction.routePath[instruction.routePath.length - 2]?.point,\n instruction.routePath[instruction.routePath.length - 1]?.point,\n ];\n\n return {\n type: 'Feature',\n geometry: { type: 'Point', coordinates: instructionLastSegment[1] },\n properties: {\n ...instruction,\n id: generateId(),\n routeIndex,\n routeState: route.properties.routeState,\n lastPointBearingDegrees: bearing(instructionLastSegment[0], instructionLastSegment[1]),\n },\n };\n }) || [],\n ),\n});\n","import { generateId, Route, Routes, SectionProps, SectionType } from '@tomtom-org/maps-sdk/core';\nimport type { DisplayRouteProps } from '../types/displayRoutes';\nimport type { DisplaySectionProps, RouteSection, RouteSections } from '../types/routeSections';\n\nconst buildRouteSectionsFromRoute = <\n S extends SectionProps = SectionProps,\n D extends DisplaySectionProps = DisplaySectionProps,\n>(\n route: Route<DisplayRouteProps>,\n sectionType: SectionType,\n displaySectionPropsBuilder?: (\n sectionProps: S,\n routeProps?: DisplayRouteProps,\n ) => Omit<D, 'routeState' | 'routeIndex'>,\n): RouteSection<D>[] =>\n (route.properties.sections[sectionType] as S[])?.map((sectionProps) => {\n const id = sectionProps.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'LineString',\n coordinates: route.geometry.coordinates.slice(\n sectionProps.startPointIndex,\n sectionProps.endPointIndex + 1,\n ),\n },\n properties: {\n ...(displaySectionPropsBuilder\n ? displaySectionPropsBuilder(sectionProps, route.properties)\n : sectionProps),\n routeState: route.properties.routeState,\n routeIndex: route.properties.index,\n id, // we need id in properties due to promoteId feature\n } as D,\n };\n }) || [];\n\n/**\n * Builds display-ready LineString features to render the sections of a given type, from the given route.\n * @param routes The routes from which to extract the section props.\n * @param sectionType The type of the sections to extract.\n * @param displaySectionPropsBuilder An optional function which will convert each section props into an extended display-ready version.\n * @ignore\n */\nexport const toDisplayRouteSections = <\n S extends SectionProps = SectionProps,\n D extends DisplaySectionProps = DisplaySectionProps,\n>(\n routes: Routes<DisplayRouteProps>,\n sectionType: SectionType,\n displaySectionPropsBuilder?: (\n sectionProps: S,\n routeProps?: DisplayRouteProps,\n ) => Omit<D, 'routeState' | 'routeIndex'>,\n): RouteSections<D> => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap((route) =>\n buildRouteSectionsFromRoute<S, D>(route, sectionType, displaySectionPropsBuilder),\n ),\n});\n","import type { Routes } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection, Geometry } from 'geojson';\nimport type { GeoJSONSourceWithLayers } from '../../shared';\nimport type { DisplayRouteProps, DisplayRouteRelatedProps } from '../types/displayRoutes';\n\n/**\n * Rebuilds route-related display features such as sections or instructions, with the updated route selection based on the given routes.\n * @ignore\n */\nexport const rebuildFeaturesWithRouteSelection = <T extends FeatureCollection<Geometry, DisplayRouteRelatedProps>>(\n routes: Routes<DisplayRouteProps>,\n featureCollection: T,\n): T => ({\n ...featureCollection,\n features: featureCollection.features.map((features) => ({\n ...features,\n properties: {\n ...features.properties,\n routeState: routes.features[features.properties.routeIndex || 0].properties.routeState,\n },\n })),\n});\n\n/**\n * @ignore\n */\nexport const showFeaturesWithRouteSelection = <T extends FeatureCollection<Geometry, DisplayRouteRelatedProps>>(\n routesWithSelection: Routes<DisplayRouteProps>,\n sourceWithLayers: GeoJSONSourceWithLayers<T>,\n): void =>\n sourceWithLayers.show(rebuildFeaturesWithRouteSelection(routesWithSelection, sourceWithLayers.shownFeatures));\n","import {\n DelayMagnitude,\n DisplayUnits,\n formatDistance,\n formatDuration,\n generateId,\n Route,\n Routes,\n TrafficSectionProps,\n} from '@tomtom-org/maps-sdk/core';\nimport type { DisplayRouteProps, DisplayRouteSummaries } from '../types/displayRoutes';\n\n/**\n * Builds map display-ready routes, applying default style props.\n * @ignore\n */\nexport const toDisplayRoutes = (routes: Route | Routes, selectedIndex = 0): Routes<DisplayRouteProps> => {\n const routesCollection: Routes = 'features' in routes ? routes : { type: 'FeatureCollection', features: [routes] };\n return {\n ...routesCollection,\n features: routesCollection.features.map((route, index) => {\n const id = route.id ?? generateId();\n return {\n ...route,\n id,\n properties: {\n ...route.properties,\n id, // we need id in properties due to promoteId feature\n routeState: index === selectedIndex ? 'selected' : 'deselected',\n },\n };\n }),\n };\n};\n\nconst hasMagnitude = (sections: TrafficSectionProps[], magnitude: DelayMagnitude): boolean =>\n sections.some((section) => section.magnitudeOfDelay === magnitude);\n\nconst summaryDelayMagnitude = (route: Route): DelayMagnitude | undefined => {\n const trafficSections = route.properties.sections.traffic;\n if (!trafficSections?.length) {\n return undefined;\n }\n if (hasMagnitude(trafficSections, 'major')) {\n return 'major';\n }\n if (hasMagnitude(trafficSections, 'moderate')) {\n return 'moderate';\n }\n if (hasMagnitude(trafficSections, 'minor')) {\n return 'minor';\n }\n return undefined;\n};\n/**\n * Builds map display-ready route summaries based on display routes.\n * @ignore\n */\nexport const toDisplayRouteSummaries = (\n routes: Routes<DisplayRouteProps>,\n displayUnits?: DisplayUnits,\n): DisplayRouteSummaries => ({\n type: 'FeatureCollection',\n features: routes.features.map((route) => {\n const summary = route.properties.summary;\n const routeCoordinates = route.geometry.coordinates;\n const formattedTraffic = formatDuration(summary.trafficDelayInSeconds, displayUnits?.time);\n const magnitudeOfDelay = summaryDelayMagnitude(route);\n const id = route.id ?? generateId();\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'Point',\n coordinates: routeCoordinates[Math.round(routeCoordinates.length / 2)],\n },\n properties: {\n id, // we need id in properties due to promoteId feature\n routeIndex: route.properties.index,\n routeState: route.properties.routeState,\n formattedDistance: formatDistance(summary.lengthInMeters, displayUnits?.distance),\n ...(magnitudeOfDelay && { magnitudeOfDelay }),\n ...(formattedTraffic && { formattedTraffic }),\n formattedDuration: formatDuration(summary.travelTimeInSeconds, displayUnits?.time),\n },\n };\n }),\n});\n","import type { Route, Routes, Waypoint, Waypoints } from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport type { StyleImageMetadata } from 'maplibre-gl';\nimport {\n AbstractMapModule,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n SVGIconStyleOptions,\n UserEvents,\n} from '../shared';\nimport { suffixNumber } from '../shared/layers/utils';\nimport { addLayers, addOrUpdateImage, updateLayersAndSource, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { INSTRUCTION_ARROW_IMAGE_ID } from './layers/guidanceLayers';\nimport { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './layers/routeMainLineLayers';\nimport { MAJOR_DELAY_COLOR, MINOR_DELAY_LABEL_COLOR, MODERATE_DELAY_COLOR, UNKNOWN_DELAY_COLOR } from './layers/shared';\nimport {\n TRAFFIC_CLEAR_IMAGE_ID,\n TRAFFIC_MAJOR_IMAGE_ID,\n TRAFFIC_MINOR_IMAGE_ID,\n TRAFFIC_MODERATE_IMAGE_ID,\n} from './layers/summaryBubbleLayers';\nimport {\n WAYPOINT_FINISH_IMAGE_ID,\n WAYPOINT_SOFT_IMAGE_ID,\n WAYPOINT_START_IMAGE_ID,\n WAYPOINT_STOP_IMAGE_ID,\n} from './layers/waypointLayers';\nimport {\n instructionArrowIconImg,\n softWaypointIcon,\n summaryBubbleImageOptions,\n summaryMapBubbleImg,\n trafficImg,\n waypointFinishIcon,\n waypointIcon,\n waypointStartIcon,\n} from './resources';\nimport type { DisplayRouteProps, DisplayRouteSummary } from './types/displayRoutes';\nimport type { DisplayInstruction } from './types/guidance';\nimport type { PlanningWaypoint } from './types/planningWaypoint';\nimport type { RoutingModuleConfig } from './types/routeModuleConfig';\nimport type { DisplayTrafficSectionProps, RouteSection } from './types/routeSections';\nimport type { RoutingLayersSpecs, RoutingSourcesWithLayers } from './types/routingSourcesAndLayers';\nimport type { ShowRoutesOptions } from './types/showOptions';\nimport type { WaypointDisplayProps } from './types/waypointDisplayProps';\nimport { createLayersSpecs, routeModuleConfigWithDefaults } from './util/config';\nimport { toDisplayChargingStops } from './util/displayChargingStops';\nimport { toDisplayTrafficSectionProps } from './util/displayTrafficSectionProps';\nimport { toDisplayWaypoints } from './util/displayWaypoints';\nimport { toDisplayInstructionArrows, toDisplayInstructions } from './util/guidance';\nimport { toDisplayRouteSections } from './util/routeSections';\nimport { showFeaturesWithRouteSelection } from './util/routeSelection';\nimport { toDisplayRouteSummaries, toDisplayRoutes } from './util/routes';\n\n/**\n * Map module for displaying and managing route visualizations.\n *\n * The RoutingModule provides comprehensive functionality for displaying routes on the map,\n * including route lines, alternative routes, turn-by-turn guidance, and interactive waypoints.\n * It integrates seamlessly with the TomTom Routing API.\n *\n * @remarks\n * **Features:**\n * - Display route lines with customizable styling\n * - Show alternative routes with different styling\n * - Interactive waypoint markers (drag, add, remove)\n * - Turn-by-turn guidance instructions\n * - Route section highlighting\n * - Distance and duration information\n * - Support for multiple routes simultaneously\n *\n * **Common Use Cases:**\n * - Turn-by-turn navigation displays\n * - Route planning and comparison\n * - Multi-stop route optimization\n * - Interactive route editing\n * - Fleet management route visualization\n *\n * @example\n * ```typescript\n * // Create the module\n * const routingModule = await RoutingModule.getInstance(map, {\n * displayUnits: {\n * distance: { type: 'metric' }\n * }\n * });\n *\n * // Calculate and display a route\n * const result = await calculateRoute({\n * key: 'your-api-key',\n * locations: [\n * [4.9041, 52.3676], // Amsterdam\n * [4.4777, 51.9244] // Rotterdam\n * ],\n * routeOptions: {\n * travelMode: 'car',\n * routeType: 'fastest'\n * }\n * });\n *\n * await routingModule.showRoutes(result);\n * ```\n *\n * @see [Routing Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/routing)\n *\n * @group Routing\n */\nexport class RoutingModule extends AbstractMapModule<RoutingSourcesWithLayers, RoutingModuleConfig> {\n private layersSpecs!: RoutingLayersSpecs;\n private layerIDPrefix!: string;\n private readonly shownFeaturesHandlers: ((\n features: { routes: Route | Routes } | { waypoints: PlanningWaypoint[] | Waypoints },\n ) => void)[] = [];\n\n /**\n * Make sure the map is ready before create an instance of the module and any other interaction with the map\n * @param tomtomMap The TomTomMap instance.\n * @param config The module optional configuration\n * @returns {Promise} Returns a promise with a new instance of this module\n *\n * @remarks\n * **Configuration Options:**\n * - `displayUnits`: Distance units (metric/imperial)\n * - `waypointsSource`: Waypoint entry point options\n * - `layers`: Complete layer styling configuration\n *\n * **Default Styling:**\n * If no custom layers are provided, uses {@link defaultRoutingLayers}.\n *\n * @example\n * Default initialization:\n * ```typescript\n * const routingModule = await RoutingModule.get(map);\n * ```\n *\n * @example\n * With custom configuration:\n * ```typescript\n * const routingModule = await RoutingModule.get(map, {\n * displayUnits: 'imperial',\n * waypointsSource: {\n * entryPoints: 'main-when-available'\n * }\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: RoutingModuleConfig): Promise<RoutingModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new RoutingModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: RoutingModuleConfig) {\n super('geojson', map, config);\n }\n\n private createSourcesWithLayers(layersSpecs: RoutingLayersSpecs): RoutingSourcesWithLayers {\n const sourcePrefix = suffixNumber('routes', this.instanceIndex);\n return {\n mainLines: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-mainLines`,\n layersSpecs.mainLines,\n false,\n ),\n waypoints: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-waypoints`,\n layersSpecs.waypoints,\n false,\n ),\n incidents: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-incidents`,\n layersSpecs.incidents,\n false,\n ),\n ferries: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-ferries`,\n layersSpecs.ferries,\n false,\n ),\n chargingStops: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-chargingStops`,\n layersSpecs.chargingStops,\n false,\n ),\n tollRoads: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-tollRoads`,\n layersSpecs.tollRoads,\n false,\n ),\n tunnels: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-tunnels`,\n layersSpecs.tunnels,\n false,\n ),\n vehicleRestricted: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-vehicleRestricted`,\n layersSpecs.vehicleRestricted,\n false,\n ),\n instructionLines: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-instructionLines`,\n layersSpecs.instructionLines,\n false,\n ),\n instructionArrows: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-instructionArrows`,\n layersSpecs.instructionArrows,\n false,\n ),\n summaryBubbles: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-summaryBubbles`,\n layersSpecs.summaryBubbles,\n false,\n ),\n };\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: RoutingModuleConfig, restore?: boolean): RoutingSourcesWithLayers {\n // `instanceIndex` is auto-assigned by AbstractMapModule; only the layerIDPrefix\n // needs to be (re)computed on first init.\n if (!restore) {\n this.layerIDPrefix = suffixNumber('routes', this.instanceIndex);\n }\n\n this.layersSpecs = createLayersSpecs(\n routeModuleConfigWithDefaults(config, this.layerIDPrefix, this.instanceIndex).layers,\n this.layerIDPrefix,\n );\n const routingSourcesWithLayers: RoutingSourcesWithLayers = this.createSourcesWithLayers(this.layersSpecs);\n addLayers(\n Object.values(routingSourcesWithLayers).flatMap((source) => source._layerSpecs),\n this.mapLibreMap,\n );\n\n const svgIconOptions: SVGIconStyleOptions = {\n // first comes the main theme fill color, if any:\n fillColor: config?.theme?.mainColor,\n // then come any icon style configs for the waypoint icons, if any:\n ...config?.waypoints?.icon?.style,\n };\n const options: Partial<StyleImageMetadata> = { pixelRatio: 2 };\n\n // Generate instance-specific image IDs to support multiple RoutingModule instances\n const waypointStartImageId = suffixNumber(WAYPOINT_START_IMAGE_ID, this.instanceIndex);\n const waypointStopImageId = suffixNumber(WAYPOINT_STOP_IMAGE_ID, this.instanceIndex);\n const waypointSoftImageId = suffixNumber(WAYPOINT_SOFT_IMAGE_ID, this.instanceIndex);\n const waypointFinishImageId = suffixNumber(WAYPOINT_FINISH_IMAGE_ID, this.instanceIndex);\n const instructionArrowImageId = suffixNumber(INSTRUCTION_ARROW_IMAGE_ID, this.instanceIndex);\n const selectedSummaryPopupImageId = suffixNumber(SELECTED_SUMMARY_POPUP_IMAGE_ID, this.instanceIndex);\n const deselectedSummaryPopupImageId = suffixNumber(DESELECTED_SUMMARY_POPUP_IMAGE_ID, this.instanceIndex);\n const trafficClearImageId = suffixNumber(TRAFFIC_CLEAR_IMAGE_ID, this.instanceIndex);\n const trafficMajorImageId = suffixNumber(TRAFFIC_MAJOR_IMAGE_ID, this.instanceIndex);\n const trafficModerateImageId = suffixNumber(TRAFFIC_MODERATE_IMAGE_ID, this.instanceIndex);\n const trafficMinorImageId = suffixNumber(TRAFFIC_MINOR_IMAGE_ID, this.instanceIndex);\n\n // loading of extra assets if not present in the map style:\n this.addImageIfNotExisting(waypointStartImageId, waypointStartIcon(svgIconOptions), options);\n this.addImageIfNotExisting(waypointStopImageId, waypointIcon(undefined, svgIconOptions), options);\n this.addImageIfNotExisting(waypointSoftImageId, softWaypointIcon(), options);\n this.addImageIfNotExisting(waypointFinishImageId, waypointFinishIcon(svgIconOptions), options);\n this.addImageIfNotExisting(instructionArrowImageId, instructionArrowIconImg, options);\n this.addImageIfNotExisting(\n selectedSummaryPopupImageId,\n summaryMapBubbleImg('white'),\n summaryBubbleImageOptions,\n );\n this.addImageIfNotExisting(\n deselectedSummaryPopupImageId,\n summaryMapBubbleImg('#EEEEEE'),\n summaryBubbleImageOptions,\n );\n this.addImageIfNotExisting(trafficClearImageId, trafficImg(UNKNOWN_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficMajorImageId, trafficImg(MAJOR_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficModerateImageId, trafficImg(MODERATE_DELAY_COLOR), options);\n this.addImageIfNotExisting(trafficMinorImageId, trafficImg(MINOR_DELAY_LABEL_COLOR), options);\n\n // If we have custom icons, ensure they're added to the map style:\n for (const customChargingStopIcon of config?.chargingStops?.icon?.customIcons ?? []) {\n this.addImageIfNotExisting(customChargingStopIcon.id, customChargingStopIcon.image as string, {\n pixelRatio: customChargingStopIcon.pixelRatio ?? 2,\n });\n }\n\n return routingSourcesWithLayers;\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config?: RoutingModuleConfig) {\n const mergedConfig = routeModuleConfigWithDefaults(config, this.layerIDPrefix, this.instanceIndex);\n\n // If there was already some config set, we must update the changes:\n if (this.config) {\n // replace existing configuration with new one\n const newLayersSpecs = createLayersSpecs(mergedConfig.layers, this.layerIDPrefix);\n\n // here we assume that keys for layer specs and sources are the same, please keep it that way to simplify the logic\n Object.keys(newLayersSpecs).forEach((layersSpecID) => {\n const id = layersSpecID as keyof RoutingSourcesWithLayers;\n updateLayersAndSource(\n newLayersSpecs[id],\n this.layersSpecs[id],\n this.sourcesWithLayers[id],\n this.mapLibreMap,\n );\n });\n // we need to add layers correctly\n const listOfSources = Object.values(this.sourcesWithLayers) as GeoJSONSourceWithLayers[];\n addLayers(\n listOfSources.flatMap((source) => source._layerSpecs),\n this.mapLibreMap,\n );\n // set the correct visibility if there are new layers\n listOfSources.forEach((source) => source.setLayersVisible(!!source.shownFeatures.features.length));\n this.layersSpecs = newLayersSpecs;\n }\n\n // Update waypoint icon images if mainColor changed\n const mainColorChanged = config?.theme?.mainColor !== this.config?.theme?.mainColor;\n if (mainColorChanged) {\n const svgIconOptions: SVGIconStyleOptions = {\n fillColor: config?.theme?.mainColor,\n ...config?.waypoints?.icon?.style,\n };\n const options: Partial<StyleImageMetadata> = { pixelRatio: 2 };\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_START_IMAGE_ID, this.instanceIndex),\n waypointStartIcon(svgIconOptions),\n this.mapLibreMap,\n options,\n );\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_STOP_IMAGE_ID, this.instanceIndex),\n waypointIcon(undefined, svgIconOptions),\n this.mapLibreMap,\n options,\n );\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(WAYPOINT_FINISH_IMAGE_ID, this.instanceIndex),\n waypointFinishIcon(svgIconOptions),\n this.mapLibreMap,\n options,\n );\n }\n\n // Summary bubbles have dedicated sources and contain distance-units dependent text ...\n // ... so we need to re-show or clear them if relevant config parts changed:\n const summaryBubblesVisible = mergedConfig.summaryBubbles?.visible !== false;\n const visibilityChanged = !isEqual(this.config?.summaryBubbles?.visible, mergedConfig.summaryBubbles?.visible);\n const displayUnitsChanged = !isEqual(this.config?.displayUnits, mergedConfig.displayUnits);\n const hasSummaryBubbles = this.sourcesWithLayers.summaryBubbles.shownFeatures.features.length > 0;\n const hasRoutes = this.sourcesWithLayers.mainLines.shownFeatures.features.length > 0;\n\n if (!summaryBubblesVisible && visibilityChanged) {\n this.sourcesWithLayers.summaryBubbles.clear();\n } else if (\n summaryBubblesVisible &&\n ((visibilityChanged && hasRoutes) || (displayUnitsChanged && hasSummaryBubbles))\n ) {\n this.sourcesWithLayers.summaryBubbles.show(\n toDisplayRouteSummaries(this.sourcesWithLayers.mainLines.shownFeatures, mergedConfig.displayUnits),\n );\n }\n\n return mergedConfig;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n const previouslyShown = Object.entries(this.sourcesWithLayers)\n .map((entry) => ({\n [entry[0]]: entry[1].shownFeatures,\n }))\n .reduce((acc, item) => ({ ...acc, ...item }), {}) as Record<keyof RoutingSourcesWithLayers, any>;\n\n this.initSourcesWithLayers(this.config, true);\n this._applyConfig(this.config);\n\n for (const key of Object.keys(previouslyShown) as (keyof RoutingSourcesWithLayers)[]) {\n this.sourcesWithLayers[key].show(previouslyShown[key]);\n }\n }\n\n private addImageIfNotExisting(\n imageId: string,\n image: string | HTMLImageElement,\n options: Partial<StyleImageMetadata> | undefined,\n ) {\n addOrUpdateImage('if-not-in-sprite', imageId, image, this.mapLibreMap, options);\n }\n\n /**\n * Displays the given routes on the map.\n *\n * @param routes - Route data from Routing API or custom routes.\n * @param options - Optional configuration for route selection and display.\n * @param options.selectedIndex - Index of the route to display as selected (default: 0).\n *\n * @remarks\n * **Waypoints are NOT shown by this method.**\n * Route lines are drawn but start/stop/destination markers are not. To display waypoint\n * markers, call {@link showWaypoints} separately. Alternatively, use\n * {@link PlacesModule} to display waypoints as fully customizable place markers.\n *\n * **Behavior:**\n * - Replaces any previously shown routes\n * - Shows all route-related features: lines, sections, summaries, guidance\n * - First route is selected by default (appears more prominent)\n *\n * **Route Features:**\n * - Main route lines (selected and deselected styles)\n * - Traffic sections with delays\n * - Ferry, tunnel, and toll sections\n * - EV charging stations (for EV routes)\n * - Turn-by-turn instruction lines and arrows\n * - Summary bubbles with distance/time/traffic info\n *\n * @example\n * Show a route (no waypoint markers):\n * ```typescript\n * await routing.showRoutes(response.routes);\n * // Waypoint markers are NOT shown — call showWaypoints() to add them\n * ```\n *\n * @example\n * Show routes and waypoint markers:\n * ```typescript\n * const locations = [[4.9, 52.4], [4.5, 51.9]];\n * const response = await calculateRoute({ locations });\n * await routing.showRoutes(response.routes);\n * await routing.showWaypoints(locations); // Show start/destination markers separately\n * ```\n *\n * @example\n * Show multiple routes with specific selection:\n * ```typescript\n * await routing.showRoutes(response.routes, { selectedIndex: 1 });\n * ```\n */\n async showRoutes(routes: Route | Routes, options?: ShowRoutesOptions) {\n const displayRoutes = toDisplayRoutes(routes, options?.selectedIndex);\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.mainLines.show(displayRoutes);\n this.sourcesWithLayers.vehicleRestricted.show(toDisplayRouteSections(displayRoutes, 'vehicleRestricted'));\n this.sourcesWithLayers.incidents.show(\n toDisplayRouteSections(displayRoutes, 'traffic', toDisplayTrafficSectionProps),\n );\n this.sourcesWithLayers.chargingStops.show(toDisplayChargingStops(displayRoutes, this.config));\n this.sourcesWithLayers.ferries.show(toDisplayRouteSections(displayRoutes, 'ferry'));\n this.sourcesWithLayers.tunnels.show(toDisplayRouteSections(displayRoutes, 'tunnel'));\n this.sourcesWithLayers.tollRoads.show(toDisplayRouteSections(displayRoutes, 'toll'));\n this.sourcesWithLayers.instructionLines.show(toDisplayInstructions(displayRoutes));\n this.sourcesWithLayers.instructionArrows.show(toDisplayInstructionArrows(displayRoutes));\n if (this.config?.summaryBubbles?.visible !== false) {\n this.sourcesWithLayers.summaryBubbles.show(\n toDisplayRouteSummaries(displayRoutes, this.config?.displayUnits),\n );\n } else {\n this.sourcesWithLayers.summaryBubbles.clear();\n }\n for (const handler of this.shownFeaturesHandlers) {\n handler({ routes });\n }\n }\n\n /**\n * Clears any previously shown routes from the map.\n *\n * @remarks\n * - Clears all route-related layers (lines, sections, guidance, summaries)\n * - Does NOT clear waypoints (use {@link clearWaypoints})\n * - Module remains initialized and ready for new routes\n *\n * @example\n * ```typescript\n * await routing.clearRoutes();\n * ```\n */\n async clearRoutes() {\n await this.waitUntilModuleReady();\n for (const key of Object.keys(this.sourcesWithLayers) as (keyof RoutingSourcesWithLayers)[]) {\n if (key !== 'waypoints') {\n this.sourcesWithLayers[key as keyof RoutingSourcesWithLayers].clear();\n }\n }\n }\n\n /**\n * Changes which route appears as selected.\n *\n * @param index - Zero-based index of the route to select.\n *\n * @remarks\n * **Visual Changes:**\n * - Selected route appears more prominent (thicker, brighter)\n * - Previously selected route becomes deselected style\n * - Updates all route-related features (sections, guidance)\n *\n * **Requirements:**\n * - Route must already be displayed via {@link showRoutes}\n * - Index must be within range of displayed routes\n *\n * @example\n * ```typescript\n * // Show multiple routes\n * await routingModule.showRoutes(routes);\n *\n * // User clicks alternative route\n * await routingModule.selectRoute(1);\n *\n * // Switch back to first route\n * await routingModule.selectRoute(0);\n * ```\n */\n async selectRoute(index: number) {\n const updatedRoutes = toDisplayRoutes(this.sourcesWithLayers.mainLines.shownFeatures, index);\n\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.mainLines.show(updatedRoutes);\n // TODO: simply update route style instead of regenerating EV stations again\n this.sourcesWithLayers.chargingStops.show(toDisplayChargingStops(updatedRoutes, this.config));\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.vehicleRestricted);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.incidents);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.ferries);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.tollRoads);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.tunnels);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.instructionLines);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.instructionArrows);\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.summaryBubbles);\n }\n\n /**\n * Displays waypoint markers (start, stop, and destination pins) on the map.\n *\n * @param waypoints - An array of planning waypoints or a `Waypoints` FeatureCollection.\n *\n * @remarks\n * Waypoint markers are **not** shown by {@link showRoutes} — this method must be called\n * separately to display them. For more control over marker appearance and behavior,\n * consider using {@link PlacesModule} to render waypoints as place markers instead.\n *\n * @example\n * Show routes and then waypoints:\n * ```typescript\n * const locations = [[4.9041, 52.3676], [4.4777, 51.9244]];\n * const response = await calculateRoute({ locations });\n * await routingModule.showRoutes(response.routes);\n * await routingModule.showWaypoints(locations); // Must be called separately\n * ```\n */\n async showWaypoints(waypoints: PlanningWaypoint[] | Waypoints) {\n const displayWaypoints = Array.isArray(waypoints)\n ? toDisplayWaypoints(waypoints, this.config?.waypoints, this.instanceIndex)\n : // FeatureCollection expected:\n toDisplayWaypoints(waypoints.features as PlanningWaypoint[], this.config?.waypoints, this.instanceIndex);\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.waypoints.show(displayWaypoints);\n for (const handler of this.shownFeaturesHandlers) {\n handler({ waypoints });\n }\n }\n\n /**\n * Clears any previously shown waypoints from the map.\n * If nothing was shown before, nothing happens.\n */\n async clearWaypoints() {\n await this.waitUntilModuleReady();\n this.sourcesWithLayers.waypoints.clear();\n }\n\n /**\n * Returns the currently shown routes and waypoints.\n *\n * @returns An object containing all currently displayed routing data.\n *\n * @remarks\n * Returns the exact data that was passed to the `showRoutes()` and `showWaypoints()` methods.\n *\n * **Returned Data:**\n * - `mainLines`: Main route lines (selected and alternative)\n * - `waypoints`: Route waypoints (start, stops, finish)\n * - `incidents`: Traffic incidents on routes\n * - `ferries`: Ferry sections\n * - `chargingStops`: EV charging stations\n * - `tollRoads`: Toll road sections\n * - `tunnels`: Tunnel sections\n * - `vehicleRestricted`: Vehicle-restricted sections\n * - `instructionLines`: Turn-by-turn instruction lines\n * - `instructionArrows`: Instruction arrow markers\n * - `summaryBubbles`: Route summary popups\n *\n * @example\n * ```typescript\n * const shown = routingModule.getShown();\n * console.log(`Showing ${shown.mainLines.features.length} routes`);\n * console.log(`Showing ${shown.waypoints.features.length} waypoints`);\n * console.log(`Showing ${shown.chargingStops.features.length} charging stops`);\n * ```\n *\n * @example\n * Check if any routes are displayed:\n * ```typescript\n * const shown = routingModule.getShown();\n * if (shown.mainLines.features.length > 0) {\n * console.log('Routes are displayed');\n * } else {\n * console.log('No routes displayed');\n * }\n * ```\n */\n getShown() {\n return {\n mainLines: this.sourcesWithLayers.mainLines.shownFeatures,\n waypoints: this.sourcesWithLayers.waypoints.shownFeatures,\n incidents: this.sourcesWithLayers.incidents.shownFeatures,\n ferries: this.sourcesWithLayers.ferries.shownFeatures,\n chargingStops: this.sourcesWithLayers.chargingStops.shownFeatures,\n tollRoads: this.sourcesWithLayers.tollRoads.shownFeatures,\n tunnels: this.sourcesWithLayers.tunnels.shownFeatures,\n vehicleRestricted: this.sourcesWithLayers.vehicleRestricted.shownFeatures,\n instructionLines: this.sourcesWithLayers.instructionLines.shownFeatures,\n instructionArrows: this.sourcesWithLayers.instructionArrows.shownFeatures,\n summaryBubbles: this.sourcesWithLayers.summaryBubbles.shownFeatures,\n };\n }\n\n /**\n * Unified events interface for the routing module, split into two namespaces:\n *\n * **`events.user`** — user interaction events, keyed by source:\n * ```typescript\n * routing.events.user.mainLines.on('click', (route) => { ... });\n * routing.events.user.waypoints.on('hover', (waypoint) => { ... });\n * ```\n *\n * **`events.module`** — module lifecycle events:\n * ```typescript\n * const unsub = routing.events.module.on('config-change', (config) => { ... });\n * const unsub = routing.events.module.on('shown-features', (features) => {\n * if ('routes' in features) { ... }\n * });\n * unsub();\n * ```\n */\n get events(): {\n user: {\n mainLines: UserEvents<Route<DisplayRouteProps>>;\n waypoints: UserEvents<Waypoint<WaypointDisplayProps>>;\n chargingStops: UserEvents<RouteSection>;\n summaryBubbles: UserEvents<DisplayRouteSummary>;\n incidents: UserEvents<RouteSection<DisplayTrafficSectionProps>>;\n vehicleRestricted: UserEvents<RouteSection>;\n ferries: UserEvents<RouteSection>;\n tollRoads: UserEvents<RouteSection>;\n tunnels: UserEvents<RouteSection>;\n instructionLines: UserEvents<DisplayInstruction>;\n };\n module: ModuleEvents<\n RoutingModuleConfig,\n { routes: Route | Routes } | { waypoints: PlanningWaypoint[] | Waypoints }\n >;\n } {\n return {\n user: {\n mainLines: new UserEvents<Route<DisplayRouteProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.mainLines,\n this.config?.events,\n ),\n waypoints: new UserEvents<Waypoint<WaypointDisplayProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.waypoints,\n this.config?.events,\n ),\n chargingStops: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.chargingStops,\n this.config?.events,\n ),\n summaryBubbles: new UserEvents<DisplayRouteSummary>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.summaryBubbles,\n this.config?.events,\n ),\n incidents: new UserEvents<RouteSection<DisplayTrafficSectionProps>>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.incidents,\n this.config?.events,\n ),\n vehicleRestricted: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.vehicleRestricted,\n this.config?.events,\n ),\n ferries: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.ferries,\n this.config?.events,\n ),\n tollRoads: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.tollRoads,\n this.config?.events,\n ),\n tunnels: new UserEvents<RouteSection>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.tunnels,\n this.config?.events,\n ),\n instructionLines: new UserEvents<DisplayInstruction>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.instructionLines,\n this.config?.events,\n ),\n },\n module: new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n };\n }\n\n /**\n * Returns the map style layer under which route lines are rendered.\n * * Useful if you want to render extra layers just above the route ones but not on top of everything else.\n * * It might differ depending on the loaded style/version.\n */\n getLayerToRenderLinesUnder(): string {\n return mapStyleLayerIDs.lowestLabel;\n }\n}\n","import type { StyleSpecification } from 'maplibre-gl';\nimport type { InternalTomTomMapParams, StandardStyle, StandardStyleID, StyleInput, StyleModule } from './types/mapInit';\nimport { styleModules } from './types/mapInit';\n\nexport const DEFAULT_STANDARD_STYLE_ID: StandardStyleID = 'standardLight';\n\nconst standardStyleModulesValues: Record<StandardStyleID, Record<StyleModule, string>> = {\n standardLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_light',\n },\n standardDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_dark',\n },\n drivingLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_light',\n },\n drivingDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_dark',\n },\n monoLight: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_mono-light',\n },\n monoDark: {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n hillshade: 'hillshade_mono-dark',\n },\n satellite: {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n hillshade: 'hillshade_satellite',\n },\n};\n\nconst mapSuffixes: Record<StandardStyleID, string> = {\n standardLight: 'basic_street-light',\n standardDark: 'basic_street-dark',\n drivingLight: 'basic_street-light-driving',\n drivingDark: 'basic_street-dark-driving',\n monoLight: 'basic_mono-light',\n monoDark: 'basic_mono-dark',\n satellite: 'basic_street-satellite',\n};\n\nconst buildStandardStyleUrl = (standardStyle: StandardStyle, baseUrl: string, apiKey: string): string => {\n const standardStyleID = standardStyle.id ?? DEFAULT_STANDARD_STYLE_ID;\n const version = standardStyle.version ?? '0.6.0-0';\n\n const styleURL = new URL(`${baseUrl}/maps/orbis/assets/styles/${version}/style.json`);\n // Param order is asserted by tests and matches the order historically\n // baked into URL_PREFIX: apiVersion, key, map, then modules.\n styleURL.searchParams.set('apiVersion', '1');\n // Proxy mode passes apiKey='' and lets the proxy inject the real key\n // server-side — skip the param so the proxy key never reaches the browser.\n if (apiKey) {\n styleURL.searchParams.set('key', apiKey);\n }\n styleURL.searchParams.set('map', mapSuffixes[standardStyleID]);\n\n for (const module of standardStyle.include ?? styleModules) {\n styleURL.searchParams.append(module, standardStyleModulesValues[standardStyleID][module]);\n }\n\n return styleURL.toString();\n};\n\nconst withApiKey = (givenUrl: string, apiKey: string): string => {\n const url = new URL(givenUrl);\n if (!url.searchParams.has('key')) {\n // In proxy mode the apiKey is intentionally empty; the proxy adds\n // the real key server-side. Don't write an empty `key=` param.\n if (apiKey) {\n url.searchParams.set('key', apiKey);\n }\n } else {\n console.warn(\n 'The style URL is coming with an API key parameter which takes priority. ' +\n 'If you want to use the SDK configured API key, remove the key param from the style URL',\n );\n }\n return url.toString();\n};\n\n/**\n * @ignore\n * @param mapParams The SDK parameters to convert to input renderer style.\n * @return The map style to load into the renderer.\n */\nexport const buildStyleInput = (mapParams: InternalTomTomMapParams): StyleSpecification | string => {\n const style = mapParams.style;\n const baseUrl = mapParams.commonBaseURL;\n const apiKey = mapParams.apiKey;\n\n if (typeof style === 'string') {\n return buildStandardStyleUrl({ id: style }, baseUrl, apiKey);\n } else if (style?.type === 'standard') {\n return buildStandardStyleUrl(style, baseUrl, apiKey);\n } else if (style?.type === 'custom' && style?.url) {\n return withApiKey(style.url, apiKey);\n } else if (style?.type === 'custom' && style?.json) {\n return style.json;\n }\n\n // no style defined, use default\n return buildStandardStyleUrl({ id: DEFAULT_STANDARD_STYLE_ID }, baseUrl, apiKey);\n};\n\n/**\n * Includes the previous standard style parts into the given standard style if the new one didn't define any.\n * * Both new and previous styles must be of \"standard\" type.\n * @ignore\n */\nexport const withPreviousStyleParts = (style: StyleInput, previousStyle?: StyleInput): StyleInput => {\n if (\n previousStyle &&\n typeof previousStyle === 'object' &&\n previousStyle.type === 'standard' &&\n previousStyle.include\n ) {\n if (typeof style === 'string' || (style.type === 'standard' && !style.include)) {\n return {\n type: 'standard',\n id: typeof style === 'string' ? style : style.id,\n include: (previousStyle as StandardStyle).include,\n };\n }\n }\n return style;\n};\n","import type { MapOptions } from 'maplibre-gl';\nimport { transformRequest } from '../shared/mapUtils';\nimport { buildStyleInput } from './styleInputBuilder';\nimport type { InternalTomTomMapParams } from './types/mapInit';\n\n/**\n * @ignore\n * @param tomtomMapParams\n */\nexport const buildMapOptions = (tomtomMapParams: InternalTomTomMapParams): MapOptions => {\n return {\n // defaults (can be overwritten by given options)\n validateStyle: false,\n maxTileCacheZoomLevels: 22,\n cancelPendingTileRequestsWhileZooming: false,\n // given options:\n ...tomtomMapParams.mapLibre,\n // SDK overrides (won't have any effect via given options):\n style: buildStyleInput(tomtomMapParams),\n attributionControl: { compact: false },\n transformRequest: transformRequest(tomtomMapParams),\n };\n};\n","import type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\n\n/**\n * @ignore\n */\nexport const isLayerLocalizable = (layer: SymbolLayerSpecification): boolean => {\n const textField = (layer.layout?.['text-field'] ?? '') as string | ExpressionSpecification;\n return textField\n ? // tries to detect layers which have a \"text-field\" that can be localized\n // ex. \"text-field\": \"{name}\" or \"text-field\": [\"get\", \"name\"]\n textField === '{name}' ||\n (textField.length === 2 && textField[1] === 'name') ||\n // tries to detect layers which have \"text-field\" that was localized already\n // ex. \"text-field\": [\"coalesce\", [\"get\", \"name_en\"], [\"get\", \"name\"]]\n (textField.length === 3 &&\n Array.isArray(textField[1]) &&\n typeof textField[1][1] === 'string' &&\n textField[1][1].includes('name_') &&\n Array.isArray(textField[2]) &&\n textField[2].includes('name'))\n : false;\n};\n","import { type BBox, type Language, mergeFromGlobal } from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport { getRTLTextPluginStatus, Map, setRTLTextPlugin, setWorkerCount } from 'maplibre-gl';\nimport { version as maplibreVersion } from 'maplibre-gl/package.json';\nimport type { InternalTomTomMapParams, MapLibreOptions, StyleInput, TomTomMapParams } from './init';\nimport { buildMapOptions } from './init/buildMapOptions';\nimport { buildStyleInput, DEFAULT_STANDARD_STYLE_ID, withPreviousStyleParts } from './init/styleInputBuilder';\nimport {\n EventsProxy,\n filterLayersBySources,\n HILLSHADE_SOURCE_ID,\n LightDark,\n TRAFFIC_FLOW_SOURCE_ID,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n} from './shared';\nimport { isLayerLocalizable } from './shared/localization';\nimport { addPinCategoriesSpriteToStyle, getStyleLightDarkTheme } from './shared/mapUtils';\n\n/**\n * Handler interface for responding to map style changes.\n *\n * @remarks\n * This interface defines callbacks that are invoked when the map style changes via {@link TomTomMap.setStyle}.\n * Use this to perform cleanup or reinitialization of custom map features when styles are switched.\n *\n * **Lifecycle:**\n * 1. `onStyleAboutToChange` - Called before the new style is applied\n * 2. Style change occurs\n * 3. `onStyleChanged` - Called after the new style has been fully loaded\n *\n * **Common Use Cases:**\n * - Saving and restoring custom layers or sources\n * - Reinitializing map modules after style changes\n * - Updating UI components based on the new style\n * - Cleaning up resources tied to the previous style\n *\n * @example\n * ```typescript\n * const styleHandler: StyleChangeHandler = {\n * onStyleAboutToChange: () => {\n * console.log('Style changing - saving state...');\n * // Save custom layer data\n * },\n * onStyleChanged: () => {\n * console.log('Style changed - restoring state...');\n * // Restore custom layers\n * }\n * };\n *\n * const unsubscribe = map.addStyleChangeHandler(styleHandler);\n * // Call unsubscribe() when the handler's owner is torn down.\n * ```\n *\n * @see {@link TomTomMap.addStyleChangeHandler}\n * @see {@link TomTomMap.setStyle}\n *\n * @group Map Style\n */\nexport type StyleChangeHandler = {\n /**\n * Callback invoked immediately before a style change begins.\n *\n * @remarks\n * Use this to perform cleanup or save state before the current style is removed.\n * This method can be synchronous or asynchronous.\n *\n * @returns void or a Promise that resolves when preparation is complete\n */\n onStyleAboutToChange?: () => void | Promise<void>;\n /**\n * Callback invoked after a new style has been fully loaded.\n *\n * @remarks\n * Use this to restore state, reinitialize layers, or perform other setup\n * that depends on the new style being ready. This method can be synchronous or asynchronous.\n *\n * @returns void or a Promise that resolves when reinitialization is complete\n */\n onStyleChanged?: () => void | Promise<void>;\n};\n\n/**\n * Main TomTom Map class for displaying interactive maps in web applications.\n *\n * This is the entry point for rendering TomTom maps. It wraps MapLibre GL JS and provides\n * a simplified, enhanced API for common mapping tasks.\n *\n * @remarks\n * **Key Features:**\n * - Built on MapLibre GL JS for high-performance rendering\n * - Seamless style switching without map reload\n * - Integrated event handling system\n * - Multi-language support with dynamic switching\n * - Compatible with TomTom map modules (traffic, POIs, routing, etc.)\n *\n * **Architecture:**\n * - Exposes the underlying MapLibre Map instance via {@link mapLibreMap}\n * - Manages map lifecycle and style transitions\n * - Coordinates with map modules for data visualization\n *\n * @example\n * Basic map initialization:\n * ```typescript\n * import { TomTomMap } from '@tomtom-international/maps-sdk-js/map';\n *\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: 'standardLight',\n * mapLibre: {\n * container: 'map',\n * center: [4.9041, 52.3676],\n * zoom: 10\n * }\n * });\n * ```\n *\n * @example\n * With modules and configuration:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: {\n * type: 'standard',\n * id: 'standardDark',\n * include: ['trafficFlow', 'trafficIncidents']\n * },\n * language: 'en-US',\n * events: {\n * precisionMode: 'point-then-box',\n * cursorOnHover: 'pointer'\n * },\n * mapLibre: {\n * container: 'map',\n * center: [-74.006, 40.7128],\n * zoom: 12\n * }\n * });\n *\n * // Access MapLibre functionality directly\n * map.mapLibreMap.on('load', () => {\n * console.log('Map loaded');\n * });\n * ```\n *\n * @group Map\n */\nexport class TomTomMap {\n /**\n * Indicates whether the map style has been fully loaded and is ready for interaction.\n *\n * @remarks\n * - `true` when the style is loaded and modules can be safely initialized\n * - `false` during map construction or style changes\n * - Check this before performing style-dependent operations\n *\n * @example\n * ```typescript\n * if (map.mapReady) {\n * // Safe to initialize modules\n * const trafficFlowModule = await TrafficFlowModule.get(map);\n * }\n * ```\n */\n mapReady = false;\n\n /**\n * The underlying MapLibre GL JS Map instance.\n *\n * @remarks\n * **When to Use:**\n * - Access advanced MapLibre functionality not exposed by TomTomMap\n * - Add custom layers, sources, or controls\n * - Listen to MapLibre-specific events\n * - Integrate third-party MapLibre plugins\n *\n * **Important:**\n * - Available immediately after TomTomMap construction\n * - Direct modifications may affect SDK module behavior\n * - Coordinate with SDK modules to avoid conflicts\n *\n * @example\n * Add custom layer:\n * ```typescript\n * map.mapLibreMap.addLayer({\n * id: 'custom-layer',\n * type: 'circle',\n * source: 'my-data',\n * paint: {\n * 'circle-radius': 6,\n * 'circle-color': '#ff0000'\n * }\n * });\n * ```\n *\n * @example\n * Listen to events:\n * ```typescript\n * map.mapLibreMap.on('moveend', () => {\n * console.log('Camera position:', map.mapLibreMap.getCenter());\n * });\n * ```\n *\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/map/ | MapLibre Map Documentation}\n */\n readonly mapLibreMap: Map;\n /**\n * @ignore\n */\n readonly _eventsProxy: EventsProxy;\n /**\n * @ignore\n */\n _params: InternalTomTomMapParams;\n\n styleLightDarkTheme: LightDark;\n\n private readonly styleChangeHandlers: StyleChangeHandler[] = [];\n\n /**\n * Constructs a new TomTom Map instance and attaches it to a DOM element.\n *\n * @param mapParams - Combined TomTom and MapLibre parameters for map initialization.\n * Includes API key, style, events, and MapLibre options like container, center, zoom, etc.\n * See {@link TomTomMapParams} for all available parameters.\n *\n * @remarks\n * **Initialization Process:**\n * 1. Merges `mapParams` with global configuration\n * 2. Creates underlying MapLibre map instance\n * 3. Loads specified style asynchronously\n * 4. Sets `mapReady` to `true` when complete\n *\n * **Configuration Priority:**\n * - Parameters passed here override global configuration\n * - Allows per-map customization while sharing common settings\n *\n * @example\n * Minimal initialization:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * mapLibre: {\n * container: 'map',\n * center: [0, 0],\n * zoom: 2\n * }\n * });\n * ```\n *\n * @example\n * Full configuration:\n * ```typescript\n * const map = new TomTomMap({\n * key: 'YOUR_API_KEY',\n * style: {\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * },\n * language: 'en-US',\n * events: {\n * precisionMode: 'point-then-box',\n * paddingBoxPx: 10\n * },\n * mapLibre: {\n * container: 'map',\n * center: [-122.4194, 37.7749],\n * zoom: 13,\n * pitch: 45,\n * bearing: -17.6,\n * antialias: true,\n * maxZoom: 18,\n * minZoom: 8\n * }\n * });\n * ```\n *\n * @throws Will log errors if RTL text plugin fails to load (non-blocking)\n *\n * @see {@link MapLibreOptions}\n * @see {@link TomTomMapParams}\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/map/ | MapLibre Map Parameters}\n * @see [Map Quickstart Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/quickstart)\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n * @see [User Interaction Events Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/user-events)\n */\n constructor(mapParams: TomTomMapParams) {\n this._params = mergeFromGlobal(mapParams);\n if (this._params.style === undefined) {\n this._params = { ...this._params, style: DEFAULT_STANDARD_STYLE_ID };\n }\n this.styleLightDarkTheme = getStyleLightDarkTheme(this._params.style);\n this.ensureMapLibreCSSLoaded();\n\n // Set worker count before creating the Map instance.\n // MapLibre defaults to 1 worker (3 on Safari). 4 workers gives smooth\n // tile-by-tile transitions during style changes without starving the main thread.\n setWorkerCount(4);\n\n this.mapLibreMap = new Map(buildMapOptions(this._params));\n // Use `style.load` (fires once after style is fully parsed and all layers are created)\n // rather than `styledata` (fires repeatedly mid-load) — see setStyle for the reasoning.\n this.mapLibreMap.once('style.load', () => {\n this.handleStyleData(false);\n });\n this._eventsProxy = new EventsProxy(this.mapLibreMap, this._params?.events);\n\n this.loadRTLTextPlugin();\n }\n\n private loadRTLTextPlugin(): void {\n // deferred (just in case), lazy loading of the RTL plugin:\n setTimeout(() => {\n if (!['deferred', 'loaded'].includes(getRTLTextPluginStatus())) {\n setRTLTextPlugin(\n 'https://unpkg.com/@mapbox/mapbox-gl-rtl-text@0.3.0/dist/mapbox-gl-rtl-text.js',\n true,\n ).catch((error) => console.error('Something went wrong when setting RTL plugin', error));\n }\n });\n }\n\n /**\n * Dynamically loads the MapLibre CSS stylesheet from CDN.\n */\n private ensureMapLibreCSSLoaded(): void {\n if (typeof document === 'undefined') {\n return;\n }\n // Check if the CSS is already loaded to avoid duplicates:\n const existingLink = Array.from(document.querySelectorAll('link[rel=\"stylesheet\"], style')).some((element) =>\n element.textContent?.includes('.maplibregl-map'),\n );\n if (existingLink) {\n return;\n }\n\n // Create and inject a link tag to load CSS from unpkg CDN\n const link = document.createElement('link');\n link.rel = 'stylesheet';\n link.href = `https://unpkg.com/maplibre-gl@${maplibreVersion}/dist/maplibre-gl.css`;\n document.head.appendChild(link);\n }\n\n /**\n * Changes the map style dynamically without reloading the entire map.\n *\n * @param style - The new style to apply. Can be a string ID or a detailed style configuration.\n * @param options - Configuration options for the style change behavior.\n * @param options.keepState - Whether to preserve SDK-rendered items and configurations when changing styles.\n * When `true` (default), maintains traffic layers, routes, markers, and other SDK features.\n * When `false`, performs a clean style switch without preserving previous state.\n *\n * @remarks\n * **Behavior:**\n * - Temporarily sets {@link mapReady} to `false` during the transition\n * - Triggers all registered {@link StyleChangeHandler} callbacks\n * - Resets {@link mapReady} to `true` when the new style is fully loaded\n *\n * **State Preservation (keepState: true):**\n * - Merges style parts from the previous style with the new one\n * - Maintains SDK module layers (traffic, routes, POIs, etc.)\n * - Preserves language settings\n *\n * **Clean Switch (keepState: false):**\n * - Applies the new style without merging previous configuration\n * - Removes all SDK module layers\n * - Useful for complete style resets\n *\n * @example\n * Simple style change:\n * ```typescript\n * // Switch to dark mode\n * map.setStyle('standardDark');\n * ```\n *\n * @example\n * Style change with detailed configuration:\n * ```typescript\n * map.setStyle({\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'hillshade']\n * });\n * ```\n *\n * @example\n * Clean style switch without state preservation:\n * ```typescript\n * // Complete reset - removes all SDK layers and modules\n * map.setStyle('standardDark', { keepState: false });\n * ```\n *\n * @example\n * With style change handlers:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * console.log('Preparing for style change...');\n * },\n * onStyleChanged: () => {\n * console.log('New style applied!');\n * }\n * });\n *\n * map.setStyle('standardDark');\n * ```\n *\n * @see {@link TomTomMapParams.style} - For setting style during initialization\n * @see {@link StyleChangeHandler} - For handling style change events\n * @see {@link getStyle} - For retrieving the current style\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/map-styles)\n */\n setStyle = (style: StyleInput, options: { keepState?: boolean } = { keepState: true }): void => {\n this.mapReady = false;\n\n // Notify all modules that the style is about to change (they mark themselves as not ready).\n // Iterate a snapshot so a handler that unsubscribes itself (or another) during the callback\n // doesn't shift the live array out from under the loop.\n for (const handler of [...this.styleChangeHandlers]) {\n try {\n handler.onStyleAboutToChange?.();\n } catch (e) {\n console.error(e);\n }\n }\n const effectiveStyle = options.keepState ? withPreviousStyleParts(style, this._params.style) : style;\n this._params = { ...this._params, style: effectiveStyle };\n this.styleLightDarkTheme = getStyleLightDarkTheme(effectiveStyle);\n this.mapLibreMap.setStyle(buildStyleInput(this._params), { validate: false });\n // `style.load` fires once after MapLibre finishes applying all diff operations from the\n // new style — at that point every new layer exists in `map.getStyle().layers`, which is\n // what the module-restoration snapshot needs. Using `styledata` here would race because\n // it fires repeatedly during the diff and may run `handleStyleData` before all layers\n // are present.\n this.mapLibreMap.once('style.load', () => {\n // We only handle the style data change if the applied style is still the same as the one we set,\n // to prevent race conditions when handling stale styles applied quickly in succession.\n if (!this.mapReady && isEqual(effectiveStyle, this._params.style)) {\n this.handleStyleData(options.keepState || true);\n }\n });\n };\n\n /**\n * Retrieves the current style configuration of the map.\n *\n * @returns The current {@link StyleInput} configuration, or `undefined` if no style is set.\n *\n * @remarks\n * Returns the style configuration as it was set, not the fully resolved MapLibre style object.\n * Use this to inspect or store the current style configuration for later restoration.\n *\n * **Return Value:**\n * - String ID (e.g., `'standardLight'`) for simple style configurations\n * - Style object with `type`, `id`, and optional `include` properties for detailed configurations\n * - `undefined` if no style has been explicitly set\n *\n * @example\n * ```typescript\n * const currentStyle = map.getStyle();\n * console.log('Current style:', currentStyle);\n *\n * // Save style for later\n * const savedStyle = map.getStyle();\n *\n * // Later, restore it\n * if (savedStyle) {\n * map.setStyle(savedStyle);\n * }\n * ```\n *\n * @example\n * Conditional logic based on current style:\n * ```typescript\n * const style = map.getStyle();\n * if (typeof style === 'string' && style.includes('Dark')) {\n * console.log('Dark mode is active');\n * }\n * ```\n *\n * @see {@link setStyle} - For changing the map style\n * @see {@link StyleInput} - For available style configuration options\n */\n getStyle = (): StyleInput | undefined => {\n return this._params.style;\n };\n\n private _setLanguage(language: Language) {\n this._params = { ...this._params, language };\n const mapLanguage = language?.includes('-') ? language.split('-')[0] : language;\n this.mapLibreMap.getStyle().layers.forEach((layer) => {\n if (layer.type === 'symbol' && isLayerLocalizable(layer)) {\n const textFieldValue = mapLanguage\n ? ['coalesce', ['get', `name_${mapLanguage}`], ['get', 'name']]\n : ['get', 'name'];\n this.mapLibreMap.setLayoutProperty(layer.id, 'text-field', textFieldValue, { validate: false });\n }\n });\n }\n\n /**\n * Changes the language of the map.\n * * You can use this method to change the language at runtime.\n * * To set the language upon initialization, you can better do it via {@link core!TomTomConfig global config}\n * or {@link TomTomMapParams}.\n * @param language The language to be used in map translations.\n *\n * @remarks\n * **Behavior:**\n * - Updates all localizable map labels to the specified language\n * - Falls back to the default label name if the requested language is unavailable\n * - Can be called before or after the map is fully loaded\n * - If called before map is ready, will apply once the style loads\n *\n * **Language Format:**\n * - Simple language codes: `'en'`, `'fr'`, `'de'`, `'ja'`, `'zh'`\n * - Locale-specific codes: `'en-US'`, `'en-GB'`, `'zh-CN'`, `'pt-BR'`\n * - When using locale codes (with `-`), only the language portion is used for labels\n *\n * **Persistence:**\n * - Language setting persists across style changes (when `keepState: true`)\n * - Set during initialization via {@link TomTomMapParams.language} for immediate application\n *\n * @example\n * Change language at runtime:\n * ```typescript\n * // Switch to French\n * map.setLanguage('fr');\n * ```\n *\n * @example\n * Use locale-specific codes:\n * ```typescript\n * // Use Simplified Chinese\n * map.setLanguage('zh-CN');\n *\n * // Use Brazilian Portuguese\n * map.setLanguage('pt-BR');\n * ```\n *\n * @example\n * Language switcher UI:\n * ```typescript\n * const languageSelector = document.getElementById('lang-select');\n * languageSelector.addEventListener('change', (e) => {\n * map.setLanguage(e.target.value);\n * });\n * ```\n *\n * @see {@link TomTomMapParams.language} - For setting language during initialization\n * @see {@link https://en.wikipedia.org/wiki/List_of_ISO_639-1_codes | ISO 639-1 Language Codes}\n */\n setLanguage(language: Language) {\n if (this.mapReady) {\n this._setLanguage(language);\n } else {\n this.mapLibreMap.once('styledata', () => this.setLanguage(language));\n }\n }\n\n /**\n * Retrieves the current visible map area as a GeoJSON bounding box.\n *\n * @returns A {@link https://tools.ietf.org/html/rfc7946#section-5 | GeoJSON BBox} array\n * in the format `[west, south, east, north]` representing the map's current viewport bounds.\n *\n * @remarks\n * **Return Format:**\n * - Array of four numbers: `[minLongitude, minLatitude, maxLongitude, maxLatitude]`\n * - Coordinates are in WGS84 decimal degrees\n * - West/East values range from -180 to 180\n * - South/North values range from -90 to 90\n *\n * @example\n * Get current bounds:\n * ```typescript\n * const bbox = map.getBBox();\n * console.log('Bounds:', bbox);\n * // Output: [-122.5, 37.7, -122.3, 37.8]\n * // [west, south, east, north]\n * ```\n *\n * @example\n * Use bounds for spatial query:\n * ```typescript\n * const bbox = map.getBBox();\n * const results = await searchAPI.searchInBoundingBox({\n * bbox: bbox,\n * query: 'restaurants'\n * });\n * ```\n *\n * @example\n * Save and restore map view:\n * ```typescript\n * // Save current view\n * const savedBounds = map.getBBox();\n * const savedZoom = map.mapLibreMap.getZoom();\n *\n * // Later, restore the view\n * const [west, south, east, north] = savedBounds;\n * map.mapLibreMap.fitBounds([[west, south], [east, north]]);\n * ```\n *\n * @see {@link https://tools.ietf.org/html/rfc7946#section-5 | GeoJSON BBox Specification}\n * @see {@link https://maplibre.org/maplibre-gl-js-docs/api/geography/#lnglatbounds | MapLibre LngLatBounds}\n */\n getBBox(): BBox {\n return this.mapLibreMap.getBounds().toArray().flat() as BBox;\n }\n\n private handleStyleData(keepState: boolean) {\n // We ensure to make traffic and hillshade hidden by default (even if right after the modules bring it back to visible state)\n // This way we ensure such layers are invisible even of their related SDK modules are not used.\n for (const layer of filterLayersBySources(this.mapLibreMap, [\n TRAFFIC_INCIDENTS_SOURCE_ID,\n TRAFFIC_FLOW_SOURCE_ID,\n HILLSHADE_SOURCE_ID,\n ])) {\n this.mapLibreMap.setLayoutProperty(layer.id, 'visibility', 'none', { validate: false });\n }\n\n // For most use cases we'll need to have pins available (places, routing...) so we add them by default:\n // (subsequent loads for the same sprite should be cached)\n addPinCategoriesSpriteToStyle(this._params, this.mapLibreMap);\n // We restore the language if it was set before:\n this._params.language && this._setLanguage(this._params.language);\n\n this.mapReady = true;\n if (keepState) {\n // Snapshot: a handler may unsubscribe during onStyleChanged (e.g. an overlay tearing down).\n for (const handler of [...this.styleChangeHandlers]) {\n try {\n handler.onStyleChanged?.();\n } catch (e) {\n console.error(e);\n }\n }\n }\n }\n\n /**\n * Registers a handler to be notified when the map style changes.\n *\n * @param handler - A {@link StyleChangeHandler} object with callbacks for style change events.\n *\n * @returns An unsubscribe function that removes this handler. Call it when the\n * handler's owner (e.g. a custom overlay) is torn down — otherwise the handler,\n * and everything it closes over, lives for the lifetime of the map. Mirrors the\n * disposer returned by module event handlers (`module.events.on(...)`).\n *\n * @remarks\n * **When to Use:**\n * - You have custom layers or sources that need to be recreated after style changes\n * - Your application needs to respond to style switches (e.g., light/dark mode transitions)\n * - You need to save and restore state during style changes\n * - Map modules need to reinitialize when styles change\n *\n * **Handler Lifecycle:**\n * 1. `onStyleAboutToChange()` - Called before the style change begins\n * 2. Style change occurs\n * 3. `onStyleChanged()` - Called after the new style has been fully loaded\n *\n * **Multiple Handlers:**\n * - Multiple handlers can be registered and will all be called in registration order\n * - Each handler's errors are caught independently and logged to the console\n * - One failing handler won't prevent others from executing\n *\n * **Important Notes:**\n * - Handlers are only triggered by {@link setStyle} calls, not initial map construction\n * - Only called when `keepState: true` in {@link setStyle} options\n * - Handlers persist for the lifetime of the TomTomMap instance\n *\n * @example\n * Basic usage:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * console.log('Style is changing...');\n * },\n * onStyleChanged: () => {\n * console.log('Style changed successfully!');\n * }\n * });\n *\n * // Later trigger the handlers\n * map.setStyle('standardDark');\n * ```\n *\n * @example\n * Unsubscribe when the owner is torn down:\n * ```typescript\n * const unsubscribe = map.addStyleChangeHandler({\n * onStyleChanged: () => reattachCustomLayers(),\n * });\n *\n * // Later, when the overlay is removed:\n * unsubscribe();\n * ```\n *\n * @example\n * Preserve custom layers across style changes:\n * ```typescript\n * let customLayerData = null;\n *\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * // Save custom layer data before style changes\n * if (map.mapLibreMap.getLayer('my-custom-layer')) {\n * customLayerData = map.mapLibreMap.getSource('my-data')._data;\n * map.mapLibreMap.removeLayer('my-custom-layer');\n * map.mapLibreMap.removeSource('my-data');\n * }\n * },\n * onStyleChanged: () => {\n * // Restore custom layer after new style is loaded\n * if (customLayerData) {\n * map.mapLibreMap.addSource('my-data', {\n * type: 'geojson',\n * data: customLayerData\n * });\n * map.mapLibreMap.addLayer({\n * id: 'my-custom-layer',\n * type: 'circle',\n * source: 'my-data',\n * paint: { 'circle-radius': 6, 'circle-color': '#007cbf' }\n * });\n * }\n * }\n * });\n * ```\n *\n * @example\n * Async handler for external API calls:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: async () => {\n * await saveStateToAPI(map.getStyle());\n * },\n * onStyleChanged: async () => {\n * await loadStateFromAPI();\n * }\n * });\n * ```\n *\n * @example\n * Update UI based on style:\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleAboutToChange: () => {\n * document.body.classList.add('style-changing');\n * },\n * onStyleChanged: () => {\n * document.body.classList.remove('style-changing');\n * const style = map.getStyle();\n * if (typeof style === 'string' && style.includes('Dark')) {\n * document.body.classList.add('dark-mode');\n * } else {\n * document.body.classList.remove('dark-mode');\n * }\n * }\n * });\n * ```\n *\n * @see {@link StyleChangeHandler} - Handler interface definition\n * @see {@link setStyle} - Method that triggers the handlers\n */\n addStyleChangeHandler(handler: StyleChangeHandler): () => void {\n this.styleChangeHandlers.push(handler);\n let disposed = false;\n return () => {\n if (disposed) return;\n disposed = true;\n const index = this.styleChangeHandlers.indexOf(handler);\n if (index !== -1) this.styleChangeHandlers.splice(index, 1);\n };\n }\n}\n","import type { AreaAnalyticsMetricKey } from '@tomtom-org/maps-sdk/core';\nimport type { BeforeLayerConfig, MapModuleCommonConfig } from '../../shared';\n\n/**\n * A single stop in an analytics color ramp.\n *\n * How `value` is interpreted depends on the surrounding `AreaAnalyticsColorStopsConfig.valueType`.\n * `color` is any valid CSS color string.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorStop = { value: number; color: string };\n\n/**\n * Visualization mode for area analytics data.\n *\n * - `'hexgrid-3d'` — 3D extruded hexagonal cells colored and raised by metric value\n * - `'hexgrid-2d'` — Flat hexagonal cells colored by metric value (no extrusion)\n * - `'square-3d'` — 3D extruded square cells colored and raised by metric value\n * - `'square-2d'` — Flat square cells colored by metric value (no extrusion)\n * - `'heatmap'` — MapLibre density heatmap layer from tile centre points\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsDisplayMode = 'heatmap' | 'hexgrid-3d' | 'hexgrid-2d' | 'square-3d' | 'square-2d';\n\n/**\n * Preset color theme for area analytics visualization.\n *\n * - `'trafficLight'` — Green → amber → red (default, traffic-signal style)\n * - `'heat'` — Blue → orange → red (heat-map style)\n * - `'monochrome'` — Light grey → dark grey (print-friendly)\n * - `'viridis'` — Purple → blue → green → yellow (perceptually uniform, colorblind-safe)\n * - `'plasma'` — Purple → magenta → orange → yellow (vivid, perceptually uniform)\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorTheme = 'trafficLight' | 'heat' | 'monochrome' | 'viridis' | 'plasma';\n\n/**\n * Determines how numeric `value` fields in color stops are interpreted.\n *\n * - `'raw'` — stop values are actual metric values used directly\n * (e.g. congestion: 0–100, speed: 0–120 km/h, travelTime: 0–20 s/km).\n * - `'relativeToPredefinedRangePCT'` — stop values are 0–100 percentages relative to the SDK's predefined\n * metric ranges (congestion 0–100, speed 0–120 km/h, travelTime 0–20 s/km).\n * Useful for consistent coloring regardless of the current dataset.\n * - `'relativeToActualRangePCT'` — stop values are 0–100 percentages relative to the live data\n * range actually present in the loaded tiles (0 % = loaded minimum, 100 % = loaded maximum).\n * Useful for maximum contrast when exact values are unknown.\n *\n * Defaults to `'raw'`.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsValueType = 'raw' | 'relativeToPredefinedRangePCT' | 'relativeToActualRangePCT';\n\n/**\n * Custom color stops configuration with an explicit value-type hint.\n *\n * @example\n * ```typescript\n * // Stops whose values are actual congestion-level values (0–100)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'raw',\n * stops: [\n * { value: 0, color: '#2dc653' },\n * { value: 30, color: '#f5a623' },\n * { value: 100, color: '#e03030' },\n * ],\n * };\n *\n * // Stops as 0–100 % of the predefined metric range (speed: 0–120 km/h)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'relativeToPredefinedRangePCT',\n * stops: [\n * { value: 0, color: '#e03030' }, // 0 km/h\n * { value: 50, color: '#f5a623' }, // 60 km/h\n * { value: 100, color: '#2dc653' }, // 120 km/h\n * ],\n * };\n *\n * // Stops as 0–100 % of the live data range (maximum contrast regardless of absolute values)\n * const color: AreaAnalyticsColorStopsConfig = {\n * valueType: 'relativeToActualRangePCT',\n * stops: [\n * { value: 0, color: '#2dc653' }, // 0% = loaded minimum\n * { value: 50, color: '#f5a623' }, // 50% = mid\n * { value: 100, color: '#e03030' }, // 100% = loaded maximum\n * ],\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsColorStopsConfig = {\n /**\n * How stop values are interpreted. Defaults to `'raw'`.\n */\n valueType?: AreaAnalyticsValueType;\n\n /**\n * The color stops array.\n */\n stops: AreaAnalyticsColorStop[];\n};\n\n/**\n * Filter for visible tiles based on a metric's value range.\n * Both `min` and `max` are optional; at least one should be provided to have effect.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsMetricFilter = {\n /** Show only tiles where the metric is ≥ this value. */\n min?: number;\n /** Show only tiles where the metric is ≤ this value. */\n max?: number;\n};\n\n/**\n * Height (extrusion) configuration for a metric.\n *\n * The shape depends on `scaleMode`:\n * - `'predefinedRange'` / `'currentRange'` (default): use `maxHeightMeters` to set a true visual ceiling.\n * - `'raw'`: use `scaleFactor` to multiply the raw metric value directly.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsHeightConfig =\n | {\n /**\n * Normalised scale mode. The metric is mapped to a known range before computing height,\n * so `maxHeightMeters` equals the actual maximum visual height in meters.\n *\n * @defaultValue 'predefinedRange'\n */\n scaleMode?: 'predefinedRange' | 'currentRange';\n\n /**\n * True visual maximum extrusion height in meters.\n * The tallest cell reaches exactly this height.\n *\n * @defaultValue 1000\n */\n maxHeightMeters?: number;\n\n /**\n * Minimum extrusion height in meters.\n *\n * @defaultValue 0\n */\n minHeightMeters?: number;\n }\n | {\n /** Raw scale mode: `scaleFactor` is multiplied directly with the raw metric value. */\n scaleMode: 'raw';\n\n /**\n * Multiplier applied to the raw metric value to obtain extrusion height in meters.\n *\n * @defaultValue 1\n */\n scaleFactor?: number;\n\n /**\n * Minimum extrusion height in meters.\n *\n * @defaultValue 0\n */\n minHeightMeters?: number;\n };\n\n/**\n * Per-metric visualization configuration.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsMetricConfig = {\n /**\n * Color for this metric's visualization.\n *\n * Pass a preset theme name (`AreaAnalyticsColorTheme`) or a custom\n * `AreaAnalyticsColorStopsConfig` object with explicit stop values and a\n * value-type hint. For `speed`, the color order is automatically inverted\n * so that slow speeds render as \"bad\" (red) and fast speeds as \"good\" (green).\n *\n * @defaultValue `'trafficLight'` theme\n */\n color?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig;\n\n /**\n * Extrusion height configuration for this metric.\n */\n height?: AreaAnalyticsHeightConfig;\n\n /**\n * Filter visible tiles to only those within the given value range for this metric.\n */\n filters?: AreaAnalyticsMetricFilter;\n};\n\n/**\n * Per-layer-type positioning configuration for area analytics layers.\n *\n * Controls where each analytics layer type sits in the map's layer stack independently.\n * Each property accepts `'top'` (above all layers) or a well-known style layer ID.\n * Omitted properties fall back to their default positions.\n *\n * @example\n * ```typescript\n * // Move all layers below the lowest label\n * const config: TrafficAreaAnalyticsConfig = {\n * beforeLayerConfig: {\n * heatmap: 'lowestLabel',\n * hexgrid: { flat2D: 'lowestLabel', extrusion3D: 'lowestPlaceLabel' },\n * square: { flat2D: 'lowestLabel', extrusion3D: 'lowestPlaceLabel' },\n * },\n * };\n *\n * // Only reposition the heatmap layer\n * const config: TrafficAreaAnalyticsConfig = {\n * beforeLayerConfig: { heatmap: 'top' },\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsBeforeLayerConfig = {\n /** Layer position for the heatmap layer. Defaults to `'lowestLabel'`. */\n heatmap?: BeforeLayerConfig;\n hexgrid?: {\n /** Layer position for the flat hexgrid fill layer. Defaults to `'lowestLabel'`. */\n flat2D?: BeforeLayerConfig;\n /** Layer position for the extruded hexgrid layer. Defaults to `'lowestPlaceLabel'`. */\n extrusion3D?: BeforeLayerConfig;\n };\n square?: {\n /** Layer position for the flat square fill layer. Defaults to `'lowestLabel'`. */\n flat2D?: BeforeLayerConfig;\n /** Layer position for the extruded square layer. Defaults to `'lowestPlaceLabel'`. */\n extrusion3D?: BeforeLayerConfig;\n };\n};\n\n/**\n * Display configuration for the actual region boundary geometry.\n *\n * When area analytics data is shown, the original region polygon (the area\n * the query was run for) is also rendered. Use this config to control its\n * appearance.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsRegionPolygonConfig = {\n /**\n * Color used for both the fill and outline of the region boundary.\n *\n * @defaultValue '#000000'\n */\n color?: string;\n\n /**\n * Opacity of the region fill.\n *\n * @defaultValue 0\n */\n fillOpacity?: number;\n\n /**\n * Opacity of the region outline.\n *\n * @defaultValue 0.5\n */\n outlineOpacity?: number;\n\n /**\n * Width of the region outline in pixels.\n *\n * @defaultValue 2\n */\n outlineWidth?: number;\n\n /**\n * When `true`, renders the region as an overlay covering everything *outside* the polygon\n * boundary, effectively tinting the area outside the analytics region.\n *\n * Uses a world-minus-polygon donut geometry (same technique as the `GeometriesModule`\n * `'inverted'` theme).\n *\n * @defaultValue false\n */\n inverted?: boolean;\n};\n\n// ── Default values ───────────────────────────────────────────────────\n\n/**\n * Default values for all {@link TrafficAreaAnalyticsConfig} options.\n *\n * Use these as a reference for what the module applies when a property is omitted,\n * or spread them as a base when constructing a config programmatically.\n *\n * @group Traffic Area Analytics\n */\nexport const AREA_ANALYTICS_DEFAULTS = {\n /** @see {@link TrafficAreaAnalyticsConfig.visible} */\n visible: true,\n /** @see {@link TrafficAreaAnalyticsConfig.displayMode} */\n displayMode: 'hexgrid-3d',\n /** @see {@link TrafficAreaAnalyticsConfig.activeMetric} */\n activeMetric: 'congestionLevel',\n /** @see {@link TrafficAreaAnalyticsConfig.metricConfig} */\n metricConfig: {\n congestionLevel: {\n color: {\n valueType: 'raw',\n stops: [\n { value: 0, color: '#2dc653' }, // free flow — green\n { value: 50, color: '#f5a623' }, // moderate — amber\n { value: 100, color: '#e03030' }, // severe — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n speed: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#e03030' }, // slow — red\n { value: 50, color: '#f5a623' }, // medium — amber\n { value: 100, color: '#2dc653' }, // fast — green\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n travelTime: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#2dc653' }, // fast travel — green\n { value: 50, color: '#f5a623' }, // moderate — amber\n { value: 100, color: '#e03030' }, // slow travel — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n freeFlowSpeed: {\n color: {\n valueType: 'relativeToPredefinedRangePCT',\n stops: [\n { value: 0, color: '#e03030' }, // slow — red\n { value: 50, color: '#f5a623' }, // medium — amber\n { value: 100, color: '#2dc653' }, // fast — green\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n // networkLength is the total road length within each tile — per-tile values vary widely\n // and are unpredictable, so relativeToActualRangePCT is used as the default to ensure\n // the visualization always scales to the live data range.\n networkLength: {\n color: {\n valueType: 'relativeToActualRangePCT',\n stops: [\n { value: 0, color: '#2dc653' }, // low coverage — green\n { value: 50, color: '#f5a623' }, // mid coverage — amber\n { value: 100, color: '#e03030' }, // high coverage — red\n ],\n },\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'currentRange' },\n },\n },\n /** @see {@link AreaAnalyticsRegionPolygonConfig} */\n regionPolygon: {\n color: '#000000',\n fillOpacity: 0,\n outlineOpacity: 0.5,\n outlineWidth: 2,\n inverted: false,\n },\n} satisfies TrafficAreaAnalyticsConfig;\n\n// ── Main config type ────────────────────────────────────────────────\n\n/**\n * Configuration for the Traffic Area Analytics visualization module.\n *\n * @remarks\n * The active metric is set via `activeMetric` (defaults to `'congestionLevel'`).\n * Per-metric style settings (color, height, filters) are nested under `metricConfig` keyed by\n * `AreaAnalyticsMetricKey`, making it easy to pre-configure multiple metrics independently and switch\n * between them at runtime via `setMetric()`.\n *\n * @example\n * ```typescript\n * const config: TrafficAreaAnalyticsConfig = {\n * displayMode: 'hexgrid-3d',\n * activeMetric: 'congestionLevel',\n * metricConfig: {\n * congestionLevel: {\n * color: 'heat',\n * height: { maxHeightMeters: 80, scaleMode: 'predefinedRange' },\n * filters: { min: 10 },\n * },\n * speed: {\n * color: {\n * valueType: 'raw',\n * stops: [\n * { value: 0, color: '#e03030' },\n * { value: 60, color: '#f5a623' },\n * { value: 120, color: '#2dc653' },\n * ],\n * },\n * },\n * },\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type TrafficAreaAnalyticsConfig = MapModuleCommonConfig & {\n /**\n * Controls the visibility of the area analytics layers.\n *\n * @defaultValue true\n */\n visible?: boolean;\n\n /**\n * Visualization mode.\n *\n * @defaultValue 'hexgrid-3d'\n */\n displayMode?: AreaAnalyticsDisplayMode;\n\n /**\n * The metric that drives color and extrusion height.\n *\n * @defaultValue 'congestionLevel'\n */\n activeMetric?: AreaAnalyticsMetricKey;\n\n /**\n * Per-metric visualization configuration (color, height, filters).\n *\n * Metrics not listed here fall back to defaults (congestion color theme, metric-dependent height scale).\n */\n metricConfig?: Partial<Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>>;\n\n /**\n * Per-layer-type positioning — controls where each analytics layer type sits in the map layer stack.\n * Omitted layer types fall back to their default positions (`'lowestLabel'` for flat/heatmap, `'lowestPlaceLabel'` for extrusions).\n */\n beforeLayerConfig?: AreaAnalyticsBeforeLayerConfig;\n\n /**\n * Display options for the region polygon boundary drawn alongside the analytics data.\n */\n regionPolygon?: AreaAnalyticsRegionPolygonConfig;\n};\n","import type { AreaAnalyticsMetricKey } from '@tomtom-org/maps-sdk/core';\nimport type {\n ExpressionSpecification,\n FillExtrusionLayerSpecification,\n FillLayerSpecification,\n HeatmapLayerSpecification,\n LineLayerSpecification,\n} from 'maplibre-gl';\nimport type { BeforeLayerConfig, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { mapStyleLayerIDs } from '../../shared';\nimport { isClickEventState } from '../../shared/layers/eventState';\nimport type {\n AreaAnalyticsColorStop,\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsColorTheme,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsRegionPolygonConfig,\n TrafficAreaAnalyticsConfig,\n} from '../types/trafficAreaAnalyticsConfig';\nimport { AREA_ANALYTICS_DEFAULTS } from '../types/trafficAreaAnalyticsConfig';\n\n// ── Internal types ───────────────────────────────────────────────────\n\n/**\n * @ignore\n */\nexport type MetricRange = { min: number; max: number };\n\n// ── Colour theme presets ─────────────────────────────────────────────\n\n/**\n * @ignore\n */\nexport const COLOR_THEMES: Record<AreaAnalyticsColorTheme, string[]> = {\n trafficLight: ['#2dc653', '#f5a623', '#e03030'],\n heat: ['#2196F3', '#FF9800', '#F44336'],\n monochrome: ['#e0e0e0', '#888888', '#1a1a1a'],\n viridis: ['#fde725', '#35b779', '#31688e', '#440154'],\n plasma: ['#f0f921', '#ed7953', '#9c179e', '#0d0887'],\n};\n\n// ── Metric ranges used for colour / height interpolation ─────────────\n\n// Per-tile networkLength values vary widely (they represent road coverage within one tile, not a region\n// total), so the predefined range is a loose upper bound used only as a fallback before data loads.\n// The networkLength default config uses relativeToActualRangePCT to normalize to the live data range.\nconst DEFAULT_METRIC_RANGES: Record<AreaAnalyticsMetricKey, MetricRange> = {\n congestionLevel: { min: 0, max: 100 },\n speed: { min: 0, max: 120 },\n travelTime: { min: 0, max: 20 },\n freeFlowSpeed: { min: 0, max: 120 },\n networkLength: { min: 0, max: 5_000 },\n};\n\n// Speed-related metrics are \"higher = better\", so preset color themes are applied in reverse.\nconst isSpeedMetric = (metric: AreaAnalyticsMetricKey): boolean => metric === 'speed' || metric === 'freeFlowSpeed';\n\n// ── Config resolution helpers ────────────────────────────────────────\n\n/** @ignore */\nexport const getActiveMetric = (config?: TrafficAreaAnalyticsConfig): AreaAnalyticsMetricKey => {\n return config?.activeMetric ?? AREA_ANALYTICS_DEFAULTS.activeMetric;\n};\n\n// Distributes an array of colors into evenly-spaced 0–1 normalized stops.\nconst distributeColors = (colors: string[]): AreaAnalyticsColorStop[] =>\n colors.map((color, i) => ({ value: colors.length === 1 ? 0 : i / (colors.length - 1), color }));\n\n/**\n * Resolves the effective color stops for a given color configuration.\n * @ignore\n */\nexport const resolveColorStops = (\n colorConfig: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n): AreaAnalyticsColorStop[] => {\n if (typeof colorConfig === 'string') return distributeColors(COLOR_THEMES[colorConfig]);\n return colorConfig.stops;\n};\n\nconst resolveBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestLabel;\n};\n\nconst resolveExtrusionBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestPlaceLabel;\n};\n\n// ── Color / height expression builders ──────────────────────────────\n\n// Builds a MapLibre interpolation expression mapping a metric property to a color scale.\nconst buildColorExpression = (\n metric: AreaAnalyticsMetricKey,\n colorConfig?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n computedRange?: MetricRange,\n): ExpressionSpecification => {\n const effectiveConfig = colorConfig ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric].color ?? 'trafficLight';\n const stops = resolveColorStops(effectiveConfig);\n\n let scaled: Array<readonly [number, string]>;\n\n if (typeof effectiveConfig === 'string') {\n // Preset theme: stops have 0-1 normalized values → scale to metric range.\n // Speed-related metrics are inverted (higher = better), so reverse only the colors —\n // values must remain ascending to satisfy MapLibre's interpolate expression requirement.\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n const colors = isSpeedMetric(metric) ? [...stops].reverse().map((s) => s.color) : stops.map((s) => s.color);\n scaled = stops.map(({ value }, i) => [min + value * (max - min), colors[i]] as const);\n } else if (effectiveConfig.valueType === 'relativeToPredefinedRangePCT') {\n // 0-100 values relative to predefined metric range\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n scaled = stops.map(({ value, color }) => [min + (value / 100) * (max - min), color] as const);\n } else if (effectiveConfig.valueType === 'relativeToActualRangePCT') {\n // 0-100 values relative to live data range: 0% = loaded min, 100% = loaded max\n const { min, max } = computedRange ?? DEFAULT_METRIC_RANGES[metric];\n scaled = stops.map(({ value, color }) => [min + (value / 100) * (max - min), color] as const);\n } else {\n // 'raw': stop values are actual data values, used directly\n scaled = stops.map(({ value, color }) => [value, color] as const);\n }\n\n return ['interpolate', ['linear'], ['get', metric], ...scaled.flatMap(([val, color]) => [val, color])];\n};\n\n// Builds a MapLibre heatmap-color expression using the given color config.\nconst buildHeatmapColorExpression = (\n metric: AreaAnalyticsMetricKey,\n colorConfig?: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig,\n): ExpressionSpecification => {\n const effectiveConfig = colorConfig ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric].color ?? 'trafficLight';\n const stops = resolveColorStops(effectiveConfig);\n // Map stop values (0-1 for presets; normalize for custom) to density range [0.3, 1.0]\n let densityStops: (number | string)[];\n\n if (typeof effectiveConfig === 'string') {\n // Preset: 0-1 normalized values map directly to density range.\n // Speed-related metrics are inverted (higher = better), so reverse only the colors —\n // values must remain ascending to satisfy MapLibre's interpolate expression requirement.\n const colors = isSpeedMetric(metric) ? [...stops].reverse().map((s) => s.color) : stops.map((s) => s.color);\n densityStops = stops.flatMap(({ value }, i) => [0.3 + value * 0.7, colors[i]]);\n } else if (\n effectiveConfig.valueType === 'relativeToPredefinedRangePCT' ||\n effectiveConfig.valueType === 'relativeToActualRangePCT'\n ) {\n // Both PCT types: 0-100 → normalize to 0-1 then map to density range [0.3, 1.0]\n densityStops = stops.flatMap(({ value, color }) => [0.3 + (value / 100) * 0.7, color]);\n } else {\n // 'raw': cannot easily normalize without data range for heatmap — treat as 0-1 normalized\n // (heatmap density is always 0-1; the first and last stop values anchor the range)\n const minValue = Math.min(...stops.map((s) => s.value));\n const maxValue = Math.max(...stops.map((s) => s.value));\n const range = maxValue - minValue || 1;\n densityStops = stops.flatMap(({ value, color }) => [0.3 + ((value - minValue) / range) * 0.7, color]);\n }\n\n return ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(0,0,0,0)', ...densityStops];\n};\n\n// Builds a MapLibre expression for fill-extrusion height driven by the active metric.\nconst buildHeightExpression = (\n metric: AreaAnalyticsMetricKey,\n heightConfig?: AreaAnalyticsHeightConfig,\n computedRange?: MetricRange,\n): ExpressionSpecification => {\n const defaultHeight = AREA_ANALYTICS_DEFAULTS.metricConfig[metric].height;\n const minHeight = heightConfig?.minHeightMeters ?? defaultHeight.minHeightMeters ?? 0;\n\n if (heightConfig?.scaleMode === 'raw') {\n const scaleFactor = heightConfig.scaleFactor ?? 1;\n if (metric === 'speed') {\n const { max } = DEFAULT_METRIC_RANGES[metric];\n return ['max', minHeight, ['*', ['-', max, ['coalesce', ['get', 'speed'], 0]], scaleFactor]];\n }\n return ['max', minHeight, ['*', ['coalesce', ['get', metric], 0], scaleFactor]];\n }\n\n // 'predefinedRange' or 'currentRange' (default)\n const maxHeight = heightConfig?.maxHeightMeters ?? defaultHeight.maxHeightMeters ?? 1000;\n const scaleMode = heightConfig?.scaleMode ?? defaultHeight.scaleMode;\n const { min, max } =\n scaleMode === 'currentRange' ? (computedRange ?? DEFAULT_METRIC_RANGES[metric]) : DEFAULT_METRIC_RANGES[metric];\n const range = max - min || 1;\n\n if (metric === 'speed') {\n return ['max', minHeight, ['*', ['/', ['-', max, ['coalesce', ['get', 'speed'], 0]], range], maxHeight]];\n }\n return ['max', minHeight, ['*', ['/', ['-', ['coalesce', ['get', metric], 0], min], range], maxHeight]];\n};\n\n// ── Heatmap layer builder ────────────────────────────────────────────\n\n// Expands a preset theme name into per-metric explicit raw stops with correct display ordering.\n// Speed-like metrics are inverted so \"slow\" always maps to the bad end of the ramp.\n// Used by the module when a theme is applied globally across all metrics.\nexport const expandThemeToAllMetrics = (\n theme: AreaAnalyticsColorTheme,\n): Partial<Record<AreaAnalyticsMetricKey, { color: AreaAnalyticsColorStopsConfig }>> => {\n const rawStops = resolveColorStops(theme);\n const toMetricStops = (metric: AreaAnalyticsMetricKey): AreaAnalyticsColorStopsConfig => {\n const { min, max } = DEFAULT_METRIC_RANGES[metric];\n const colors = isSpeedMetric(metric)\n ? [...rawStops].reverse().map((s) => s.color)\n : rawStops.map((s) => s.color);\n return {\n valueType: 'raw',\n stops: rawStops.map(({ value }, i) => ({ value: min + value * (max - min), color: colors[i] })),\n };\n };\n return {\n congestionLevel: { color: toMetricStops('congestionLevel') },\n speed: { color: toMetricStops('speed') },\n travelTime: { color: toMetricStops('travelTime') },\n freeFlowSpeed: { color: toMetricStops('freeFlowSpeed') },\n networkLength: { color: toMetricStops('networkLength') },\n };\n};\n\n/**\n * Builds the heatmap layer spec for a given source and config.\n * @ignore\n */\nexport const buildHeatmapLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<HeatmapLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n const { min, max } = computedRange ?? DEFAULT_METRIC_RANGES[metric];\n\n return {\n id: layerId,\n type: 'heatmap',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.heatmap),\n paint: {\n 'heatmap-weight': ['interpolate', ['linear'], ['get', metric], min, 0, max, 1],\n 'heatmap-intensity': ['interpolate', ['linear'], ['zoom'], 10, 0.5, 14, 2, 16, 4],\n 'heatmap-color': buildHeatmapColorExpression(metric, metricConfig?.color),\n 'heatmap-radius': ['interpolate', ['linear'], ['zoom'], 4, 1, 8, 3, 12, 35, 14, 80, 16, 100],\n 'heatmap-opacity': ['interpolate', ['linear'], ['zoom'], 4, 0.8, 13, 0.8, 17, 0],\n },\n };\n};\n\n// ── Hexgrid layer builders ───────────────────────────────────────────\n\n/**\n * Builds the flat hex fill layer spec.\n * @ignore\n */\nexport const buildHexFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.hexgrid?.flat2D),\n paint: {\n 'fill-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-opacity': 0.6,\n },\n };\n};\n\n/**\n * Builds the extruded hex layer spec.\n * @ignore\n */\nexport const buildHexExtrusionLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill-extrusion',\n beforeID: resolveExtrusionBeforeID(config?.beforeLayerConfig?.hexgrid?.extrusion3D),\n paint: {\n 'fill-extrusion-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-extrusion-height': buildHeightExpression(metric, metricConfig?.height, computedRange),\n 'fill-extrusion-base': 0,\n 'fill-extrusion-opacity': 0.75,\n },\n };\n};\n\n// ── Square grid layer builders ───────────────────────────────────────\n\n/**\n * Builds the flat square fill layer spec.\n * @ignore\n */\nexport const buildSquareFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(config?.beforeLayerConfig?.square?.flat2D),\n paint: {\n 'fill-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-opacity': 0.6,\n },\n };\n};\n\n/**\n * Builds the extruded square layer spec.\n * @ignore\n */\nexport const buildSquareExtrusionLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metricConfig?.[metric];\n\n return {\n id: layerId,\n type: 'fill-extrusion',\n beforeID: resolveExtrusionBeforeID(config?.beforeLayerConfig?.square?.extrusion3D),\n paint: {\n 'fill-extrusion-color': buildColorExpression(metric, metricConfig?.color, computedRange),\n 'fill-extrusion-height': buildHeightExpression(metric, metricConfig?.height, computedRange),\n 'fill-extrusion-base': 0,\n 'fill-extrusion-opacity': 0.75,\n },\n };\n};\n\n// ── Region boundary layer builders ───────────────────────────────────\n\n// Resolves the region display config with defaults applied.\nconst resolveRegionConfig = (region?: AreaAnalyticsRegionPolygonConfig) => ({\n ...AREA_ANALYTICS_DEFAULTS.regionPolygon,\n ...region,\n});\n\n/**\n * Builds the region boundary fill layer spec.\n * @ignore\n */\nexport const buildRegionFillLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const { color, fillOpacity } = resolveRegionConfig(config?.regionPolygon);\n\n return {\n id: layerId,\n type: 'fill',\n beforeID: resolveBeforeID(),\n paint: {\n 'fill-color': color,\n 'fill-opacity': fillOpacity,\n },\n };\n};\n\n/**\n * Builds the region boundary outline layer spec.\n * @ignore\n */\nexport const buildRegionLineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> => {\n const { color, outlineOpacity, outlineWidth } = resolveRegionConfig(config?.regionPolygon);\n\n return {\n id: layerId,\n type: 'line',\n beforeID: resolveBeforeID(),\n paint: {\n 'line-color': color,\n 'line-opacity': outlineOpacity,\n 'line-width': outlineWidth,\n },\n };\n};\n\n// ── Event-state highlight layer builders ──────────────────────────────\n//\n// These layers render only when a feature has an `eventState` property set\n// (written automatically by the EventsProxy on hover / click). They provide\n// visual feedback on top of the main data layers without modifying them.\n\n// Shared: white polygon footprint outline on hover / click.\nconst buildPolygonOutlineLayerSpec = (\n layerId: string,\n beforeID: string | undefined,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> => ({\n id: layerId,\n type: 'line',\n beforeID,\n filter: ['has', 'eventState'],\n paint: {\n 'line-color': '#ffffff',\n 'line-width': ['case', isClickEventState, 3, 2],\n 'line-opacity': ['case', isClickEventState, 1, 0.8],\n },\n});\n\n/**\n * White outline at the hex cell footprint on hover / click (2D mode).\n * @ignore\n */\nexport const buildHexOutlineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, resolveBeforeID(config?.beforeLayerConfig?.hexgrid?.flat2D));\n\n/**\n * White outline at the square cell footprint on hover / click (2D mode).\n * @ignore\n */\nexport const buildSquareOutlineLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, resolveBeforeID(config?.beforeLayerConfig?.square?.flat2D));\n\n// Shared: derives a white semi-transparent fill-extrusion highlight from the given base data layer spec.\n// Inherits beforeID and fill-extrusion-base; raises height by 0.5 m to prevent z-fighting.\n// Raises height by 0.5 m so the highlight prism clears the data layer in the depth buffer.\n// Fill-extrusion uses a strict depth test: same-height overlays are discarded entirely.\nconst buildExtrusionHighlightLayerSpec = (\n base: ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification>,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => ({\n ...base,\n filter: ['has', 'eventState'],\n paint: {\n ...base.paint,\n // fill-extrusion-opacity is not data-driven — use a constant and vary the color instead.\n 'fill-extrusion-color': ['case', isClickEventState, '#ffffff', '#ffffff'],\n 'fill-extrusion-height': ['+', base.paint?.['fill-extrusion-height'] as ExpressionSpecification, 0.5],\n 'fill-extrusion-opacity': 0.6,\n },\n});\n\n/**\n * Semi-transparent white fill-extrusion overlay on hover / click in 3D hex mode.\n * Highlights all visible faces of the extruded prism, approximating a \"mesh\" highlight effect.\n * @ignore\n */\nexport const buildHexExtrusionHighlightLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(buildHexExtrusionLayerSpec(layerId, config, computedRange));\n\n/**\n * Semi-transparent white fill-extrusion overlay on hover / click in 3D square mode.\n * Highlights all visible faces of the extruded prism, approximating a \"mesh\" highlight effect.\n * @ignore\n */\nexport const buildSquareExtrusionHighlightLayerSpec = (\n layerId: string,\n config?: TrafficAreaAnalyticsConfig,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(buildSquareExtrusionLayerSpec(layerId, config, computedRange));\n","import type { AreaAnalyticsTileEntry, TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport { bboxFromGeoJSON } from '@tomtom-org/maps-sdk/core';\nimport { booleanPointInPolygon, hexGrid, squareGrid } from '@turf/turf';\nimport type { BBox, Feature, FeatureCollection, Point, Polygon } from 'geojson';\nimport type { AreaAnalyticsDisplayProperties } from '../types/trafficAreaAnalyticsFeature';\n\n/**\n * @ignore\n */\nexport const tilesToPointFeatures = (\n analytics: TrafficAreaAnalytics,\n): FeatureCollection<Point, AreaAnalyticsDisplayProperties> => {\n const tiles = analytics.features.flatMap((feature) => feature.properties?.tiledData?.tiles ?? []);\n return {\n type: 'FeatureCollection',\n features: tiles.map((tile, index) => {\n const [lon, lat] = tile.tileCentre;\n const id = `tile-${index}-${lon}-${lat}`;\n\n return {\n type: 'Feature',\n id,\n geometry: {\n type: 'Point',\n coordinates: [lon, lat],\n },\n properties: {\n id,\n speed: tile.speed ?? 0,\n freeFlowSpeed: tile.freeFlowSpeed ?? 0,\n congestionLevel: tile.congestionLevel ?? 0,\n travelTime: tile.travelTime ?? 0,\n networkLength: tile.networkLength ?? 0,\n },\n };\n }),\n };\n};\n\ntype CellAggregate = {\n congestionSum: number;\n speedSum: number;\n freeFlowSum: number;\n travelTimeSum: number;\n networkLengthSum: number;\n count: number;\n};\n\nconst buildCellAggregates = (\n tiles: ReadonlyArray<AreaAnalyticsTileEntry>,\n grid: FeatureCollection<Polygon>,\n): Array<CellAggregate | undefined> => {\n const cellAggregates: Array<CellAggregate | undefined> = [];\n\n for (const tile of tiles) {\n for (let index = 0; index < grid.features.length; index++) {\n if (booleanPointInPolygon(tile.tileCentre, grid.features[index])) {\n let cell = cellAggregates[index];\n if (cell === undefined) {\n cell = {\n congestionSum: 0,\n speedSum: 0,\n freeFlowSum: 0,\n travelTimeSum: 0,\n networkLengthSum: 0,\n count: 0,\n };\n cellAggregates[index] = cell;\n }\n cell.congestionSum += tile.congestionLevel ?? 0;\n cell.speedSum += tile.speed ?? 0;\n cell.freeFlowSum += tile.freeFlowSpeed ?? 0;\n cell.travelTimeSum += tile.travelTime ?? 0;\n cell.networkLengthSum += tile.networkLength ?? 0;\n cell.count++;\n break;\n }\n }\n }\n\n return cellAggregates;\n};\n\nconst cellAggregatesToFeatures = (\n cellAggregates: Array<CellAggregate | undefined>,\n grid: FeatureCollection<Polygon>,\n idPrefix: string,\n): Feature<Polygon, AreaAnalyticsDisplayProperties>[] => {\n const features: Feature<Polygon, AreaAnalyticsDisplayProperties>[] = [];\n\n for (let cellIndex = 0; cellIndex < cellAggregates.length; cellIndex++) {\n const cell = cellAggregates[cellIndex];\n if (!cell) continue;\n const { congestionSum, speedSum, freeFlowSum, travelTimeSum, networkLengthSum, count } = cell;\n const id = `${idPrefix}-${cellIndex}`;\n features.push({\n type: 'Feature',\n id,\n geometry: grid.features[cellIndex].geometry,\n properties: {\n id,\n congestionLevel: Math.round(congestionSum / count),\n speed: Math.round(speedSum / count),\n freeFlowSpeed: Math.round(freeFlowSum / count),\n travelTime: Math.round((travelTimeSum / count) * 10) / 10,\n networkLength: Math.round(networkLengthSum / count),\n },\n });\n }\n\n return features;\n};\n\nconst aggregateTilesIntoCells = (\n tiles: ReadonlyArray<AreaAnalyticsTileEntry>,\n grid: FeatureCollection<Polygon>,\n idPrefix: string,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>['features'] =>\n cellAggregatesToFeatures(buildCellAggregates(tiles, grid), grid, idPrefix);\n\ntype BuildGridFn = (bbox: BBox, cellSideKm: number) => FeatureCollection<Polygon>;\n\n// Expand the bbox slightly so tile centres that sit exactly on the bbox edge\n// fall strictly inside a grid cell. booleanPointInPolygon returns false for\n// boundary points, so without this padding those tiles would never be matched.\nconst BBOX_PADDING_DEG = 0.005; // ~350–550 m — enough for hexGrid/squareGrid to generate cells even when all tiles are very close together\n\nconst tilesToGridFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm: number,\n buildGridFn: BuildGridFn,\n idPrefix: string,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> => {\n const allCells: FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>['features'] = [];\n\n for (let featureIndex = 0; featureIndex < analytics.features.length; featureIndex++) {\n const analyticsFeature = analytics.features[featureIndex];\n const tiles = analyticsFeature.properties?.tiledData?.tiles ?? [];\n\n if (tiles.length === 0) continue;\n\n const bbox = bboxFromGeoJSON(analyticsFeature);\n if (!bbox) continue;\n\n const paddedBbox: BBox = [\n bbox[0] - BBOX_PADDING_DEG,\n bbox[1] - BBOX_PADDING_DEG,\n bbox[2] + BBOX_PADDING_DEG,\n bbox[3] + BBOX_PADDING_DEG,\n ];\n const grid = buildGridFn(paddedBbox, cellSideKm);\n allCells.push(...aggregateTilesIntoCells(tiles, grid, `${idPrefix}-${featureIndex}`));\n }\n\n return { type: 'FeatureCollection', features: allCells };\n};\n\n/**\n * Converts area-analytics tile entries into a hexagonal polygon grid\n * using Turf's `hexGrid` and spatial-join aggregation.\n *\n * Tiles that fall within the same hexagon cell are averaged. Empty hexagons\n * (containing no tiles) are excluded from the result.\n *\n * @param analytics - The parsed `TrafficAreaAnalytics` response.\n * @param cellSideKm - Hex cell side length in kilometres (default 0.25).\n * @returns A Polygon FeatureCollection with averaged metric properties.\n *\n * @ignore\n */\nexport const tilesToHexFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm = 0.25,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> =>\n tilesToGridFeatures(analytics, cellSideKm, (bbox, km) => hexGrid(bbox, km, { units: 'kilometers' }), 'hex');\n\n/**\n * Converts area-analytics tile entries into a square polygon grid\n * using Turf's `squareGrid` and spatial-join aggregation.\n *\n * Tiles that fall within the same square cell are averaged. Empty cells\n * (containing no tiles) are excluded from the result.\n *\n * @param analytics - The parsed `TrafficAreaAnalytics` response.\n * @param cellSideKm - Square cell side length in kilometres (default 0.5).\n * @returns A Polygon FeatureCollection with averaged metric properties.\n *\n * @ignore\n */\nexport const tilesToSquareFeatures = (\n analytics: TrafficAreaAnalytics,\n cellSideKm = 0.5,\n): FeatureCollection<Polygon, AreaAnalyticsDisplayProperties> =>\n tilesToGridFeatures(analytics, cellSideKm, (bbox, km) => squareGrid(bbox, km, { units: 'kilometers' }), 'square');\n","import type { AreaAnalyticsMetricKey, TrafficAreaAnalytics } from '@tomtom-org/maps-sdk/core';\nimport { mask } from '@turf/turf';\nimport type { FeatureCollection, Point, Polygon } from 'geojson';\nimport { ExpressionSpecification } from 'maplibre-gl';\nimport type { BeforeLayerConfig } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { changeLayerProps, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildHeatmapLayerSpec,\n buildHexExtrusionHighlightLayerSpec,\n buildHexExtrusionLayerSpec,\n buildHexFillLayerSpec,\n buildHexOutlineLayerSpec,\n buildRegionFillLayerSpec,\n buildRegionLineLayerSpec,\n buildSquareExtrusionHighlightLayerSpec,\n buildSquareExtrusionLayerSpec,\n buildSquareFillLayerSpec,\n buildSquareOutlineLayerSpec,\n expandThemeToAllMetrics,\n getActiveMetric,\n} from './layers/areaAnalyticsLayers';\nimport type {\n AreaAnalyticsBeforeLayerConfig,\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsColorTheme,\n AreaAnalyticsDisplayMode,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsMetricConfig,\n AreaAnalyticsMetricFilter,\n TrafficAreaAnalyticsConfig,\n} from './types/trafficAreaAnalyticsConfig';\nimport { AREA_ANALYTICS_DEFAULTS } from './types/trafficAreaAnalyticsConfig';\nimport type { AreaAnalyticsDisplayProperties, AreaAnalyticsTileFeature } from './types/trafficAreaAnalyticsFeature';\nimport { tilesToHexFeatures, tilesToPointFeatures, tilesToSquareFeatures } from './util/areaAnalyticsGeoJSONTiles';\n\n/**\n * Sources and layers managed by this module.\n * @ignore\n */\ntype AreaAnalyticsSourcesWithLayers = {\n heatmap: GeoJSONSourceWithLayers<FeatureCollection<Point, AreaAnalyticsDisplayProperties>>;\n hexgrid: GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>;\n square: GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>;\n region: GeoJSONSourceWithLayers;\n};\n\n/**\n * Traffic Area Analytics visualization module.\n *\n * Renders area-analytics data on the map in one of five modes:\n * - **hexgrid-3d** — 3D extruded hexagonal cells coloured and raised by the active metric (default)\n * - **hexgrid-2d** — Flat hexagonal cells coloured by the active metric\n * - **square-3d** — 3D extruded square cells coloured and raised by the active metric\n * - **square-2d** — Flat square cells coloured by the active metric\n * - **heatmap** — a MapLibre density-heatmap layer built from tile-centre points\n *\n * The actual region boundary is always rendered alongside the analytics cells.\n *\n * @remarks\n * **Quick start:**\n * ```typescript\n * import { TrafficAreaAnalyticsModule } from '@tomtom-org/maps-sdk/map';\n * import { trafficAreaAnalytics } from '@tomtom-org/maps-sdk/services';\n *\n * const module = await TrafficAreaAnalyticsModule.get(map, {\n * displayMode: 'hexgrid-3d',\n * metric: { active: 'congestionLevel' },\n * });\n * const analytics = await trafficAreaAnalytics({ ... });\n * await module.show(analytics);\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport class TrafficAreaAnalyticsModule extends AbstractMapModule<\n AreaAnalyticsSourcesWithLayers,\n TrafficAreaAnalyticsConfig\n> {\n // ── Cached layer specs (used by changeLayerProps for config updates) ──\n\n private heatmapLayerSpec!: ReturnType<typeof buildHeatmapLayerSpec>;\n private hexFillLayerSpec!: ReturnType<typeof buildHexFillLayerSpec>;\n private hexExtrusionLayerSpec!: ReturnType<typeof buildHexExtrusionLayerSpec>;\n private hexOutlineLayerSpec!: ReturnType<typeof buildHexOutlineLayerSpec>;\n private hexExtrusionHighlightLayerSpec!: ReturnType<typeof buildHexExtrusionHighlightLayerSpec>;\n private squareFillLayerSpec!: ReturnType<typeof buildSquareFillLayerSpec>;\n private squareExtrusionLayerSpec!: ReturnType<typeof buildSquareExtrusionLayerSpec>;\n private squareOutlineLayerSpec!: ReturnType<typeof buildSquareOutlineLayerSpec>;\n private squareExtrusionHighlightLayerSpec!: ReturnType<typeof buildSquareExtrusionHighlightLayerSpec>;\n private regionFillLayerSpec!: ReturnType<typeof buildRegionFillLayerSpec>;\n private regionLineLayerSpec!: ReturnType<typeof buildRegionLineLayerSpec>;\n\n // Cached for style-change restoration.\n private lastAnalytics: TrafficAreaAnalytics | null = null;\n private readonly shownFeaturesHandlers: ((features: TrafficAreaAnalytics) => void)[] = [];\n\n // ── Factory ──────────────────────────────────────────────────────\n\n /**\n * Creates and initialises a new Traffic Area Analytics module.\n *\n * All configuration properties are optional. When `config` is omitted (or only\n * partially supplied), built-in defaults are applied for every missing field.\n * After `get()` resolves, {@link TrafficAreaAnalyticsModule.getConfig} always returns a\n * fully-populated configuration — there is no need to reference any default\n * values yourself.\n *\n * @param tomtomMap - The TomTomMap instance.\n * @param config - Optional initial configuration. Omit to use all defaults.\n * @returns A promise that resolves with the initialised module.\n *\n * @example\n * ```typescript\n * // No config needed — defaults are applied automatically.\n * const module = await TrafficAreaAnalyticsModule.get(map);\n * await module.show(analytics);\n *\n * // Or override specific properties while keeping everything else at default:\n * const module = await TrafficAreaAnalyticsModule.get(map, {\n * displayMode: 'heatmap',\n * metricConfig: {\n * congestionLevel: { color: 'heat' },\n * },\n * });\n * ```\n */\n static async get(tomtomMap: TomTomMap, config?: TrafficAreaAnalyticsConfig): Promise<TrafficAreaAnalyticsModule> {\n await waitUntilMapIsReady(tomtomMap);\n // Always pass a config object (even if empty) so _applyConfig fills in all\n // defaults and getConfig() returns a fully-populated result from the start.\n return new TrafficAreaAnalyticsModule(tomtomMap, config ?? {});\n }\n\n private constructor(map: TomTomMap, config?: TrafficAreaAnalyticsConfig) {\n super('geojson', map, config);\n }\n\n // ── AbstractMapModule hooks ──────────────────────────────────────\n\n /** @ignore */\n protected _initSourcesWithLayers(\n config?: TrafficAreaAnalyticsConfig,\n _restore?: boolean,\n ): AreaAnalyticsSourcesWithLayers {\n const index = this.instanceIndex;\n const heatmapSourceId = `area-analytics-heatmap-${index}`;\n const hexgridSourceId = `area-analytics-hexgrid-${index}`;\n const squareSourceId = `area-analytics-square-${index}`;\n const regionSourceId = `area-analytics-region-${index}`;\n\n const heatmapLayerId = `${heatmapSourceId}-layer`;\n const hexFillLayerId = `${hexgridSourceId}-fill`;\n const hexExtrusionLayerId = `${hexgridSourceId}-extrusion`;\n const hexOutlineLayerId = `${hexgridSourceId}-outline`;\n const hexExtrusionHighlightLayerId = `${hexgridSourceId}-extrusion-highlight`;\n const squareFillLayerId = `${squareSourceId}-fill`;\n const squareExtrusionLayerId = `${squareSourceId}-extrusion`;\n const squareOutlineLayerId = `${squareSourceId}-outline`;\n const squareExtrusionHighlightLayerId = `${squareSourceId}-extrusion-highlight`;\n const regionFillLayerId = `${regionSourceId}-fill`;\n const regionLineLayerId = `${regionSourceId}-line`;\n\n // No computed ranges available at init time — pass undefined\n this.heatmapLayerSpec = buildHeatmapLayerSpec(heatmapLayerId, config);\n this.hexFillLayerSpec = buildHexFillLayerSpec(hexFillLayerId, config);\n this.hexExtrusionLayerSpec = buildHexExtrusionLayerSpec(hexExtrusionLayerId, config);\n this.squareFillLayerSpec = buildSquareFillLayerSpec(squareFillLayerId, config);\n this.squareExtrusionLayerSpec = buildSquareExtrusionLayerSpec(squareExtrusionLayerId, config);\n this.regionFillLayerSpec = buildRegionFillLayerSpec(regionFillLayerId, config);\n this.regionLineLayerSpec = buildRegionLineLayerSpec(regionLineLayerId, config);\n\n // Highlight/outline specs — added directly to MapLibre (not through GeoJSONSourceWithLayers)\n // so they are never included in interactiveLayerIDs and won't break queryRenderedFeatures.\n this.hexOutlineLayerSpec = buildHexOutlineLayerSpec(hexOutlineLayerId, config);\n this.hexExtrusionHighlightLayerSpec = buildHexExtrusionHighlightLayerSpec(hexExtrusionHighlightLayerId, config);\n this.squareOutlineLayerSpec = buildSquareOutlineLayerSpec(squareOutlineLayerId, config);\n this.squareExtrusionHighlightLayerSpec = buildSquareExtrusionHighlightLayerSpec(\n squareExtrusionHighlightLayerId,\n config,\n );\n\n // Create sources with only the interactive data layers. Highlight layers share the same\n // MapLibre source but are added separately below to stay out of interactiveLayerIDs.\n const result: AreaAnalyticsSourcesWithLayers = {\n heatmap: new GeoJSONSourceWithLayers<FeatureCollection<Point, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n heatmapSourceId,\n [this.heatmapLayerSpec],\n ),\n hexgrid: new GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n hexgridSourceId,\n [this.hexFillLayerSpec, this.hexExtrusionLayerSpec],\n ),\n square: new GeoJSONSourceWithLayers<FeatureCollection<Polygon, AreaAnalyticsDisplayProperties>>(\n this.mapLibreMap,\n squareSourceId,\n [this.squareFillLayerSpec, this.squareExtrusionLayerSpec],\n ),\n region: new GeoJSONSourceWithLayers(this.mapLibreMap, regionSourceId, [\n this.regionFillLayerSpec,\n this.regionLineLayerSpec,\n ]),\n };\n\n // Sources are now in the map — safe to add highlight layers on top.\n this.addHighlightLayersToMap(hexgridSourceId, squareSourceId);\n\n return result;\n }\n\n /** @ignore */\n protected _applyConfig(config: TrafficAreaAnalyticsConfig | undefined): TrafficAreaAnalyticsConfig | undefined {\n if (config === undefined) return;\n\n // Deep-merge the metricConfig record at the per-metric-key level so that\n // passing { metricConfig: { congestionLevel: { color: 'heat' } } } does\n // not wipe out configs for other metrics.\n const mergedMetricConfig: TrafficAreaAnalyticsConfig['metricConfig'] =\n config.metricConfig && this.config?.metricConfig\n ? { ...this.config.metricConfig, ...config.metricConfig }\n : (config.metricConfig ?? this.config?.metricConfig);\n\n // Ensure every metric has an explicit config entry so getConfig() always\n // returns fully-populated stops (callers never need to fall back to defaults).\n const defaultKeys = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const filledMetricConfig = Object.fromEntries(\n defaultKeys.map((metric) => [\n metric,\n mergedMetricConfig?.[metric] ?? AREA_ANALYTICS_DEFAULTS.metricConfig[metric],\n ]),\n ) as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>;\n\n const merged: TrafficAreaAnalyticsConfig = {\n activeMetric: AREA_ANALYTICS_DEFAULTS.activeMetric,\n displayMode: AREA_ANALYTICS_DEFAULTS.displayMode,\n ...this.config,\n ...config,\n metricConfig: filledMetricConfig,\n };\n\n this.applyLayerConfig(merged);\n\n if (merged.regionPolygon) {\n this.applyRegionConfig(merged);\n }\n\n if (merged.beforeLayerConfig) {\n this._moveBeforeLayer(merged.beforeLayerConfig, false);\n }\n\n // Set config before applyModeVisibility, which reads `this.config.visible` and\n // `this.config.displayMode`. The parent's applyConfig will overwrite this.config\n // with the same value after we return, and emit config-change exactly once.\n this.config = merged;\n this.applyModeVisibility();\n return merged;\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n const cachedAnalytics = this.lastAnalytics;\n this.initSourcesWithLayers(this.config, true);\n\n if (cachedAnalytics) {\n // Skip _applyConfig — show() will apply metrics + visibility after data is loaded.\n if (this.config?.visible === false) {\n this.setVisible(false);\n }\n void this.show(cachedAnalytics);\n } else if (this.config) {\n this._applyConfig(this.config);\n }\n }\n\n // ── Public API ───────────────────────────────────────────────────\n\n /**\n * Displays area analytics data on the map.\n *\n * Accepts the raw response from `trafficAreaAnalytics()`. Tile data is\n * transformed internally to point features (heatmap), hexagonal polygons\n * (hexgrid modes), and square polygons (square modes).\n * The region boundary geometry is also displayed.\n *\n * @param analytics - The raw `TrafficAreaAnalytics` service response.\n *\n * @example\n * ```typescript\n * const analytics = await trafficAreaAnalytics({ ... });\n * await module.show(analytics);\n * ```\n */\n async show(analytics: TrafficAreaAnalytics): Promise<void> {\n await this.waitUntilModuleReady();\n\n this.lastAnalytics = analytics;\n\n this.sourcesWithLayers.heatmap.show(tilesToPointFeatures(analytics), { automaticVisibility: false });\n this.sourcesWithLayers.hexgrid.show(tilesToHexFeatures(analytics), { automaticVisibility: false });\n this.sourcesWithLayers.square.show(tilesToSquareFeatures(analytics), { automaticVisibility: false });\n\n const inverted = this.config?.regionPolygon?.inverted ?? AREA_ANALYTICS_DEFAULTS.regionPolygon.inverted;\n this.sourcesWithLayers.region.show(this.buildRegionFC(analytics.features, inverted));\n\n this.applyLayerConfig(this.config);\n this.applyModeVisibility();\n for (const handler of this.shownFeaturesHandlers) {\n handler(analytics);\n }\n }\n\n /**\n * Removes all area analytics data from the map.\n */\n async clear(): Promise<void> {\n await this.waitUntilModuleReady();\n this.lastAnalytics = null;\n this.sourcesWithLayers.heatmap.clear();\n this.sourcesWithLayers.hexgrid.clear();\n this.sourcesWithLayers.square.clear();\n this.sourcesWithLayers.region.clear();\n }\n\n /**\n * Switches the visualisation mode.\n *\n * @param mode - One of `'heatmap'`, `'hexgrid-3d'`, `'hexgrid-2d'`, `'square-3d'`, `'square-2d'`.\n */\n setMode(mode: AreaAnalyticsDisplayMode): void {\n if (mode === this.mode) return;\n this.config = { ...this.config, displayMode: mode };\n this.applyModeVisibility();\n this.emitConfigChange();\n }\n\n /**\n * Changes the active metric that drives colour and height.\n *\n * Updates `activeMetric` in the config to the given metric key.\n *\n * @param metric - One of `'congestionLevel'`, `'speed'`, `'travelTime'`, `'freeFlowSpeed'`, or `'networkLength'`.\n */\n setMetric(metric: AreaAnalyticsMetricKey): void {\n if (metric === this.activeMetric) return;\n this.config = { ...this.config, activeMetric: metric };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Sets the color for one or more metrics.\n *\n * Pass a preset theme name or a custom color-stops configuration.\n * Pass `undefined` to clear the color override for the targeted metrics.\n *\n * @param color - A preset theme, an `AreaAnalyticsColorStopsConfig`, or `undefined` to clear.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the color is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Apply a theme to all metrics\n * module.setColor('heat');\n *\n * // Apply a theme to specific metrics only\n * module.setColor('heat', ['speed', 'freeFlowSpeed']);\n *\n * // Apply custom stops to a single metric\n * module.setColor(\n * { valueType: 'raw', stops: [{ value: 0, color: '#2dc653' }, { value: 100, color: '#e03030' }] },\n * ['congestionLevel'],\n * );\n *\n * // Clear color override for all metrics\n * module.setColor(undefined);\n *\n * // Clear color override for a specific metric\n * module.setColor(undefined, ['speed']);\n * ```\n */\n setColor(\n color: AreaAnalyticsColorTheme | AreaAnalyticsColorStopsConfig | undefined,\n metrics?: AreaAnalyticsMetricKey[],\n ): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n if (color === undefined) {\n // Clear color override for targeted metrics.\n for (const metric of targetMetrics) {\n const { color: _c, ...rest } = updatedMetricConfig[metric] ?? {};\n updatedMetricConfig[metric] = rest as AreaAnalyticsMetricConfig;\n }\n } else if (typeof color === 'string') {\n // Theme preset: expand to per-metric explicit stops respecting display ordering\n // (speed-like metrics are inverted so \"slow\" maps to the bad end of the ramp).\n const expanded = expandThemeToAllMetrics(color);\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = {\n ...updatedMetricConfig[metric],\n ...expanded[metric],\n };\n }\n } else {\n // Custom stops: apply same config to all targeted metrics.\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], color };\n }\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Configures extrusion height behaviour for one or more metrics.\n *\n * @param heightConfig - Height configuration.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the height config is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Apply to all metrics\n * module.setHeight({ maxHeightMeters: 100, minHeightMeters: 5 });\n *\n * // Apply only to specific metrics\n * module.setHeight({ scaleMode: 'currentRange', maxHeightMeters: 500 }, ['speed', 'freeFlowSpeed']);\n * ```\n */\n setHeight(heightConfig: AreaAnalyticsHeightConfig, metrics?: AreaAnalyticsMetricKey[]): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], height: heightConfig };\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Filters visible tiles by value range for one or more metrics.\n *\n * @param filter - Filter config. Pass `undefined` to clear.\n * @param metrics - Optional list of metrics to target.\n * When omitted, the filter is applied to **all** metrics.\n *\n * @example\n * ```typescript\n * // Filter all metrics\n * module.filter({ min: 20, max: 80 });\n *\n * // Filter only a specific metric\n * module.filter({ min: 50 }, ['congestionLevel']);\n *\n * // Clear filter for all metrics\n * module.filter(undefined);\n * ```\n */\n filter(filter?: AreaAnalyticsMetricFilter, metrics?: AreaAnalyticsMetricKey[]): void {\n const ALL_METRICS = Object.keys(AREA_ANALYTICS_DEFAULTS.metricConfig) as AreaAnalyticsMetricKey[];\n const targetMetrics = metrics ?? ALL_METRICS;\n const updatedMetricConfig = { ...this.config?.metricConfig };\n\n for (const metric of targetMetrics) {\n updatedMetricConfig[metric] = { ...updatedMetricConfig[metric], filters: filter };\n }\n\n this.config = {\n ...this.config,\n metricConfig: updatedMetricConfig as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>,\n };\n this.applyFiltersToLayers();\n this.emitConfigChange();\n }\n\n /**\n * Clears any active tile filter for one or more metrics.\n *\n * @param metrics - Optional list of metrics to clear. When omitted, clears filters for **all** metrics.\n */\n clearFilter(metrics?: AreaAnalyticsMetricKey[]): void {\n this.filter(undefined, metrics);\n }\n\n /**\n * Shows or hides all area analytics layers.\n */\n setVisible(visible: boolean): void {\n this.config = { ...this.config, visible };\n this.applyModeVisibility();\n this.emitConfigChange();\n }\n\n /**\n * Whether any area analytics (data) layer is currently visible.\n */\n isVisible(): boolean {\n return (\n this.sourcesWithLayers.heatmap.isAnyLayerVisible() ||\n this.sourcesWithLayers.hexgrid.isAnyLayerVisible() ||\n this.sourcesWithLayers.square.isAnyLayerVisible()\n );\n }\n\n /**\n * Returns the currently displayed data for all visualisation modes.\n */\n getShown() {\n return {\n heatmap: this.sourcesWithLayers.heatmap.shownFeatures,\n hexgrid: this.sourcesWithLayers.hexgrid.shownFeatures,\n square: this.sourcesWithLayers.square.shownFeatures,\n };\n }\n\n /**\n * Repositions analytics layers independently by layer type.\n *\n * Only the properties present in `layerConfig` are repositioned; omitted layer types are left in place.\n *\n * @param layerConfig - Per-layer-type positioning config. Each value is `'top'` or a `MapStyleLayerID`.\n */\n moveBeforeLayer(layerConfig: AreaAnalyticsBeforeLayerConfig): void {\n this._moveBeforeLayer(layerConfig);\n }\n\n private _moveBeforeLayer(layerConfig: AreaAnalyticsBeforeLayerConfig, updateConfig = true): void {\n const toId = (value: BeforeLayerConfig) => (value === 'top' ? undefined : mapStyleLayerIDs[value]);\n\n if (layerConfig.heatmap !== undefined) {\n this.mapLibreMap.moveLayer(this.heatmapLayerSpec.id, toId(layerConfig.heatmap));\n }\n\n if (layerConfig.hexgrid?.flat2D !== undefined) {\n this.mapLibreMap.moveLayer(this.hexFillLayerSpec.id, toId(layerConfig.hexgrid.flat2D));\n }\n\n if (layerConfig.hexgrid?.extrusion3D !== undefined) {\n this.mapLibreMap.moveLayer(this.hexExtrusionLayerSpec.id, toId(layerConfig.hexgrid.extrusion3D));\n }\n\n if (layerConfig.square?.flat2D !== undefined) {\n this.mapLibreMap.moveLayer(this.squareFillLayerSpec.id, toId(layerConfig.square.flat2D));\n }\n\n if (layerConfig.square?.extrusion3D !== undefined) {\n this.mapLibreMap.moveLayer(this.squareExtrusionLayerSpec.id, toId(layerConfig.square.extrusion3D));\n }\n\n if (updateConfig) {\n this.config = { ...this.config, beforeLayerConfig: layerConfig };\n }\n }\n\n /**\n * Event interface for the hexgrid and square layers.\n *\n * Supports feature-interaction events (`hover`, `click`, `contextmenu`, `long-hover`) as well\n * as module lifecycle events (`config-change`, `shown-features`) — all through the same\n * `events.on(type, handler)` surface.\n *\n * @example\n * ```typescript\n * module.events.on('hover', (feature, lngLat) => {\n * console.log(feature.properties.congestionLevel);\n * });\n *\n * const unsub = module.events.on('config-change', (config) => {\n * console.log('Config updated:', config);\n * });\n *\n * module.events.on('shown-features', (data) => {\n * console.log('Analytics data shown:', data);\n * });\n * ```\n */\n get events(): CombinedEvents<AreaAnalyticsTileFeature, TrafficAreaAnalyticsConfig, TrafficAreaAnalytics> {\n return new CombinedEvents(\n new UserEvents(\n this.tomtomMap._eventsProxy,\n [this.sourcesWithLayers.hexgrid, this.sourcesWithLayers.square, this.sourcesWithLayers.heatmap],\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n\n // ── Private helpers ──────────────────────────────────────────────\n\n private get mode(): AreaAnalyticsDisplayMode {\n return this.config?.displayMode ?? AREA_ANALYTICS_DEFAULTS.displayMode;\n }\n\n private get activeMetric(): AreaAnalyticsMetricKey {\n return getActiveMetric(this.config);\n }\n\n private applyModeVisibility(): void {\n if (this.config?.visible === false) {\n this.hideAllDataLayers();\n return;\n }\n\n const mode = this.mode;\n const hasHeatmapData = this.sourcesWithLayers.heatmap.shownFeatures.features.length > 0;\n const hasHexData = this.sourcesWithLayers.hexgrid.shownFeatures.features.length > 0;\n const hasSquareData = this.sourcesWithLayers.square.shownFeatures.features.length > 0;\n const hasRegionData = this.sourcesWithLayers.region.shownFeatures.features.length > 0;\n\n // Data layers (managed by GeoJSONSourceWithLayers)\n this.sourcesWithLayers.heatmap.setLayersVisible(mode === 'heatmap' && hasHeatmapData);\n this.sourcesWithLayers.hexgrid.setLayersVisible(\n mode === 'hexgrid-3d' && hasHexData,\n (layerSpec) => layerSpec.id === this.hexExtrusionLayerSpec.id,\n );\n this.sourcesWithLayers.hexgrid.setLayersVisible(\n mode === 'hexgrid-2d' && hasHexData,\n (layerSpec) => layerSpec.id === this.hexFillLayerSpec.id,\n );\n this.sourcesWithLayers.square.setLayersVisible(\n mode === 'square-3d' && hasSquareData,\n (layerSpec) => layerSpec.id === this.squareExtrusionLayerSpec.id,\n );\n this.sourcesWithLayers.square.setLayersVisible(\n mode === 'square-2d' && hasSquareData,\n (layerSpec) => layerSpec.id === this.squareFillLayerSpec.id,\n );\n this.sourcesWithLayers.region.setLayersVisible(hasRegionData);\n\n // Highlight layers (added directly to MapLibre, not tracked by GeoJSONSourceWithLayers).\n // Outlines are 2D-only — line layers render in the 2D pass and appear behind fill-extrusion prisms.\n // Extrusion highlights are 3D-only.\n this.setHighlightLayerVisible(this.hexOutlineLayerSpec.id, mode === 'hexgrid-2d' && hasHexData);\n this.setHighlightLayerVisible(this.hexExtrusionHighlightLayerSpec.id, mode === 'hexgrid-3d' && hasHexData);\n this.setHighlightLayerVisible(this.squareOutlineLayerSpec.id, mode === 'square-2d' && hasSquareData);\n this.setHighlightLayerVisible(this.squareExtrusionHighlightLayerSpec.id, mode === 'square-3d' && hasSquareData);\n }\n\n private hideAllDataLayers(): void {\n this.sourcesWithLayers.heatmap.setLayersVisible(false);\n this.sourcesWithLayers.hexgrid.setLayersVisible(false);\n this.sourcesWithLayers.square.setLayersVisible(false);\n this.sourcesWithLayers.region.setLayersVisible(false);\n this.setHighlightLayerVisible(this.hexOutlineLayerSpec.id, false);\n this.setHighlightLayerVisible(this.hexExtrusionHighlightLayerSpec.id, false);\n this.setHighlightLayerVisible(this.squareOutlineLayerSpec.id, false);\n this.setHighlightLayerVisible(this.squareExtrusionHighlightLayerSpec.id, false);\n }\n\n private addHighlightLayersToMap(hexgridSourceId: string, squareSourceId: string): void {\n const addHidden = (spec: { id: string; beforeID?: string; [key: string]: unknown }, sourceId: string) => {\n if (!this.mapLibreMap.getLayer(spec.id)) {\n // eslint-disable-next-line @typescript-eslint/no-explicit-any\n this.mapLibreMap.addLayer({ ...spec, source: sourceId } as any, spec.beforeID);\n }\n this.mapLibreMap.setLayoutProperty(spec.id, 'visibility', 'none');\n };\n\n addHidden(this.hexOutlineLayerSpec, hexgridSourceId);\n addHidden(this.hexExtrusionHighlightLayerSpec, hexgridSourceId);\n addHidden(this.squareOutlineLayerSpec, squareSourceId);\n addHidden(this.squareExtrusionHighlightLayerSpec, squareSourceId);\n }\n\n private setHighlightLayerVisible(layerId: string, visible: boolean): void {\n if (this.mapLibreMap.getLayer(layerId)) {\n this.mapLibreMap.setLayoutProperty(layerId, 'visibility', visible ? 'visible' : 'none');\n }\n }\n\n private applyLayerConfig(config: TrafficAreaAnalyticsConfig | undefined): void {\n const computedRange = this.lastAnalytics?.properties.ranges?.[this.activeMetric];\n\n const newHeatmapSpec = buildHeatmapLayerSpec(this.heatmapLayerSpec.id, config, computedRange);\n const newHexFillSpec = buildHexFillLayerSpec(this.hexFillLayerSpec.id, config, computedRange);\n const newHexExtrusionSpec = buildHexExtrusionLayerSpec(this.hexExtrusionLayerSpec.id, config, computedRange);\n const newHexExtrusionHighlightSpec = buildHexExtrusionHighlightLayerSpec(\n this.hexExtrusionHighlightLayerSpec.id,\n config,\n computedRange,\n );\n const newSquareFillSpec = buildSquareFillLayerSpec(this.squareFillLayerSpec.id, config, computedRange);\n const newSquareExtrusionSpec = buildSquareExtrusionLayerSpec(\n this.squareExtrusionLayerSpec.id,\n config,\n computedRange,\n );\n const newSquareExtrusionHighlightSpec = buildSquareExtrusionHighlightLayerSpec(\n this.squareExtrusionHighlightLayerSpec.id,\n config,\n computedRange,\n );\n\n changeLayerProps(newHeatmapSpec, this.heatmapLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexFillSpec, this.hexFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexExtrusionSpec, this.hexExtrusionLayerSpec, this.mapLibreMap);\n changeLayerProps(newHexExtrusionHighlightSpec, this.hexExtrusionHighlightLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareFillSpec, this.squareFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionSpec, this.squareExtrusionLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionHighlightSpec, this.squareExtrusionHighlightLayerSpec, this.mapLibreMap);\n\n this.heatmapLayerSpec = newHeatmapSpec;\n this.hexFillLayerSpec = newHexFillSpec;\n this.hexExtrusionLayerSpec = newHexExtrusionSpec;\n this.hexExtrusionHighlightLayerSpec = newHexExtrusionHighlightSpec;\n this.squareFillLayerSpec = newSquareFillSpec;\n this.squareExtrusionLayerSpec = newSquareExtrusionSpec;\n this.squareExtrusionHighlightLayerSpec = newSquareExtrusionHighlightSpec;\n\n this.applyFiltersToLayers();\n }\n\n private applyRegionConfig(config: TrafficAreaAnalyticsConfig | undefined): void {\n const newRegionFillSpec = buildRegionFillLayerSpec(this.regionFillLayerSpec.id, config);\n const newRegionLineSpec = buildRegionLineLayerSpec(this.regionLineLayerSpec.id, config);\n\n changeLayerProps(newRegionFillSpec, this.regionFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newRegionLineSpec, this.regionLineLayerSpec, this.mapLibreMap);\n\n this.regionFillLayerSpec = newRegionFillSpec;\n this.regionLineLayerSpec = newRegionLineSpec;\n\n // Re-apply region geometry in case `inverted` changed after data was already shown.\n if (this.lastAnalytics) {\n const inverted = config?.regionPolygon?.inverted ?? AREA_ANALYTICS_DEFAULTS.regionPolygon.inverted;\n this.sourcesWithLayers.region.show(this.buildRegionFC(this.lastAnalytics.features, inverted));\n }\n }\n\n private applyFiltersToLayers(): void {\n const metric = this.activeMetric;\n const filter = this.config?.metricConfig?.[metric]?.filters;\n\n const layerIds = [\n this.hexFillLayerSpec.id,\n this.hexExtrusionLayerSpec.id,\n this.squareFillLayerSpec.id,\n this.squareExtrusionLayerSpec.id,\n ];\n\n if (!filter || (filter.min === undefined && filter.max === undefined)) {\n for (const layerId of layerIds) {\n this.mapLibreMap.setFilter(layerId, null);\n }\n return;\n }\n\n const conditions: ExpressionSpecification[] = [];\n if (filter.min !== undefined) conditions.push(['>=', ['get', metric], filter.min]);\n if (filter.max !== undefined) conditions.push(['<=', ['get', metric], filter.max]);\n\n const filterExpr: ExpressionSpecification = conditions.length === 1 ? conditions[0] : ['any', ...conditions];\n\n for (const layerId of layerIds) {\n this.mapLibreMap.setFilter(layerId, filterExpr);\n }\n }\n\n private buildRegionFC(features: TrafficAreaAnalytics['features'], inverted: boolean): FeatureCollection {\n return {\n type: 'FeatureCollection',\n features: features.map((feature, i) => {\n const id = `traffic-area-analytics-region-${i}`;\n const geometry = inverted ? mask(feature as any).geometry : feature.geometry;\n return { ...feature, id, geometry, properties: { id } };\n }),\n };\n }\n}\n","import { indexedMagnitudes } from '@tomtom-org/maps-sdk/core';\nimport { isNil } from 'lodash-es';\nimport type {\n ExpressionFilterSpecification,\n FilterSpecification,\n LayerSpecification,\n LegacyFilterSpecification,\n Map,\n} from 'maplibre-gl';\nimport type { MultiSyntaxFilter, ValuesFilter } from '../../shared';\nimport { buildValuesFilter, getMergedAllFilter, getMergedAnyFilter } from '../../shared/mapLibreFilterUtils';\nimport type { TrafficCommonFilter } from '../types/trafficCommonConfig';\nimport type { TrafficFlowFilter, TrafficFlowFilters } from '../types/trafficFlowConfig';\nimport type { DelayFilter, TrafficIncidentsFilter, TrafficIncidentsFilters } from '../types/trafficIncidentsConfig';\nimport { incidentToIconCategoryMapping } from '../util/trafficIncidentMapping';\n\nconst toMultiSyntaxAllFilter = (\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n): MultiSyntaxFilter | null => {\n if (!newSyntaxExpressions.length) {\n return null;\n }\n if (newSyntaxExpressions.length === 1) {\n return {\n expression: newSyntaxExpressions[0] as ExpressionFilterSpecification,\n legacy: legacySyntaxExpressions[0] as LegacyFilterSpecification,\n };\n }\n return {\n expression: ['all', ...newSyntaxExpressions] as ExpressionFilterSpecification,\n legacy: ['all', ...legacySyntaxExpressions] as LegacyFilterSpecification,\n };\n};\n\nconst delayFilterToMapLibre = (delayFilter: DelayFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions = [];\n const legacySyntaxExpressions = [];\n if (delayFilter.mustHaveDelay && delayFilter.minDelayMinutes) {\n // there must be a delay and with the min specified value:\n const delaySeconds = delayFilter.minDelayMinutes * 60;\n newSyntaxExpressions.push(['>=', ['get', 'delay'], delaySeconds]);\n legacySyntaxExpressions.push(['>=', 'delay', delaySeconds]);\n } else if (delayFilter.mustHaveDelay) {\n // just expects a delay of any kind\n newSyntaxExpressions.push(['>', ['get', 'delay'], 0]);\n legacySyntaxExpressions.push(['>', 'delay', 0]);\n } else if (delayFilter.minDelayMinutes) {\n // Min delay expected, but also allows for non-existing delays:\n const delaySeconds = delayFilter.minDelayMinutes * 60;\n newSyntaxExpressions.push([\n 'any',\n ['!', ['has', 'delay']],\n ['==', ['get', 'delay'], 0],\n ['>=', ['get', 'delay'], delaySeconds],\n ]);\n legacySyntaxExpressions.push(['any', ['!has', 'delay'], ['==', 'delay', 0], ['>=', 'delay', delaySeconds]]);\n }\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\nconst addFilter = (\n filter: MultiSyntaxFilter | undefined | null,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n) => {\n if (filter) {\n newSyntaxExpressions.push(filter.expression);\n legacySyntaxExpressions.push(filter.legacy);\n }\n};\n\nconst addValuesFilter = (\n valuesFilter: ValuesFilter<string> | undefined,\n propName: string,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n) => {\n if (valuesFilter) {\n addFilter(buildValuesFilter(propName, valuesFilter), newSyntaxExpressions, legacySyntaxExpressions);\n }\n};\n\nconst addCommonFilterExpressions = (\n sdkFilter: TrafficCommonFilter,\n newSyntaxExpressions: unknown[],\n legacySyntaxExpressions: unknown[],\n): void => {\n addValuesFilter(sdkFilter.roadCategories, 'road_category', newSyntaxExpressions, legacySyntaxExpressions);\n addValuesFilter(sdkFilter.roadSubCategories, 'road_subcategory', newSyntaxExpressions, legacySyntaxExpressions);\n};\n\nconst buildMapLibreIncidentsFilter = (sdkFilter: TrafficIncidentsFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions: unknown[] = [];\n const legacySyntaxExpressions: unknown[] = [];\n\n addCommonFilterExpressions(sdkFilter, newSyntaxExpressions, legacySyntaxExpressions);\n\n if (sdkFilter.incidentCategories) {\n const incidentCategoryFilter = buildValuesFilter(\n 'icon_category_0',\n sdkFilter.incidentCategories,\n (value) => incidentToIconCategoryMapping[value],\n );\n addFilter(incidentCategoryFilter, newSyntaxExpressions, legacySyntaxExpressions);\n }\n if (sdkFilter.magnitudes) {\n const magnitudesFilter = buildValuesFilter('magnitude_of_delay', sdkFilter.magnitudes, (magnitude) =>\n indexedMagnitudes.indexOf(magnitude),\n );\n addFilter(magnitudesFilter, newSyntaxExpressions, legacySyntaxExpressions);\n }\n if (sdkFilter.delays) {\n addFilter(delayFilterToMapLibre(sdkFilter.delays), newSyntaxExpressions, legacySyntaxExpressions);\n }\n\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\n/**\n * @ignore\n */\nexport const buildMapLibreIncidentFilters = (incidentFilters: TrafficIncidentsFilters): MultiSyntaxFilter | null => {\n if (!incidentFilters?.any?.length) {\n return null;\n }\n const mapLibreFilters = incidentFilters.any\n .map(buildMapLibreIncidentsFilter)\n .filter((mapLibreFilter) => !isNil(mapLibreFilter));\n return getMergedAnyFilter(mapLibreFilters);\n};\n\nconst buildMapLibreFlowFilter = (sdkFilter: TrafficFlowFilter): MultiSyntaxFilter | null => {\n const newSyntaxExpressions: unknown[] = [];\n const legacySyntaxExpressions: unknown[] = [];\n\n addCommonFilterExpressions(sdkFilter, newSyntaxExpressions, legacySyntaxExpressions);\n if (sdkFilter.showRoadClosures) {\n const operator = sdkFilter.showRoadClosures === 'only' ? '==' : '!=';\n newSyntaxExpressions.push([operator, ['get', 'road_closure'], true]);\n legacySyntaxExpressions.push([operator, 'road_closure', true]);\n }\n\n return toMultiSyntaxAllFilter(newSyntaxExpressions, legacySyntaxExpressions);\n};\n\n/**\n * @ignore\n */\nexport const buildMapLibreFlowFilters = (flowFilters: TrafficFlowFilters): MultiSyntaxFilter | null => {\n if (!flowFilters?.any?.length) {\n return null;\n }\n const mapLibreFilters = flowFilters.any\n .map(buildMapLibreFlowFilter)\n .filter((mapLibreFilter) => !isNil(mapLibreFilter));\n return getMergedAnyFilter(mapLibreFilters);\n};\n\n/**\n * @ignore\n * @param filter\n * @param layers\n * @param mapLibreMap\n * @param originalFilters\n */\nexport const applyFilter = (\n filter: MultiSyntaxFilter | undefined,\n layers: LayerSpecification[],\n mapLibreMap: Map,\n originalFilters: Record<string, FilterSpecification | undefined>,\n) => {\n for (const layer of layers) {\n mapLibreMap.setFilter(\n layer.id,\n filter ? getMergedAllFilter(filter, originalFilters[layer.id]) : originalFilters[layer.id],\n );\n }\n};\n","import { isNil, omitBy } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport type { LayerSpecWithSource } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n filterLayersBySources,\n ModuleEvents,\n StyleSourceWithLayers,\n TRAFFIC_FLOW_SOURCE_ID,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyFilter, buildMapLibreFlowFilters } from './filters/trafficFilters';\nimport type { FlowConfig, TrafficFlowFilters } from './types/trafficFlowConfig';\nimport type { TrafficFlowModuleFeature } from './types/trafficFlowFeature';\nimport { trafficFlowMapping } from './util/trafficFlowMapping';\n\n/**\n * IDs of sources and layers for traffic flow module.\n */\ntype TrafficFlowSourcesWithLayers = {\n trafficFlow: StyleSourceWithLayers;\n};\n\n/**\n * Traffic Flow Module for displaying and configuring real-time traffic flow information on the map.\n *\n * This module controls the vector tile traffic flow layers that visualize current\n * traffic speed conditions using color-coded road segments.\n *\n * @remarks\n * **Features:**\n * - Toggle traffic flow visibility on/off\n * - Filter by road categories and types\n * - Color-coded speed visualization (green = free flow, red = congestion)\n * - Real-time traffic data from vector tiles\n * - Filter road closures\n *\n * **Visual Representation:**\n * - Green: Free-flowing traffic\n * - Yellow/Orange: Slow traffic\n * - Red: Heavy congestion\n * - Dark gray: Road closures\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { TrafficFlowModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module (auto-add to style if needed)\n * const trafficFlow = await TrafficFlowModule.get(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * trafficFlow.setVisible(false);\n * trafficFlow.setVisible(true);\n * ```\n *\n * @example\n * Filter by road type:\n * ```typescript\n * // Show only highway traffic\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk']\n * }\n * }]\n * });\n *\n * // Hide local streets\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'all_except',\n * values: ['street']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Show only road closures:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * showRoadClosures: 'only'\n * }]\n * });\n * ```\n *\n * @see [Traffic Flow Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-flow)\n * @see [Traffic Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic)\n *\n * @group Traffic Flow\n */\nexport class TrafficFlowModule extends AbstractMapModule<TrafficFlowSourcesWithLayers, FlowConfig> {\n private originalFilters!: Record<string, FilterSpecification | undefined>;\n\n /**\n * Retrieves a TrafficFlowModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization, visibility, and filters.\n *\n * @returns A promise that resolves to the initialized TrafficFlowModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state\n * - `ensureAddedToStyle`: Auto-add traffic flow to style if missing\n * - `filters`: Road category and type filters\n *\n * **Style Requirement:**\n * Traffic flow must be included in the map style or added via `ensureAddedToStyle`.\n *\n * @throws Error if traffic flow source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const trafficFlowModule = await TrafficFlowModule.get(map);\n * ```\n *\n * @example\n * Auto-add to style:\n * ```typescript\n * const trafficFlowModule = await TrafficFlowModule.get(map, {\n * visible: true,\n * filters: {\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk', 'primary']\n * }\n * }]\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: FlowConfig): Promise<TrafficFlowModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, TRAFFIC_FLOW_SOURCE_ID, 'trafficFlow');\n return new TrafficFlowModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: FlowConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const flowSource = this.mapLibreMap.getSource(TRAFFIC_FLOW_SOURCE_ID);\n if (!flowSource) {\n throw notInTheStyle(`init ${TrafficFlowModule.name} with source ID ${TRAFFIC_FLOW_SOURCE_ID}`);\n }\n this.originalFilters = {};\n for (const layer of this.getLayers()) {\n this.originalFilters[layer.id] = layer.filter;\n }\n return { trafficFlow: new StyleSourceWithLayers(this.mapLibreMap, flowSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: FlowConfig | undefined) {\n this._setVisible(config?.visible ?? false, false);\n this._filter(config?.filters, false);\n return config;\n }\n\n private getLayers(): LayerSpecWithSource[] {\n return filterLayersBySources(this.tomtomMap.mapLibreMap, [TRAFFIC_FLOW_SOURCE_ID]);\n }\n\n /**\n * Applies filters to traffic flow display.\n *\n * @param filters - Filter configuration for road types, categories, and closures.\n * Pass `undefined` to reset to defaults (show all).\n *\n * @remarks\n * **Filter Options:**\n * - `roadCategories`: Filter by road importance (motorway, trunk, primary, etc.)\n * - `roadSubCategories`: Filter by specific street types\n * - `showRoadClosures`: Show only closures or exclude them\n *\n * **Available Road Categories:**\n * - `motorway`: Major highways\n * - `trunk`: Major roads\n * - `primary`: Primary roads\n * - `secondary`: Secondary roads\n * - `tertiary`: Tertiary roads\n * - `street`: Local streets\n *\n * **Filter Logic:**\n * Uses \"any\" (OR) logic - traffic matching any filter is shown.\n *\n * @example\n * Show only major roads:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'only',\n * values: ['motorway', 'trunk', 'primary']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Hide street-level traffic:\n * ```typescript\n * trafficFlow.filter({\n * any: [{\n * roadCategories: {\n * show: 'all_except',\n * values: ['street']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Multiple filter criteria:\n * ```typescript\n * trafficFlow.filter({\n * any: [\n * {\n * roadCategories: { show: 'only', values: ['motorway'] }\n * },\n * {\n * showRoadClosures: 'only'\n * }\n * ]\n * });\n * ```\n *\n * @example\n * Reset filters:\n * ```typescript\n * trafficFlow.filter(undefined);\n * ```\n */\n filter(filters?: TrafficFlowFilters) {\n this._filter(filters);\n }\n\n private _filter(filters: TrafficFlowFilters | undefined, updateConfig = true) {\n if (this.tomtomMap.mapReady) {\n if (filters?.any?.length) {\n const filterExpression = buildMapLibreFlowFilters(filters);\n if (filterExpression) {\n applyFilter(filterExpression, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n } else if (this.config?.filters?.any?.length) {\n applyFilter(undefined, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n }\n\n if (updateConfig) {\n this.config = omitBy({ ...this.config, filters }, isNil);\n this.emitConfigChange();\n }\n }\n\n /**\n * Sets the visibility of traffic flow layers.\n *\n * @param visible - `true` to show traffic flow, `false` to hide it.\n *\n * @example\n * ```typescript\n * trafficFlow.setVisible(true); // Show traffic\n * trafficFlow.setVisible(false); // Hide traffic\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficFlow.setLayersVisible(visible);\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n /**\n * Returns if any layer for traffic flow is visible or not.\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.trafficFlow.isAnyLayerVisible();\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible traffic flow features.\n *\n * @remarks\n * Returns all traffic flow features currently rendered in the visible map area.\n * These represent road segments with real-time traffic speed information.\n *\n * **Feature Properties:**\n * Properties depend on the traffic flow vector tile schema and may include:\n * - Current speed\n * - Free flow speed\n * - Road category\n * - Congestion level\n * - Road closure status\n *\n * @example\n * ```typescript\n * const shown = trafficFlow.getShown();\n * console.log(`Visible flow segments: ${shown.trafficFlow.length}`);\n * shown.trafficFlow.forEach(segment => {\n * console.log('Road segment:', segment.properties);\n * });\n * ```\n *\n * @example\n * Analyze congestion in viewport:\n * ```typescript\n * const shown = trafficFlow.getShown();\n * const congested = shown.trafficFlow.filter(\n * segment => segment.properties.speed < segment.properties.freeFlowSpeed * 0.5\n * );\n * console.log(`Congested segments: ${congested.length}`);\n * ```\n */\n getShown(): { trafficFlow: TrafficFlowModuleFeature[] } {\n return {\n trafficFlow: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.trafficFlow.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(trafficFlowMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficFlowModuleFeature, FlowConfig, never> {\n return new CombinedEvents(\n new UserEvents<TrafficFlowModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.trafficFlow,\n this.config?.events,\n trafficFlowMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","/**\n * Direction chevron sprite for {@link TrafficIncidentOverlayModule}.\n *\n * A single right-pointing chevron, rasterised from SVG and registered via\n * `map.addImage()`. Referenced as a `line-pattern` on a line layer stacked on top\n * of `-inner-solid` — transparent pixels let the magnitude colour show through,\n * opaque strokes paint the direction indicator.\n *\n * Because REST LineStrings arrive in travel order, MapLibre tiles the pattern\n * along that direction automatically; no per-feature rotation needed. A dark halo\n * under the white stroke keeps the chevron legible on all three jam tints\n * (yellow `minor`, orange `moderate`, red `major`).\n *\n * @module\n * @ignore\n */\nimport { isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { parseSvg } from '../../shared/resources';\n\nexport const INCIDENT_DIRECTION_CHEVRON_IMAGE_ID = 'tt-traffic-incident-direction-chevron';\n\n/**\n * Rasterised chevron, memoised. Returns `undefined` in non-DOM environments\n * (node-only unit tests); callers should skip sprite registration in that case.\n * @ignore\n */\nexport const getIncidentDirectionChevronImage = (): HTMLImageElement | undefined => {\n if (!isDOMImageSupported()) return undefined;\n if (!cachedImg) cachedImg = svgToImg(parseSvg(svgMarkup));\n return cachedImg;\n};\n\nconst svgMarkup = `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"48\" height=\"18\" viewBox=\"0 0 48 18\">\n<path d=\"M 18 4 L 30 9 L 18 14\" fill=\"none\" stroke=\"rgba(20, 30, 50, 0.5)\" stroke-width=\"5\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n<path d=\"M 18 4 L 30 9 L 18 14\" fill=\"none\" stroke=\"white\" stroke-width=\"3\" stroke-linecap=\"round\" stroke-linejoin=\"round\"/>\n</svg>`;\n\nlet cachedImg: HTMLImageElement | undefined;\n","/**\n * Read per-magnitude incident colours from the loaded MapLibre style. Missing layers fall\n * back silently; layers present with a non-string `line-color` (match/case/etc.) fall back\n * and warn once — the author likely intended an override we can't evaluate here.\n *\n * @module\n * @ignore\n */\n\nimport type { Map as MapLibreMap } from 'maplibre-gl';\n\nconst MAGNITUDES = ['unknown', 'minor', 'moderate', 'major', 'indefinite'] as const;\ntype Magnitude = (typeof MAGNITUDES)[number];\n\n/** @ignore */\nexport type IncidentPalette = {\n outline: Record<Magnitude, string>;\n inner: Record<Magnitude, string>;\n};\n\ntype LayerIdPair = { outline: string; inner: string };\n\n// Layer IDs we pull colours from. Update when the loaded style renames them.\nconst LAYER_IDS: Record<Magnitude, LayerIdPair> = {\n unknown: { outline: 'TrafficIncidents - No delay outline', inner: 'TrafficIncidents - No delay pattern' },\n minor: { outline: 'TrafficIncidents - Minor jam outline', inner: 'TrafficIncidents - Minor jam' },\n moderate: { outline: 'TrafficIncidents - Moderate jam outline', inner: 'TrafficIncidents - Moderate jam' },\n major: { outline: 'TrafficIncidents - Major jam outline', inner: 'TrafficIncidents - Major jam' },\n indefinite: { outline: 'TrafficIncidents - Closed road outline', inner: 'TrafficIncidents - Closed road pattern' },\n};\n\n/** @ignore */\nexport const FALLBACK_INCIDENT_PALETTE: IncidentPalette = {\n outline: {\n unknown: 'hsl(198, 20%, 54%)',\n minor: 'hsl(45, 85%, 36%)',\n moderate: 'hsl(9, 87%, 31%)',\n major: 'hsl(0, 100%, 17%)',\n indefinite: 'hsl(0, 100%, 34%)',\n },\n inner: {\n unknown: 'hsl(198, 20%, 90%)',\n minor: 'hsl(45, 100%, 51%)',\n moderate: 'hsl(9, 97%, 51%)',\n major: 'hsl(0, 100%, 34%)',\n indefinite: 'hsl(197, 28%, 95%)',\n },\n};\n\n/** @ignore */\nexport const readIncidentPalette = (map: Pick<MapLibreMap, 'getStyle'>): IncidentPalette => {\n const layers = map.getStyle()?.layers ?? [];\n const byId = new Map<string, unknown>();\n for (const layer of layers) {\n byId.set(layer.id, layer);\n }\n\n const palette: IncidentPalette = {\n outline: { ...FALLBACK_INCIDENT_PALETTE.outline },\n inner: { ...FALLBACK_INCIDENT_PALETTE.inner },\n };\n const nonLiteralLayerIds: string[] = [];\n\n for (const magnitude of MAGNITUDES) {\n const ids = LAYER_IDS[magnitude];\n const outline = readLineColor(byId.get(ids.outline), ids.outline, nonLiteralLayerIds);\n const inner = readLineColor(byId.get(ids.inner), ids.inner, nonLiteralLayerIds);\n if (outline) palette.outline[magnitude] = outline;\n if (inner) palette.inner[magnitude] = inner;\n }\n\n if (nonLiteralLayerIds.length > 0) {\n console.warn(\n '[TrafficIncidentOverlayModule] style layers have non-literal `line-color` ' +\n '(match/case/etc.); falling back to default colour for: ' +\n nonLiteralLayerIds.join(', '),\n );\n }\n\n return palette;\n};\n\nconst readLineColor = (layer: unknown, layerId: string, nonLiteralSink: string[]): string | undefined => {\n if (!layer || typeof layer !== 'object') return undefined;\n const paint = (layer as { paint?: unknown }).paint;\n if (!paint || typeof paint !== 'object') return undefined;\n const color = (paint as { 'line-color'?: unknown })['line-color'];\n if (typeof color === 'string') return color;\n if (color !== undefined) nonLiteralSink.push(layerId);\n return undefined;\n};\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport { INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from '../util/incidentDirectionChevron';\nimport { FALLBACK_INCIDENT_PALETTE, type IncidentPalette } from '../util/readIncidentPalette';\n\n/**\n * Incident rendering for {@link TrafficIncidentOverlayModule}. All road-class-driven geometry\n * is dropped: REST features carry no `road_category`, `road_subcategory`, `display_class`, or\n * `point_type`, so per-road-class widths and offsets would be uniform approximations\n * pretending to be precise. Incidents render as a uniform stripe centred on the road —\n * callers wanting full per-road-class styling can use the vector-tile\n * `TrafficIncidentsModule` instead.\n *\n * Per-magnitude colours come from a palette passed in (see\n * {@link ../util/readIncidentPalette}). Each factory bakes `beforeID` into every layer spec.\n *\n * @module incidentDetailsLayers\n * @ignore\n */\n\ntype LineLayerWithBefore = Omit<LineLayerSpecification, 'source'> & { beforeID?: string };\ntype SymbolLayerWithBefore = Omit<SymbolLayerSpecification, 'source'> & { beforeID?: string };\n\n// --- Line layers ---------------------------------------------------------------------------\n\n// Zoom interpolations collapsed from canonical's `primary` road-class path. Canonical ramps\n// widths up by road class (motorway > trunk > primary > ... > street); we don't have road\n// class, so every incident renders at the primary-road width.\nconst INNER_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 0,\n 9,\n 0.96,\n 13,\n 2.1,\n 19,\n 10.8,\n];\nconst OUTLINE_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 0.75,\n 9,\n 1.96,\n 13,\n 3.6,\n 19,\n 13.8,\n];\n\n// Default case uses `major` so an unexpected magnitude is loud, not silent.\nconst buildColorExpr = (colors: IncidentPalette['outline']): ExpressionSpecification => [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n colors.unknown,\n 'minor',\n colors.minor,\n 'moderate',\n colors.moderate,\n 'major',\n colors.major,\n 'indefinite',\n colors.indefinite,\n colors.major,\n];\n\n// Pattern sprites for magnitudes with no solid colour (no-delay stripe, closed-road stripe).\nconst INNER_PATTERN: ExpressionSpecification = [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'unknown',\n 'traffic-incidents-no_delay-pattern',\n 'indefinite',\n 'traffic-incidents-road_closed-pattern',\n '',\n];\n\n// Focus treatment: pop focused features, leave everything else as-is.\n// - `focused=null` (no focus active): every feature renders at full strength.\n// - `focused=true`: wider stripe + outline painted beneath (when focus styling is enabled).\n// - `focused=false`: identical to `focused=null` — unfocused features are not dimmed.\n//\n// Defaults are applied via `resolveFocusStyle` and can be overridden / disabled via the\n// module's `focus` config.\nconst DEFAULT_FOCUS_OUTLINE_COLOR = '#000';\nconst DEFAULT_FOCUS_WIDTH_SCALE = 1.6;\n\ntype ResolvedFocus = { outlineColor: string; widthScale: number };\n\n/**\n * Resolve the user's `focus` config into an internal shape:\n * `false` → `null` (no halo layer, no width pop, no feature-state expressions on width)\n * `undefined` → defaults\n * object → defaults merged with overrides\n * @ignore\n */\nexport const resolveFocusStyle = (\n focus: false | { outlineColor?: string; widthScale?: number } | undefined,\n): ResolvedFocus | null => {\n if (focus === false) return null;\n return {\n outlineColor: focus?.outlineColor ?? DEFAULT_FOCUS_OUTLINE_COLOR,\n widthScale: focus?.widthScale ?? DEFAULT_FOCUS_WIDTH_SCALE,\n };\n};\n\n// Halo spec — a sharp outline painted behind the magnitude outline so it reads as a\n// border around the focused stripe. Width tracks the focused outline plus a small constant\n// offset; no blur, so the edge is crisp.\nconst HALO_BLUR = 0;\nconst HALO_OPACITY = 1;\nconst HALO_WIDTH: ExpressionSpecification = [\n 'interpolate',\n ['exponential', 1.5],\n ['zoom'],\n 5,\n 2.2,\n 9,\n 4.6,\n 13,\n 8.3,\n 19,\n 26,\n];\n\n// Halo opacity: full on focused features, zero elsewhere (MapLibre can't filter on feature-state\n// at the filter level for line layers, so we hide via paint instead).\nconst haloOpacity: ExpressionSpecification = ['case', ['==', ['feature-state', 'focused'], true], HALO_OPACITY, 0];\n\n// MapLibre allows at most ONE zoom-based `interpolate`/`step` in the entire expression tree for\n// a given paint property. A `case` branching between two `interpolate` expressions counts as\n// two, which is illegal. The legal pattern is a single outer `interpolate` whose *stop values*\n// (not the zoom axis) contain the feature-state case — per-feature values inside stop outputs\n// are evaluated at render time and are not themselves zoom-based sub-expressions.\n//\n// focusWidth rewrites ['interpolate', curve, ['zoom'], z0, v0, z1, v1, ...]\n// into ['interpolate', curve, ['zoom'], z0, case(v0*s|v0), z1, case(v1*s|v1), ...]\n// `scale === null` returns the original expression unmodified — used when the focus\n// visual is disabled, so widths don't carry a feature-state expression at all.\nconst focusWidth = (interp: ExpressionSpecification, scale: number | null): ExpressionSpecification => {\n if (scale === null) return interp;\n const [op, curve, input, ...stops] = interp as unknown as [\n string,\n ExpressionSpecification,\n ExpressionSpecification,\n ...(number | ExpressionSpecification)[],\n ];\n const focusedStops = stops.map((v, i) =>\n i % 2 === 1\n ? ([\n 'case',\n ['==', ['feature-state', 'focused'], true],\n (v as number) * scale,\n v,\n ] as ExpressionSpecification)\n : v,\n );\n return [op, curve, input, ...focusedStops] as unknown as ExpressionSpecification;\n};\n\nconst SOLID_FILTER: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['minor', 'moderate', 'major']],\n];\nconst PATTERN_FILTER: ExpressionSpecification = [\n 'in',\n ['get', 'magnitudeOfDelay'],\n ['literal', ['unknown', 'indefinite']],\n];\n\n/**\n * 4–5 line layers covering all five delay magnitudes plus an optional focus halo:\n * - focus-halo (only emitted when `focus !== null`; visible only on feature-state.focused=true)\n * - outline (all magnitudes, colour via match)\n * - inner solid colour (minor/moderate/major)\n * - inner direction chevron (minor/moderate/major — white chevron pattern on top of solid,\n * stretches with line width so it's visible at all zooms where the stripe itself is visible)\n * - inner pattern (unknown → no-delay stripes, indefinite → road-closed stripes)\n *\n * Replaces canonical's 10 per-magnitude layer pairs; uses match-on-magnitude expressions\n * inside paint rather than duplicating layers. The chevron layer supersedes the canonical\n * per-feature `roads-arrow` symbol (which only activates at z15+ and drops out on short\n * LineStrings because sprite spacing is absolute-pixel). `beforeID` is applied to every layer.\n *\n * `focus === null` (caller passed `focus: false` in the module config) drops the halo layer\n * entirely and leaves widths free of any feature-state expression — `setFocus()` still\n * writes `feature-state.focused`, but no layer reads it.\n * @ignore\n */\nexport const buildCanonicalIncidentLineLayers = (\n sourceId: string,\n beforeID: string | undefined,\n palette: IncidentPalette = FALLBACK_INCIDENT_PALETTE,\n focus: ResolvedFocus | null = resolveFocusStyle(undefined),\n): LineLayerWithBefore[] => {\n const widthScale = focus?.widthScale ?? null;\n const layers: LineLayerWithBefore[] = [];\n if (focus !== null) {\n layers.push({\n // Outline halo. Painted FIRST so it sits beneath the magnitude outline and\n // shows as a border around it; invisible for non-focused features via\n // `line-opacity: 0`.\n id: `${sourceId}-focus-halo`,\n type: 'line',\n minzoom: 6,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': focus.outlineColor,\n 'line-width': HALO_WIDTH,\n 'line-blur': HALO_BLUR,\n 'line-opacity': haloOpacity,\n },\n beforeID,\n });\n }\n layers.push(\n {\n id: `${sourceId}-outline`,\n type: 'line',\n minzoom: 6,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': buildColorExpr(palette.outline),\n 'line-width': focusWidth(OUTLINE_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n id: `${sourceId}-inner-solid`,\n type: 'line',\n minzoom: 6,\n filter: SOLID_FILTER,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': buildColorExpr(palette.inner),\n 'line-width': focusWidth(INNER_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n // `line-cap: butt` so the rounded end-caps of `-inner-solid` aren't re-stamped by the\n // pattern (which would bleed the chevron into the cap).\n id: `${sourceId}-inner-chevron`,\n type: 'line',\n minzoom: 12,\n filter: SOLID_FILTER,\n layout: { 'line-cap': 'butt', 'line-join': 'round' },\n paint: {\n 'line-pattern': INCIDENT_DIRECTION_CHEVRON_IMAGE_ID,\n 'line-width': focusWidth(INNER_WIDTH, widthScale),\n },\n beforeID,\n },\n {\n id: `${sourceId}-inner-pattern`,\n type: 'line',\n minzoom: 6,\n filter: PATTERN_FILTER,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: { 'line-pattern': INNER_PATTERN, 'line-width': focusWidth(INNER_WIDTH, widthScale) },\n beforeID,\n },\n );\n return layers;\n};\n\n// --- Symbol layers ------------------------------------------------------------------------\n\nconst ICON_SIZE: ExpressionSpecification = ['step', ['zoom'], 0.7, 14, 0.8, 16, 0.9, 18, 1];\nconst JAM_TEXT_SIZE: ExpressionSpecification = ['step', ['zoom'], 12.6, 14, 14.4, 16, 16.2, 18, 18];\n\n// Non-jam, non-closed-road incidents map category → sprite. `''` suppresses rendering for\n// categories with no canonical sprite (jam — handled by jam-marker; animals-on-road,\n// narrow-lanes — canonical omits them).\nconst INCIDENT_MARKER_ICON: ExpressionSpecification = [\n 'match',\n ['get', 'category'],\n 'other',\n 'traffic-incidents-info',\n 'accident',\n 'traffic-incidents-accident',\n 'fog',\n 'traffic-incidents-fog',\n 'danger',\n 'traffic-incidents-roadworks',\n 'rain',\n 'traffic-incidents-rain',\n 'frost',\n 'traffic-incidents-frost',\n 'lane-closed',\n 'traffic-incidents-lane_closed',\n 'roadworks',\n 'traffic-incidents-roadworks',\n 'wind',\n 'traffic-incidents-wind',\n 'flooding',\n 'traffic-incidents-flooding',\n 'broken-down-vehicle',\n 'traffic-incidents-broken_down_vehicle',\n '',\n];\n\n// Canonical declutter: hide short-delay incidents until the user zooms in further. Hides\n// indefinite (road closed) — those are drawn by the closed-road-marker layer.\nconst INCIDENT_MARKER_FILTER: ExpressionSpecification = [\n 'all',\n ['!=', ['get', 'magnitudeOfDelay'], 'indefinite'],\n [\n 'any',\n ['!', ['has', 'delayInSeconds']],\n [\n 'step',\n ['zoom'],\n ['>', ['get', 'delayInSeconds'], 900],\n 14,\n ['>', ['get', 'delayInSeconds'], 600],\n 15,\n ['>', ['get', 'delayInSeconds'], 300],\n 16,\n true,\n ],\n ],\n];\n\nconst JAM_MARKER_FILTER: ExpressionSpecification = [\n 'all',\n ['==', ['get', 'category'], 'jam'],\n ['!=', ['get', 'magnitudeOfDelay'], 'unknown'],\n ['!=', ['get', 'magnitudeOfDelay'], 'indefinite'],\n [\n 'step',\n ['zoom'],\n ['>', ['get', 'delayInSeconds'], 900],\n 13,\n ['>', ['get', 'delayInSeconds'], 600],\n 14,\n ['>', ['get', 'delayInSeconds'], 300],\n 15,\n true,\n ],\n];\n\nconst CLOSED_ROAD_FILTER: ExpressionSpecification = ['==', ['get', 'magnitudeOfDelay'], 'indefinite'];\n\n// Jam sprite: `traffic-jam-<magnitude>-<size>`. Size suffix picks a sprite wide enough to fit\n// the delay text rendered alongside (~<10 min, <1 hr, ≥1 hr). Simplified from canonical's\n// ~25-line size selector — the visual outcome is the same.\nconst JAM_ICON: ExpressionSpecification = [\n 'concat',\n 'traffic-jam-',\n ['get', 'magnitudeOfDelay'],\n [\n 'case',\n ['<', ['get', 'delayInSeconds'], 600],\n '-small',\n ['<', ['get', 'delayInSeconds'], 3600],\n '-medium',\n '-large',\n ],\n];\n\n// Canonical delay formatting: \"1 min\" / \"X min\" / \"Y hr Z min\" (drops \" 0 min\" on exact hours).\nconst JAM_TEXT: ExpressionSpecification = [\n 'case',\n ['<=', ['get', 'delayInSeconds'], 60],\n '1 min',\n ['>=', ['get', 'delayInSeconds'], 3600],\n [\n 'concat',\n ['to-string', ['floor', ['/', ['to-number', ['get', 'delayInSeconds']], 3600]]],\n ' hr ',\n [\n 'case',\n ['==', ['%', ['round', ['/', ['to-number', ['get', 'delayInSeconds']], 60]], 60], 0],\n '',\n [\n 'concat',\n ['to-string', ['round', ['%', ['/', ['to-number', ['get', 'delayInSeconds']], 60], 60]]],\n ' min',\n ],\n ],\n ],\n ['concat', ['round', ['/', ['get', 'delayInSeconds'], 60]], ' min'],\n];\n\n// Worst-delay incidents sort highest (top). Canonical adds a magnitude tiebreaker (index 0-4);\n// dropped because delayInSeconds dominates by orders of magnitude.\nconst SORT_KEY: ExpressionSpecification = ['case', ['has', 'delayInSeconds'], ['*', -1, ['get', 'delayInSeconds']], 0];\n\n/**\n * 3 symbol layers in z-order: incident marker, jam marker (with delay text), closed-road\n * marker. Canonical splits jam into a pure icon (z12+) and an icon+delay-text layer (z13+);\n * stock MapLibre ignores the `tomtom:transition` hint that would hide one behind the other,\n * so we render a single jam-with-text layer from z12. Direction is rendered by the\n * `-inner-chevron` line layer, not a per-feature symbol.\n * @ignore\n */\nexport const buildCanonicalIncidentSymbolLayers = (\n sourceId: string,\n beforeID: string | undefined,\n): SymbolLayerWithBefore[] => [\n {\n id: `${sourceId}-incident-marker`,\n type: 'symbol',\n minzoom: 13,\n filter: INCIDENT_MARKER_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-anchor': 'bottom-right',\n 'icon-offset': [-1, 3],\n 'icon-image': INCIDENT_MARKER_ICON,\n 'icon-size': ICON_SIZE,\n 'symbol-sort-key': SORT_KEY,\n },\n beforeID,\n },\n {\n id: `${sourceId}-jam-marker`,\n type: 'symbol',\n minzoom: 12,\n filter: JAM_MARKER_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'icon-anchor': 'bottom-left',\n 'icon-offset': [2, 3],\n 'icon-image': JAM_ICON,\n 'icon-size': ICON_SIZE,\n 'icon-padding': 0,\n 'text-anchor': 'bottom-left',\n 'text-field': JAM_TEXT,\n 'text-font': ['NotoSans-CondensedBold'],\n 'text-offset': [2.7, -0.75],\n 'text-size': JAM_TEXT_SIZE,\n 'symbol-sort-key': SORT_KEY,\n },\n paint: { 'text-color': 'hsl(210, 30%, 14%)' },\n beforeID,\n },\n {\n id: `${sourceId}-closed-road-marker`,\n type: 'symbol',\n minzoom: 12,\n filter: CLOSED_ROAD_FILTER,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-anchor': 'bottom-right',\n 'icon-offset': [-1, 3],\n 'icon-image': 'traffic-incidents-road_closed',\n 'icon-size': ICON_SIZE,\n },\n beforeID,\n },\n];\n","import type { TrafficIncident, TrafficIncidentDetails } from '@tomtom-org/maps-sdk/core';\nimport type { BeforeLayerConfig } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n GeoJSONSourceWithLayers,\n ModuleEvents,\n mapStyleLayerIDs,\n UserEvents,\n} from '../shared';\nimport { addOrUpdateImage, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildCanonicalIncidentLineLayers,\n buildCanonicalIncidentSymbolLayers,\n resolveFocusStyle,\n} from './layers/incidentDetailsLayers';\nimport type { TrafficIncidentOverlayConfig } from './types/trafficIncidentOverlayConfig';\nimport { getIncidentDirectionChevronImage, INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from './util/incidentDirectionChevron';\nimport { readIncidentPalette } from './util/readIncidentPalette';\n\ntype Sources = {\n incidents: GeoJSONSourceWithLayers<TrafficIncidentDetails>;\n};\n\n// Resolve a caller-supplied `beforeLayerConfig` value into a concrete MapLibre layer-ID.\n// Undefined (no config) places incidents below `lowestLabel` so labels stay readable on top;\n// `'top'` passes undefined to MapLibre so layers go to the end of the stack. Mirrors\n// `resolveBeforeID` in `traffic/layers/areaAnalyticsLayers.ts` for consistency.\nconst resolveBeforeID = (beforeLayerConfig?: BeforeLayerConfig): string | undefined => {\n if (beforeLayerConfig === 'top') return undefined;\n if (beforeLayerConfig) return mapStyleLayerIDs[beforeLayerConfig];\n return mapStyleLayerIDs.lowestLabel;\n};\n\n/**\n * Renders traffic incidents returned by `trafficIncidentDetails()` on the map.\n *\n * Use this module when you fetch incidents via the Incident Details service and want\n * to display them as a developer-controlled overlay. {@link TrafficIncidentsModule} is\n * the vector-tile alternative — hidden in the default style, so the overlay renders\n * on its own without collision; if you've enabled it elsewhere in your app, hide it\n * again before using the overlay to avoid double-rendering.\n *\n * ## Rendering approach\n *\n * 7–8 layers — 4 to 5 line (optional focus-halo, outline, solid inner, direction chevron,\n * pattern inner) and 3 symbol (incident marker, jam marker with delay text, closed-road\n * marker). Per-magnitude styling uses match expressions on `magnitudeOfDelay` inside a\n * single layer instead of one layer per magnitude. Colours are read from the loaded style\n * at init (see {@link ./util/readIncidentPalette}), falling back to built-in defaults when\n * absent. Direction is rendered as a tiled `line-pattern` (chevron sprite registered at\n * init) instead of the vector-tile module's per-feature `roads-arrow` symbol — the\n * pattern is continuous, so it reads on short jams and at planning zooms (z12+) where\n * the per-feature symbol only activates at z15+.\n *\n * ## Focus\n *\n * {@link setFocus} writes MapLibre feature-state `focused=true|false` on each rendered\n * incident. The default visual treatment widens the focused subset and paints a black\n * outline beneath it; unfocused incidents are unchanged. Override or disable the visual\n * treatment via the `focus` config — `focus: false` keeps `setFocus` writing feature-state\n * but emits no halo layer and no width pop, leaving callers free to drive their own\n * styling off `feature-state.focused`.\n *\n * ## API limitations\n *\n * The Incident Details REST API returns a feature list, not a rendering-ready tile.\n * Several tags that the vector-tile pipeline injects per-feature are absent:\n *\n * - **Road classification** (`road_category`, `road_subcategory`) — the vector-tile\n * pipeline scales line width and offset by road class (motorway wider than street,\n * stripe placed alongside the road on one carriageway). Without it, per-road-class\n * geometry becomes a guess: we render every incident as a uniform stripe centred on\n * the road, no offset.\n * - **Traffic-direction side** (`left_hand_traffic`) — only meaningful when paired with\n * road-class-driven offset; irrelevant here.\n * - **Declutter priority** (`display_class`) — the vector-tile pipeline hides\n * low-priority incidents at low zooms; we show everything above the layer minzoom.\n * - **Endpoint restriction** (`point_type`) — the vector-tile pipeline limits jam and\n * closed-road icons to line endpoints; we render on every matching feature.\n * - **Secondary cause** (`icon_category_1`) — the vector-tile pipeline layers a cause\n * icon (e.g. accident) over the jam icon; we show only the primary category.\n *\n * Callers wanting full per-road-class visual fidelity should use\n * {@link TrafficIncidentsModule} instead.\n *\n * ## Known limitations\n *\n * - **Overlapping incidents** — when multiple incidents share the same point or stack\n * along the same road segment, MapLibre's symbol collision culling keeps only the\n * highest-sort-key feature. Culled features are also invisible to\n * `queryRenderedFeatures`, so hover and click cannot reach them. If you need to\n * surface multiple incidents at one location, query the source data directly\n * (the `FeatureCollection` you passed to {@link show}) rather than relying on\n * render-time hit-testing.\n *\n * @example\n * Fetch and render incidents for Amsterdam:\n * ```typescript\n * import { TomTomConfig } from '@tomtom-org/maps-sdk/core';\n * import { TomTomMap, TrafficIncidentOverlayModule } from '@tomtom-org/maps-sdk/map';\n * import { trafficIncidentDetails } from '@tomtom-org/maps-sdk/services';\n *\n * TomTomConfig.instance.put({ apiKey: '<API_KEY>' });\n *\n * const map = new TomTomMap({ mapLibre: { container: 'map', center: [4.9, 52.37], zoom: 13 } });\n *\n * const overlay = await TrafficIncidentOverlayModule.get(map);\n * const result = await trafficIncidentDetails({\n * bbox: [4.85, 52.34, 4.95, 52.40],\n * timeValidityFilter: ['present'],\n * });\n * await overlay.show(result);\n *\n * // Highlight a subset.\n * overlay.setFocus([result.features[0].properties.id as string]);\n * ```\n *\n * @see {@link TrafficIncidentsModule} — the vector-tile alternative. Hidden in the\n * default style; if you've enabled it explicitly elsewhere in your app, hide it again\n * before using the overlay to avoid double-rendering.\n * @see [Traffic Incident Overlay Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-incident-overlay)\n * @see [Traffic Incident Details Service](https://docs.tomtom.com/maps-sdk-js/guides/services/traffic/traffic-incident-details)\n *\n * @group Traffic\n */\nexport class TrafficIncidentOverlayModule extends AbstractMapModule<Sources, TrafficIncidentOverlayConfig> {\n private lastResult: TrafficIncidentDetails | null = null;\n private lastFeatureIds: string[] = [];\n private lastFocusIds: readonly string[] | null = null;\n private readonly shownFeaturesHandlers: ((result: TrafficIncidentDetails) => void)[] = [];\n\n static async get(\n tomtomMap: TomTomMap,\n config?: TrafficIncidentOverlayConfig,\n ): Promise<TrafficIncidentOverlayModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new TrafficIncidentOverlayModule(tomtomMap, config ?? {});\n }\n\n private constructor(map: TomTomMap, config?: TrafficIncidentOverlayConfig) {\n super('geojson', map, config);\n }\n\n /** @ignore */\n protected _initSourcesWithLayers(config?: TrafficIncidentOverlayConfig, _restore?: boolean): Sources {\n const sourceId = `traffic-incident-overlay-${this.instanceIndex}`;\n\n // Skipped in non-DOM envs — the image getter returns undefined there.\n const chevronImg = getIncidentDirectionChevronImage();\n if (chevronImg) {\n void addOrUpdateImage(\n 'if-not-in-sprite',\n INCIDENT_DIRECTION_CHEVRON_IMAGE_ID,\n chevronImg,\n this.mapLibreMap,\n );\n }\n\n // Layers are inserted below `lowestLabel` by default so incidents sit under map\n // labels (roads, places, POIs) rather than obscuring them — matches the routing,\n // analytics, and geometries modules. Callers can override via `beforeLayerConfig`.\n const beforeID = resolveBeforeID(config?.beforeLayerConfig);\n const palette = readIncidentPalette(this.mapLibreMap);\n const focus = resolveFocusStyle(config?.focus);\n const layerSpecs = [\n ...buildCanonicalIncidentLineLayers(sourceId, beforeID, palette, focus),\n ...buildCanonicalIncidentSymbolLayers(sourceId, beforeID),\n ];\n\n return {\n incidents: new GeoJSONSourceWithLayers<TrafficIncidentDetails>(this.mapLibreMap, sourceId, layerSpecs),\n };\n }\n\n /** @ignore */\n protected _applyConfig(config: TrafficIncidentOverlayConfig | undefined): TrafficIncidentOverlayConfig | undefined {\n if (config?.visible === false) {\n this.sourcesWithLayers.incidents.setLayersVisible(false);\n } else {\n this.sourcesWithLayers.incidents.setLayersVisible(true);\n }\n this.applyBeforeLayer(config?.beforeLayerConfig);\n return config;\n }\n\n private applyBeforeLayer(beforeLayerConfig?: BeforeLayerConfig): void {\n const beforeId = resolveBeforeID(beforeLayerConfig);\n for (const lid of this.sourcesWithLayers.incidents.sourceAndLayerIDs.layerIDs) {\n this.mapLibreMap.moveLayer(lid, beforeId);\n }\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n this.initSourcesWithLayers(this.config, true);\n if (this.lastResult) {\n this.sourcesWithLayers.incidents.show(this.lastResult);\n }\n this._applyConfig(this.config);\n if (this.lastFocusIds !== null && this.lastFocusIds.length > 0) {\n this.setFocus(this.lastFocusIds);\n }\n }\n\n /**\n * Render the service result. Replaces any previously shown snapshot.\n */\n async show(result: TrafficIncidentDetails): Promise<void> {\n await this.waitUntilModuleReady();\n this.clearFeatureState();\n this.lastFocusIds = null;\n this.lastResult = result;\n this.lastFeatureIds = result.features\n .map((f) => f.properties.id)\n .filter((id): id is string => typeof id === 'string');\n this.sourcesWithLayers.incidents.show(result);\n for (const handler of this.shownFeaturesHandlers) {\n handler(result);\n }\n }\n\n /**\n * Remove all rendered incidents (source kept, data emptied).\n */\n async clear(): Promise<void> {\n await this.waitUntilModuleReady();\n this.clearFeatureState();\n this.lastResult = null;\n this.lastFeatureIds = [];\n this.lastFocusIds = null;\n this.sourcesWithLayers.incidents.clear();\n }\n\n /**\n * Show or hide all incident layers.\n */\n setVisible(visible: boolean): void {\n this.config = { ...this.config, visible };\n this.sourcesWithLayers.incidents.setLayersVisible(visible);\n this.emitConfigChange();\n }\n\n /**\n * Whether any incident layer is currently visible.\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.incidents.isAnyLayerVisible();\n }\n\n /**\n * Move incident layers before the given map-style layer, or to the top.\n */\n moveBeforeLayer(beforeLayerConfig: BeforeLayerConfig): void {\n this.config = { ...this.config, beforeLayerConfig };\n this.applyBeforeLayer(beforeLayerConfig);\n this.emitConfigChange();\n }\n\n /**\n * Returns the currently shown incidents — the exact data that was passed to {@link show}.\n *\n * Reads the module's client-side cache (`shownFeatures`), mirroring the other GeoJSON\n * modules (`PlacesModule`, `TrafficAreaAnalyticsModule`, `GeometriesModule`). It does\n * **not** hit-test the viewport: the proxy already scopes and de-duplicates the\n * `allEventFeatures` it dispatches to click handlers, so no caller needs viewport-level\n * de-duplication here.\n */\n getShown(): { incidents: TrafficIncidentDetails } {\n return { incidents: this.sourcesWithLayers.incidents.shownFeatures };\n }\n\n get events(): CombinedEvents<TrafficIncident, TrafficIncidentOverlayConfig, TrafficIncidentDetails> {\n return new CombinedEvents(\n new UserEvents<TrafficIncident>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.incidents,\n this.config?.events,\n ),\n new ModuleEvents(this.configChangeHandlers, this.shownFeaturesHandlers),\n );\n }\n\n /**\n * Mark a subset of currently-rendered incidents as \"focused\" via MapLibre\n * feature-state. `null` clears the state. Paint expressions in\n * `incidentDetailsLayers.ts` read `['feature-state', 'focused']` and pop the\n * focused subset (wider stripe + black outline). Unfocused features are\n * unchanged — the focus treatment adds emphasis, it does not dim the rest.\n *\n * No effect if called before `show()`.\n */\n setFocus(ids: readonly string[] | null): void {\n if (ids === null || ids.length === 0) {\n this.lastFocusIds = null;\n this.clearFeatureState();\n return;\n }\n if (this.lastFeatureIds.length === 0) return;\n this.lastFocusIds = ids;\n const focusSet = new Set<string>(ids);\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featId of this.lastFeatureIds) {\n this.mapLibreMap.setFeatureState({ source: sourceId, id: featId }, { focused: focusSet.has(featId) });\n }\n }\n\n private clearFeatureState(): void {\n if (this.lastFeatureIds.length === 0) return;\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featId of this.lastFeatureIds) {\n this.mapLibreMap.removeFeatureState({ source: sourceId, id: featId });\n }\n }\n}\n","import { isEmpty, isNil, omitBy } from 'lodash-es';\nimport type { FilterSpecification } from 'maplibre-gl';\nimport type { LayerSpecWithSource } from '../shared';\nimport {\n AbstractMapModule,\n CombinedEvents,\n filterLayersBySources,\n ModuleEvents,\n StyleSourceWithLayers,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n UserEvents,\n} from '../shared';\nimport { notInTheStyle } from '../shared/errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyFilter, buildMapLibreIncidentFilters } from './filters/trafficFilters';\nimport type { IncidentsConfig, TrafficIncidentsFilters } from './types/trafficIncidentsConfig';\nimport { TrafficIncidentsModuleFeature } from './types/trafficIncidentsFeature';\nimport { trafficIncidentMapping } from './util/trafficIncidentMapping';\n\n/**\n * IDs of sources and layers for traffic incidents module.\n */\ntype TrafficIncidentsSourcesWithLayers = {\n trafficIncidents: StyleSourceWithLayers;\n};\n\n/**\n * Traffic Incidents Module for displaying and configuring real-time traffic incidents on the map.\n *\n * This module controls the vector tile traffic incidents layers that show traffic\n * events like accidents, road closures, construction, and hazards.\n *\n * @remarks\n * **Features:**\n * - Toggle incidents visibility on/off\n * - Separate control for incident icons\n * - Filter by incident type (accident, construction, etc.)\n * - Filter by severity/delay magnitude\n * - Filter by road categories\n * - Icon and line/polygon visualization\n *\n * **Incident Types:**\n * - Accidents\n * - Road closures\n * - Construction/road works\n * - Weather conditions (fog, ice, rain, etc.)\n * - Lane closures\n * - Traffic jams\n * - Broken down vehicles\n *\n * @example\n * Basic usage:\n * ```typescript\n * import { TrafficIncidentsModule } from '@tomtom-international/maps-sdk-js/map';\n *\n * // Get module (auto-add to style if needed)\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map, {\n * visible: true\n * });\n *\n * // Toggle visibility\n * trafficIncidentsModule.setVisible(false);\n * trafficIncidentsModule.setVisible(true);\n *\n * // Control icons separately\n * trafficIncidentsModule.setIconsVisible(false);\n * ```\n *\n * @example\n * Filter by incident type:\n * ```typescript\n * // Show only accidents and road closures\n * trafficIncidentsModule.filter({\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed']\n * }\n * }]\n * });\n *\n * // Hide construction\n * trafficIncidentsModule.filter({\n * any: [{\n * incidentCategories: {\n * show: 'all_except',\n * values: ['roadworks']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter by severity:\n * ```typescript\n * // Show only major delays\n * incidents.filter({\n * any: [{\n * magnitudes: {\n * show: 'only',\n * values: ['major']\n * }\n * }]\n * });\n *\n * // Show incidents with at least 10 minutes delay\n * incidents.filter({\n * any: [{\n * delays: {\n * mustHaveDelay: true,\n * minDelayMinutes: 10\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter icons separately from incident areas:\n * ```typescript\n * // Show all incidents but only major icons\n * incidents.filter(\n * {\n * any: [{}] // Show all incidents\n * },\n * {\n * any: [{\n * magnitudes: { show: 'only', values: ['major'] }\n * }]\n * }\n * );\n * ```\n *\n * @see [Traffic Incidents Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-incidents)\n * @see [Traffic Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic)\n * @see {@link TrafficIncidentOverlayModule} — for rendering incidents fetched via the\n * `trafficIncidentDetails()` REST service as a developer-controlled overlay (use the\n * overlay when you need typed incident objects in app code; this vector-tile module\n * remains the recommended default for visual fidelity).\n *\n * @group Traffic Incidents\n */\nexport class TrafficIncidentsModule extends AbstractMapModule<TrafficIncidentsSourcesWithLayers, IncidentsConfig> {\n private originalFilters!: Record<string, FilterSpecification | undefined>;\n\n /**\n * Retrieves a TrafficIncidentsModule instance for the given map.\n *\n * @param map - The TomTomMap instance to attach this module to.\n * @param config - Optional configuration for initialization, visibility, and filters.\n *\n * @returns A promise that resolves to the initialized TrafficIncidentsModule.\n *\n * @remarks\n * **Configuration:**\n * - `visible`: Initial visibility state for all incidents\n * - `icons.visible`: Initial visibility for incident icons\n * - `ensureAddedToStyle`: Auto-add traffic incidents to style if missing\n * - `filters`: Incident type, severity, and delay filters\n * - `icons.filters`: Separate filters for icons\n *\n * @throws Error if traffic incidents source is not in style and `ensureAddedToStyle` is false\n *\n * @example\n * Default initialization:\n * ```typescript\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map);\n * ```\n *\n * @example\n * With configuration:\n * ```typescript\n * const trafficIncidentsModule = await TrafficIncidentsModule.get(map, {\n * visible: true,\n * icons: { visible: true },\n * filters: {\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed', 'jam']\n * }\n * }]\n * }\n * });\n * ```\n */\n static async get(map: TomTomMap, config?: IncidentsConfig): Promise<TrafficIncidentsModule> {\n await waitUntilMapIsReady(map);\n await ensureAddedToStyle(map, TRAFFIC_INCIDENTS_SOURCE_ID, 'trafficIncidents');\n return new TrafficIncidentsModule(map, config);\n }\n\n private constructor(map: TomTomMap, config?: IncidentsConfig) {\n super('style', map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const incidentsSource = this.mapLibreMap.getSource(TRAFFIC_INCIDENTS_SOURCE_ID);\n if (!incidentsSource) {\n throw notInTheStyle(`init ${TrafficIncidentsModule.name} with source ID ${TRAFFIC_INCIDENTS_SOURCE_ID}`);\n }\n this.originalFilters = {};\n for (const layer of this.getLayers()) {\n this.originalFilters[layer.id] = layer.filter;\n }\n return { trafficIncidents: new StyleSourceWithLayers(this.mapLibreMap, incidentsSource) };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: IncidentsConfig | undefined) {\n // We do not update config in setVisible since it could override icons visibility setting:\n this._setVisible(config?.visible ?? false, { updateConfig: false });\n if (!isNil(config?.icons?.visible)) {\n this._setIconsVisible(config.icons.visible, false);\n }\n this._filter(config?.filters, config?.icons?.filters, false);\n return config;\n }\n\n /**\n * Applies filters to traffic incidents display.\n *\n * @param incidentFilters - Filter for incident areas/lines. Pass `undefined` to reset.\n * @param iconFilters - Optional separate filter for incident icons. Pass `undefined` to reset.\n *\n * @remarks\n * **Filter Options:**\n * - `incidentCategories`: Filter by incident type\n * - `magnitudes`: Filter by delay severity (minor/moderate/major/unknown)\n * - `delays`: Filter by delay duration\n * - `roadCategories`: Filter by road importance\n * - `roadSubCategories`: Filter by specific road types\n *\n * **Available Incident Categories:**\n * - `accident`, `road-closed`, `lane-closed`\n * - `roadworks` (construction)\n * - `jam` (traffic jam)\n * - `fog`, `rain`, `frost`, `wind`, `flooding`\n * - `danger`, `animals-on-road`, `narrow-lanes`\n * - `broken-down-vehicle`\n * - `other`\n *\n * **Delay Magnitudes:**\n * - `minor`: Small delays\n * - `moderate`: Moderate delays\n * - `major`: Significant delays\n * - `unknown`: Unknown or no delay info\n *\n * @example\n * Filter by type:\n * ```typescript\n * incidents.filter({\n * any: [{\n * incidentCategories: {\n * show: 'only',\n * values: ['accident', 'road-closed']\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Filter by severity and delay:\n * ```typescript\n * incidents.filter({\n * any: [{\n * magnitudes: { show: 'only', values: ['major', 'moderate'] },\n * delays: {\n * mustHaveDelay: true,\n * minDelayMinutes: 5\n * }\n * }]\n * });\n * ```\n *\n * @example\n * Different filters for icons and areas:\n * ```typescript\n * // Show all incidents on roads\n * const incidentFilter = {\n * any: [{\n * roadCategories: { show: 'only', values: ['motorway', 'trunk'] }\n * }]\n * };\n *\n * // But only show icons for major incidents\n * const iconFilter = {\n * any: [{\n * magnitudes: { show: 'only', values: ['major'] }\n * }]\n * };\n *\n * incidents.filter(incidentFilter, iconFilter);\n * ```\n */\n filter(incidentFilters?: TrafficIncidentsFilters, iconFilters?: TrafficIncidentsFilters) {\n this._filter(incidentFilters, iconFilters);\n }\n\n private _filter(\n incidentFilters: TrafficIncidentsFilters | undefined,\n iconFilters: TrafficIncidentsFilters | undefined,\n updateConfig = true,\n ) {\n if (this.tomtomMap.mapReady) {\n if (incidentFilters?.any?.length) {\n const incidentFilterExpression = buildMapLibreIncidentFilters(incidentFilters);\n if (incidentFilterExpression) {\n const layers = iconFilters ? this.getNonSymbolLayers() : this.getLayers();\n applyFilter(incidentFilterExpression, layers, this.mapLibreMap, this.originalFilters);\n }\n } else if (this.config?.filters?.any?.length) {\n applyFilter(undefined, this.getLayers(), this.mapLibreMap, this.originalFilters);\n }\n if (iconFilters?.any?.length) {\n const iconFilterExpression = buildMapLibreIncidentFilters(iconFilters);\n if (iconFilterExpression) {\n applyFilter(iconFilterExpression, this.getSymbolLayers(), this.mapLibreMap, this.originalFilters);\n }\n }\n }\n\n // else: default incidents visibility has been set already if necessary\n if (updateConfig) {\n this.config = omitBy(\n {\n ...this.config,\n filters: incidentFilters,\n icons: { ...this.config?.icons, filters: iconFilters },\n },\n isNil,\n );\n this.emitConfigChange();\n }\n }\n\n private getLayers(): LayerSpecWithSource[] {\n return filterLayersBySources(this.tomtomMap.mapLibreMap, [TRAFFIC_INCIDENTS_SOURCE_ID]);\n }\n\n private getSymbolLayers(): LayerSpecWithSource[] {\n return this.getLayers().filter((layer) => layer.type === 'symbol');\n }\n\n private getNonSymbolLayers(): LayerSpecWithSource[] {\n return this.getLayers().filter((layer) => layer.type != 'symbol');\n }\n\n /**\n * Sets the visibility of incident icon layers.\n *\n * @param visible - `true` to show icons, `false` to hide them.\n *\n * @remarks\n * This controls only the icon/symbol layers, not the incident area polygons or lines.\n *\n * @example\n * ```typescript\n * // Hide icons but keep incident areas visible\n * incidents.setIconsVisible(false);\n *\n * // Show icons\n * incidents.setIconsVisible(true);\n * ```\n */\n setIconsVisible(visible: boolean): void {\n this._setIconsVisible(visible);\n }\n\n private _setIconsVisible(visible: boolean, updateConfig = true): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficIncidents.setLayersVisible(\n visible,\n (layerSpec) => layerSpec.type === 'symbol',\n );\n }\n if (updateConfig) {\n this.config = { ...this.config, icons: { ...this.config?.icons, visible } };\n this.emitConfigChange();\n }\n }\n\n /**\n * Sets the visibility of all traffic incident layers.\n *\n * @param visible - `true` to show incidents, `false` to hide them.\n *\n * @remarks\n * This controls all incident layers including icons, lines, and polygons.\n *\n * @example\n * ```typescript\n * incidents.setVisible(false); // Hide all incidents\n * incidents.setVisible(true); // Show all incidents\n * ```\n */\n setVisible(visible: boolean): void {\n this._setVisible(visible);\n }\n\n private _setVisible(visible: boolean, options?: { updateConfig: boolean }): void {\n const updateConfig = options?.updateConfig ?? true;\n if (updateConfig) {\n // setting all traffic visible also nullifies the icons visible setting\n delete this.config?.icons?.visible;\n // we remove empty values from config to avoid confusion (in case icons part is just empty after deleting visible)\n this.config = { ...omitBy({ ...this.config }, isEmpty), visible };\n this.emitConfigChange();\n }\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficIncidents.setLayersVisible(visible);\n }\n }\n\n /**\n * Checks if any traffic incident layers are currently visible.\n *\n * @returns `true` if any incident layer is visible, `false` if all are hidden.\n *\n * @example\n * ```typescript\n * if (incidents.isVisible()) {\n * console.log('Incidents are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.sourcesWithLayers.trafficIncidents.isAnyLayerVisible();\n }\n\n /**\n * Checks if any incident icon layers are currently visible.\n *\n * @returns `true` if any icon layer is visible, `false` if all icons are hidden.\n *\n * @example\n * ```typescript\n * if (incidents.anyIconLayersVisible()) {\n * console.log('Incident icons are shown');\n * }\n * ```\n */\n anyIconLayersVisible(): boolean {\n return this.sourcesWithLayers.trafficIncidents?.isAnyLayerVisible((layerSpec) => layerSpec.type === 'symbol');\n }\n\n /**\n * Returns features currently visible in the viewport.\n *\n * @returns An object containing visible traffic incident features.\n *\n * @remarks\n * Returns all incident features currently rendered in the visible map area,\n * including both incident areas/lines and icon markers.\n *\n * **Feature Properties:**\n * Properties depend on the traffic incidents vector tile schema and may include:\n * - Incident type/category\n * - Severity/magnitude\n * - Delay information\n * - Road affected\n * - Description\n *\n * @example\n * ```typescript\n * const shown = incidents.getShown();\n * console.log(`Visible incidents: ${shown.trafficIncidents.length}`);\n * shown.trafficIncidents.forEach(incident => {\n * console.log('Incident:', incident.properties);\n * });\n * ```\n *\n * @example\n * Filter and analyze visible incidents:\n * ```typescript\n * const shown = incidents.getShown();\n * const accidents = shown.trafficIncidents.filter(\n * incident => incident.properties.category === 'accident'\n * );\n * console.log(`Accidents in view: ${accidents.length}`);\n * ```\n */\n getShown(): { trafficIncidents: TrafficIncidentsModuleFeature[] } {\n return {\n trafficIncidents: this.mapLibreMap\n .queryRenderedFeatures({\n layers: this.sourcesWithLayers.trafficIncidents.sourceAndLayerIDs.layerIDs,\n validate: false,\n })\n .map(trafficIncidentMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficIncidentsModuleFeature, IncidentsConfig, never> {\n return new CombinedEvents(\n new UserEvents<TrafficIncidentsModuleFeature>(\n this.tomtomMap._eventsProxy,\n this.sourcesWithLayers.trafficIncidents,\n this.config?.events,\n trafficIncidentMapping,\n ),\n new ModuleEvents(this.configChangeHandlers, []),\n );\n }\n}\n","import type { FilterShowMode, ValuesFilter } from '../../shared';\n\n/**\n * Available road hierarchy category identifiers.\n *\n * @remarks\n * These categories represent different levels in the road network hierarchy,\n * from major highways to local streets.\n *\n * @group Traffic\n */\nexport const roadCategories = [\n 'motorway',\n 'motorway_link',\n 'trunk',\n 'trunk_link',\n 'primary',\n 'primary_link',\n 'secondary',\n 'secondary_link',\n 'tertiary',\n 'tertiary_link',\n 'street',\n 'service',\n 'track',\n] as const;\n\n/**\n * Road hierarchy category type.\n *\n * @remarks\n * Classifies roads by their importance and capacity in the transportation network.\n * Used for filtering traffic data display based on road significance.\n *\n * Road hierarchy (from highest to lowest):\n * - `motorway` / `motorway_link` - High-capacity highways with restricted access\n * - `trunk` / `trunk_link` - Major inter-city roads\n * - `primary` / `primary_link` - Primary through routes\n * - `secondary` / `secondary_link` - Secondary through routes\n * - `tertiary` / `tertiary_link` - Connecting roads\n * - `street` - Local streets (see {@link StreetRoadSubCategory})\n * - `service` - Service roads (see {@link ServiceRoadSubCategory})\n * - `track` - Unpaved or agricultural tracks\n *\n * @group Traffic\n */\nexport type RoadCategory = (typeof roadCategories)[number];\n\n/**\n * Available street road sub-category identifiers.\n *\n * @remarks\n * Provides finer granularity for classifying local streets.\n *\n * @group Traffic\n */\nexport const streetRoadSubCategories = ['unclassified', 'residential', 'living_street'] as const;\n\n/**\n * Street road sub-category type.\n *\n * @remarks\n * Further classifies `street` roads into specific sub-types.\n *\n * Sub-categories:\n * - `unclassified` - Unclassified local roads\n * - `residential` - Roads in residential areas\n * - `living_street` - Pedestrian-priority shared streets\n *\n * @group Traffic\n */\nexport type StreetRoadSubCategory = (typeof streetRoadSubCategories)[number];\n\n/**\n * Available service road sub-category identifiers.\n *\n * @remarks\n * Provides finer granularity for classifying service roads.\n *\n * @group Traffic\n */\nexport const serviceRoadSubCategories = ['parking', 'driveway', 'alley'] as const;\n\n/**\n * Service road sub-category type.\n *\n * @remarks\n * Further classifies `service` roads into specific sub-types.\n *\n * Sub-categories:\n * - `parking` - Roads within parking areas\n * - `driveway` - Private access roads\n * - `alley` - Narrow back-access lanes\n *\n * @group Traffic\n */\nexport type ServiceRoadSubCategory = (typeof serviceRoadSubCategories)[number];\n\n/**\n * Road sub-category type.\n *\n * @remarks\n * Combines street and service sub-categories. Only `street` and `service` road categories have sub-categories.\n *\n * @group Traffic\n */\nexport type RoadSubCategory = StreetRoadSubCategory | ServiceRoadSubCategory;\n\n/**\n * Common filter configuration shared between traffic incidents and flow visualization.\n *\n * @remarks\n * Provides road category filtering capabilities used by both incident and flow modules.\n *\n * @group Traffic\n */\nexport type TrafficCommonFilter = {\n /**\n * Filters traffic data by road hierarchy categories.\n *\n * @remarks\n * Controls which road types display traffic information.\n * Use the `mode` field to specify whether to show or hide the selected categories.\n *\n * @example\n * ```ts\n * // Show only motorways and trunk roads\n * roadCategories: { show: 'only', values: ['motorway', 'trunk'] }\n * ```\n */\n roadCategories?: ValuesFilter<RoadCategory>;\n\n /**\n * Filters traffic data by road sub-categories.\n *\n * @remarks\n * Provides finer-grained control for street and service roads.\n * Applies to sub-categories of {@link StreetRoadSubCategory} and {@link ServiceRoadSubCategory}.\n *\n * @example\n * ```ts\n * // Hide minor local streets\n * roadSubCategories: { show: 'all_except', values: ['living_street'] }\n * ```\n */\n roadSubCategories?: ValuesFilter<RoadSubCategory>;\n};\n\nexport type { FilterShowMode };\n","import type { BBox } from '@tomtom-org/maps-sdk/core';\nimport type { Position } from 'geojson';\nimport type { Map } from 'maplibre-gl';\nimport type { TomTomMap } from '../TomTomMap';\nimport type {\n CalculateFittingBBoxOptions,\n CalculateMapBoundsOptions,\n CalculateMapCenterOptions,\n} from './types/paddedBounds';\n\n// (Originally mostly AI-generated)\n\ntype BoundsPX = {\n left: number;\n top: number;\n right: number;\n bottom: number;\n};\n\nconst getRelativeElementBounds = (elementRect: DOMRect, containerRect: DOMRect): BoundsPX => ({\n left: elementRect.left - containerRect.left,\n top: elementRect.top - containerRect.top,\n right: elementRect.right - containerRect.left,\n bottom: elementRect.bottom - containerRect.top,\n});\n\nconst isElementOutsideContainer = (bounds: BoundsPX, containerWidth: number, containerHeight: number): boolean =>\n bounds.right <= 0 || bounds.left >= containerWidth || bounds.bottom <= 0 || bounds.top >= containerHeight;\n\n// Adjusts the current visible bounds to account for a horizontal bar element (that spans the whole width)\nconst adjustForHorizontalBar = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerHeight: number,\n padding: number,\n): void => {\n const distanceFromTop = bounds.top;\n const distanceFromBottom = containerHeight - bounds.bottom;\n if (distanceFromTop <= distanceFromBottom) {\n currentVisible.top = Math.max(currentVisible.top, bounds.bottom + padding);\n } else {\n currentVisible.bottom = Math.min(currentVisible.bottom, bounds.top - padding);\n }\n};\n\n// Adjusts the current visible bounds to account for a vertical bar element (that spans the whole height)\nconst adjustForVerticalBar = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerWidth: number,\n padding: number,\n): void => {\n const distanceFromLeft = bounds.left;\n const distanceFromRight = containerWidth - bounds.right;\n if (distanceFromLeft <= distanceFromRight) {\n currentVisible.left = Math.max(currentVisible.left, bounds.right + padding);\n } else {\n currentVisible.right = Math.min(currentVisible.right, bounds.left - padding);\n }\n};\n\n// Adjusts the current visible bounds to account for a floating element (that does not span full width or height)\nconst adjustForFloatingElement = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n containerWidth: number,\n containerHeight: number,\n padding: number,\n): void => {\n const distanceFromLeft = bounds.left;\n const distanceFromRight = containerWidth - bounds.right;\n const distanceFromTop = bounds.top;\n const distanceFromBottom = containerHeight - bounds.bottom;\n const minDistance = Math.min(distanceFromLeft, distanceFromRight, distanceFromTop, distanceFromBottom);\n\n if (minDistance === distanceFromLeft) {\n currentVisible.left = Math.max(currentVisible.left, bounds.right + padding);\n } else if (minDistance === distanceFromRight) {\n currentVisible.right = Math.min(currentVisible.right, bounds.left - padding);\n } else if (minDistance === distanceFromTop) {\n currentVisible.top = Math.max(currentVisible.top, bounds.bottom + padding);\n } else {\n currentVisible.bottom = Math.min(currentVisible.bottom, bounds.top - padding);\n }\n};\n\n/**\n * Clamps the element bounds to the container boundaries.\n * This handles cases where UI elements extend beyond the visible container\n * (e.g., a sidebar that goes past the screen edge).\n */\nconst clampBoundsToContainer = (bounds: BoundsPX, containerWidth: number, containerHeight: number): BoundsPX => ({\n left: Math.max(0, bounds.left),\n top: Math.max(0, bounds.top),\n right: Math.min(containerWidth, bounds.right),\n bottom: Math.min(containerHeight, bounds.bottom),\n});\n\nconst adjustVisibleBoundsForElement = (\n bounds: BoundsPX,\n currentVisible: BoundsPX, // will be mutated\n mapWidthPX: number,\n mapHeightPX: number,\n padding: number,\n): void => {\n // Clamp bounds to container to handle elements that extend beyond the screen\n const clampedBounds = clampBoundsToContainer(bounds, mapWidthPX, mapHeightPX);\n\n // Determine if the element touches or extends beyond the full width or height of the container\n // An element \"spans full width\" if it touches both left and right edges (or extends beyond)\n const spansFullWidth = bounds.left <= 0 && bounds.right >= mapWidthPX;\n const spansFullHeight = bounds.top <= 0 && bounds.bottom >= mapHeightPX;\n\n // For elements spanning full width (horizontal bars), only consider vertical adjustment\n if (spansFullWidth && !spansFullHeight) {\n adjustForHorizontalBar(clampedBounds, currentVisible, mapHeightPX, padding);\n return;\n }\n\n // For elements spanning full height (vertical bars), only consider horizontal adjustment\n if (spansFullHeight && !spansFullWidth) {\n adjustForVerticalBar(clampedBounds, currentVisible, mapWidthPX, padding);\n return;\n }\n\n // Check if element touches/extends beyond an edge (makes it act like a bar on that side)\n const touchesLeft = bounds.left <= 0;\n const touchesRight = bounds.right >= mapWidthPX;\n const touchesTop = bounds.top <= 0;\n const touchesBottom = bounds.bottom >= mapHeightPX;\n\n // If element touches opposite edges in one dimension but not the other,\n // treat it as a bar in that dimension\n if ((touchesLeft || touchesRight) && !touchesTop && !touchesBottom) {\n // Element extends horizontally but not vertically - treat as horizontal bar\n adjustForHorizontalBar(clampedBounds, currentVisible, mapHeightPX, padding);\n return;\n }\n if ((touchesTop || touchesBottom) && !touchesLeft && !touchesRight) {\n // Element extends vertically but not horizontally - treat as vertical bar\n adjustForVerticalBar(clampedBounds, currentVisible, mapWidthPX, padding);\n return;\n }\n\n // For corner elements (touching edges in both dimensions) or floating elements\n // determine the best adjustment based on which edge the element is closest to\n adjustForFloatingElement(clampedBounds, currentVisible, mapWidthPX, mapHeightPX, padding);\n};\n\n/**\n * Resolves a surrounding element reference to an HTMLElement.\n * If the reference is a string, it's treated as a DOM selector.\n * @param ref - Either an HTMLElement or a DOM selector string\n * @returns The resolved HTMLElement, or null if not found\n */\nconst toElement = (ref: HTMLElement | string): HTMLElement | null => {\n if (typeof ref === 'string') {\n return document.querySelector(ref);\n }\n return ref;\n};\n\n/**\n * Resolves a map reference to a MapLibre Map instance.\n * If the reference is a TomTomMap, it extracts the underlying mapLibreMap.\n * @param map - Either a TomTomMap or a MapLibre Map instance\n * @returns The MapLibre Map instance\n */\nconst getMapLibreMap = (map: TomTomMap | Map): Map => {\n if ('mapLibreMap' in map) {\n return map.mapLibreMap;\n }\n return map;\n};\n\ntype VisibleAreaResult = {\n visibleAreaBounds: BoundsPX;\n mapWidthPX: number;\n mapHeightPX: number;\n} | null;\n\n/**\n * Calculates the visible area bounds in pixel coordinates after accounting for\n * surrounding UI elements and padding.\n */\nconst calculateVisibleAreaBounds = (\n map: TomTomMap | Map,\n surroundingElements: (HTMLElement | string)[],\n paddingPX: number,\n): VisibleAreaResult => {\n const containerRect = getMapLibreMap(map).getContainer().getBoundingClientRect();\n const { width: mapWidthPX, height: mapHeightPX } = containerRect;\n\n // Initialize visible bounds with padding applied to all edges (will be mutated)\n const visibleAreaBounds: BoundsPX = {\n left: paddingPX,\n top: paddingPX,\n right: mapWidthPX - paddingPX,\n bottom: mapHeightPX - paddingPX,\n };\n\n for (const elementRef of surroundingElements) {\n const element = toElement(elementRef);\n if (!element) {\n continue;\n }\n const elementRect = element.getBoundingClientRect();\n const elementBounds = getRelativeElementBounds(elementRect, containerRect);\n\n if (isElementOutsideContainer(elementBounds, mapWidthPX, mapHeightPX)) {\n continue;\n }\n\n adjustVisibleBoundsForElement(elementBounds, visibleAreaBounds, mapWidthPX, mapHeightPX, paddingPX);\n }\n\n if (visibleAreaBounds.left >= visibleAreaBounds.right || visibleAreaBounds.top >= visibleAreaBounds.bottom) {\n return null;\n }\n\n return { visibleAreaBounds, mapWidthPX, mapHeightPX };\n};\n\n/**\n * Calculates the bounding box in lng-lat coordinates of the visible map area\n * that does not overlap with the given UI HTML elements.\n *\n * @remarks\n * This is useful for determining the area of the map that is not obscured by UI components.\n *\n * @param options - The options for calculating map bounds\n * @returns The padded bounding box as [west, south, east, north], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculatePaddedBBox = (options: CalculateMapBoundsOptions): BBox | null => {\n const { map, surroundingElements, paddingPX = 0 } = options;\n\n const result = calculateVisibleAreaBounds(map, surroundingElements, paddingPX);\n if (!result) {\n return null;\n }\n\n const { visibleAreaBounds } = result;\n const mapLibreMap = getMapLibreMap(map);\n const sw = mapLibreMap.unproject([visibleAreaBounds.left, visibleAreaBounds.bottom]);\n const ne = mapLibreMap.unproject([visibleAreaBounds.right, visibleAreaBounds.top]);\n\n return [sw.lng, sw.lat, ne.lng, ne.lat];\n};\n\n/**\n * Calculates the center point in lng-lat coordinates of the visible map area\n * that does not overlap with the given UI HTML elements.\n *\n * @remarks\n * * This is useful to offset the map center in a way that it looks harmonious with surrounding UI components.\n * * It's equivalent to the center of calculatePaddedBBox.\n *\n * @param options - The options for calculating map center\n * @returns The center as [lng, lat], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculatePaddedCenter = (options: CalculateMapCenterOptions): Position | null => {\n const { map, surroundingElements } = options;\n const bbox = calculatePaddedBBox({ map, surroundingElements });\n if (!bbox) {\n return null;\n }\n const [west, south, east, north] = bbox;\n return [(west + east) / 2, (south + north) / 2];\n};\n\n/**\n * Calculates an expanded bounding box that, when the map is zoomed to it, ensures that the given to-be-contained bounding box\n * is visible within the area not obscured by surrounding UI elements, including optional padding.\n * * In other words, calculates a bounding box that ensure the to-be-contained bbox fits within the visible area of the map.\n *\n * @remarks\n * This is useful when you have a specific geographic area (containedBBox) that you want to be fully visible\n * in the unobscured portion of the map (the area not covered by UI components).\n * The function returns a larger bounding box that accounts for the space taken by UI elements.\n *\n * @param options - The options for calculating expanded bounds\n * @returns The expanded bounding box as [west, south, east, north], or null if the visible area is too small\n *\n * @group Utils\n */\nexport const calculateFittingBBox = (options: CalculateFittingBBoxOptions): BBox | null => {\n const { map, toBeContainedBBox, surroundingElements, paddingPX = 0 } = options;\n\n const result = calculateVisibleAreaBounds(map, surroundingElements, paddingPX);\n if (!result) {\n return null;\n }\n\n const { visibleAreaBounds, mapWidthPX, mapHeightPX } = result;\n\n // Calculate the ratios of how much the visible area is offset from the full container\n // These represent what fraction of the full map the visible area occupies\n const leftRatio = visibleAreaBounds.left / mapWidthPX;\n const rightRatio = visibleAreaBounds.right / mapWidthPX;\n const topRatio = visibleAreaBounds.top / mapHeightPX;\n const bottomRatio = visibleAreaBounds.bottom / mapHeightPX;\n\n // Calculate the width and height ratios of the visible area\n const widthRatio = rightRatio - leftRatio;\n const heightRatio = bottomRatio - topRatio;\n\n // Target bbox coordinates\n const [west, south, east, north] = toBeContainedBBox;\n const targetWidth = east - west;\n const targetHeight = north - south;\n\n // Calculate the full map extent needed so that when cropped by the visible area,\n // the target bbox fits exactly\n const fullWidth = targetWidth / widthRatio;\n const fullHeight = targetHeight / heightRatio;\n\n // Calculate the fitting expanded bounds\n // The visible area starts at leftRatio of the full width, so we need to extend west\n const expandedWest = west - leftRatio * fullWidth;\n const expandedEast = expandedWest + fullWidth;\n\n // For latitude, remember that in pixel coordinates, top is smaller Y but higher latitude\n // topRatio represents the fraction from top, which corresponds to north\n const expandedNorth = north + topRatio * fullHeight;\n const expandedSouth = expandedNorth - fullHeight;\n\n return [expandedWest, expandedSouth, expandedEast, expandedNorth];\n};\n"],"mappings":"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"}
|