@tomtom-org/maps-sdk 0.63.0 → 1.0.0-rc.0

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.
@@ -1 +0,0 @@
1
- {"version":3,"file":"map.es.js","names":["composersByMap","registerColorSpace","pinSvgRaw","VISIBILITY_CONTRIBUTOR","buildCatalogue","buildTitle","buildCatalogue","buildBaseLineSpec","generated","instructionArrowSvgRaw","summaryMapBubbleSvgRaw","countryCrossingPlaqueSvgRaw","speedLimitPlaqueSvgRaw","tunnelSignSvgRaw","trafficSvgRaw","startSvgRaw","finishSvgRaw","bearingBetween","mapColor","Map","DEFAULT_BEFORE_LAYER","clamp","clone","get","groupBy","has","isEmpty","isEqual","isNil","isObject","isUndefined","keyBy","mapValues","omit","omitBy","pick","remove","setWith","sortBy","toPath","unset","DEFAULT_COMMON_BASE_URL","MILE_IN_METERS","TomTomConfig","areaAnalyticsMetricKeys","bboxCenter","bboxFromCoordsArray","bboxFromGeoJSON","budgetUnits","formatDistance","formatDuration","fullTrafficIncidentCategories","generateId","generateTomTomHeaders","getPosition","iconToTrafficIncidentCategory","indexedMagnitudes","isProxyCredentialsMode","mergeFromGlobal","poiCategories","poiCategoriesToID","trafficIncidentToIconCategory","clampRgb","differenceHueNaive","formatHex","formatHsl","interpolate","modeHsl","modeLrgb","modeRgb","parse","round","useMode","wcagLuminance","area","bearing","booleanPointInPolygon","difference","featureCollection","hexGrid","mask","squareGrid","union","Map$1","setWorkerCount","CombinedEvents","constructor","userEventsInstance","moduleEventsInstance","this","where","scope","config","on","type","handler","off","andPredicates","outer","inner","args","combineEventScopes","layerFilter","featureMatches","scopedLayerSpecs","sourceWithLayers","_layerSpecs","filter","reservedScopeNames","assertNoReservedScopeNames","names","context","offender","find","name","includes","Error","join","filterShowModes","clampFactor","factor","range","fallback","Number","isFinite","min","max","copyOf","nested","configWith","path","value","everyOf","options","valuesFilterKnobs","noun","valuesNote","show","kind","description","values","withIds","definitions","Object","keys","map","id","defaultOf","default","FACTOR_RANGE","step","bounds","WIDE_FACTOR_RANGE","OPACITY_RANGE","LABEL_SIZE_RANGE","LABEL_HALO_WIDTH_RANGE","ZOOM_RANGE","isExpressionFilter","Array","isArray","length","filterItem","slice","propertyAccessor","property","convertHas","convertIn","convertLegacyFilter","operator","subFilters","convertNullComparison","convertOrdering","subFilter","toExpressionFilter","getMergedFilter","combinator","filters","isCompleteValuesFilter","buildMappedValuesFilter","propertyName","showMode","membership","ofMap","byMap","create","set","AbstractLayerComposer","tomtomMap","byLayer","Map","mapLibreMap","addStyleChangeHandler","onStyleAboutToChange","clear","priority","NEGATIVE_INFINITY","setOrDelete","entries","contributor","delete","composersByMap$1","WeakMap","LayerFilterComposer","super","setTransform","layerId","transform","contribute","transforms","setClause","clause","clauses","register","getLayer","contributions","styleFilter","getFilter","apply","base","reduce","terms","composeFilter","setFilter","validate","ModuleEvents","configChangeHandlers","shownFeaturesHandlers","configHandler","push","index","indexOf","splice","shownHandler","standardStyleIDs","styleParts","stylePartURLParams","trafficIncidents","trafficFlow","terrain","lightStylePartValues","darkStylePartValues","standardStylePartValues","standardLight","standardDark","drivingLight","drivingDark","monoLight","monoDark","satellite","mapSuffixes","setApiKeyParameter","searchParameters","apiKey","buildStandardStyleUrl","standardStyle","baseUrl","standardStyleID","styleURL","URL","searchParams","part","include","append","toString","buildStyleInput","mapParams","style","commonBaseURL","url","givenUrl","console","warn","withApiKey","json","isLegacyFunction","stops","scaleNumericValue","Math","roundScaled","scaleLegacyStops","scaleRampOutputs","JSON","stringify","mentionsZoom","input","output","scaled","branchOn","condition","scaledWhen","branched","shiftZoomOffset","shift","patched","walk","node","toRgb","toHslSpace","sourceProperty","outputProperty","cachedImage","toHundredths","STYLE_COLOR_SYNTAX","parseColor","cssColor","trim","toLowerCase","color","test","parseCssColor","r","g","b","alpha","toHsl","isColorLiteral","formatStyleHsl","hsl","roundStyleHsl","h","s","l","mode","opaqueColor","clampUnit","hslShiftBetween","fromColor","toColor","from","to","shiftHsl","relativeLuminance","mixColor","towardColor","toward","darkerShadeOf","hueDistance","hue","otherHue","abs","paintedBy","fill","line","paintLayers","layerType","selector","layers","layerTypes","properties","fillLayers","lineLayers","extrusionLayers","roadLine","idIncludes","idExcludes","metadataGroups","MIXED_CLASS_ROAD_LINES","MAJOR_ROAD_LINES","MINOR_ROAD_LINES","MINOR_ROAD_OUTLINES","RAIL_AND_PATH_LINES","CAPITAL_LABELS","TEXT_LAYERS","mapColorDefinitions","land","anchor","targets","water","vegetation","park","artificial","lastStop","target","recolorsTints","roadMajor","roadClass","road","roadOutline","label","labelOutline","baseMapColorNames","mapColorNames","isMapColorName","some","known","mapColorKnobId","baseMapColorKnobs","definitionOf","fromEntries","allButLand","landDerivedMapColors","mapColorDerivationInputs","themeLuminance","luminance","withLightness","lighten","darken","desaturate","lightenBy","amount","darkenBy","sign","positive","withDerivedMapColors","given","derivedNames","styleLand","toDerive","derived","roadSign","labelSign","derivedRoad","effectiveRoad","deriveMapColorsFromLand","MAJOR_ROAD_CLASSES","Set","roadClassOfKeys","key","replace","TOP_LEVEL_CONTEXT","keyed","mapColorLiterals","visit","isOpaqueColorLiteral","onRoadClass","result","fallbackClass","at","item","collectColorLiterals","found","literal","tintSaturationScale","shadeShift","anchorColor","newColor","shade","grey","targetAlpha","recolorValue","originalValue","bids","lastFirst","reverse","owning","owns","tintBid","bid","tint","anchorScale","newScale","recolorTint","FLOW_TAGS","trafficFlowMapping","feature","geometry","roadCategory","road_category","leftHandTraffic","Boolean","left_hand_traffic","roadClosure","road_closure","relativeSpeed","relative_speed","road_subcategory","roadSubcategory","absolute_speed","absoluteSpeed","part_of_two_way_road","partOfTwoWayRoad","openlr","display_class","displayClass","INCIDENT_TAGS","trafficIncidentMapping","description_0","category","icon_category_0","magnitudeOfDelay","magnitude_of_delay","delay","delayInSeconds","start_time","startTime","Date","end_time","endTime","probability_of_occurrence","probabilityOfOccurrence","number_of_reports","numberOfReports","last_report_time","lastReportTime","average_speed_kmph","averageSpeedKmph","time_validity","timeValidity","point_type","pointType","toPinSpriteImageID","imageID","isDOMImageSupported","document","DOMParser","btoa","urlToImg","img","createElement","src","svgToImg","svgDomElement","XMLSerializer","serializeToString","ModuleSourcesAndLayers","sourceIds","layerIds","carriedSourceIds","carriedLayerIds","addSource","sourceId","spec","add","getSource","addLayer","current","existingBeforeLayerID","beforeID","carried","serialize","changeLayerProps","layout","visibility","moveLayerBefore","isCarried","carryInto","previous","next","sources","nextLayerIds","layer","carriedBelow","run","source","flatMap","dropUnadopted","removeLayer","removeSource","parseSvg","svgString","parseFromString","documentElement","pinSvg","element","fillColor","querySelector","setAttribute","outlineColor","outlineOpacity","applySvgStyleOptions","waitUntilMapIsReady","async","mapReady","once","VECTOR_TILE_VIEW_PARAMETERS","vectorTileViewParameter","pathname","isVectorTileUrl","tileUrl","reloadVectorTiles","getStyle","tiles","vectorTileSourceIds","tileManager","tileManagers","tileId","getIds","tile","getTileByID","etag","refreshTiles","gateOnDemosProxySession","ensureSession","globalThis","__DEMOS_PROXY_ENSURE_SESSION__","then","transformRequest","globalConfig","baseURL","isProxyMode","isCredentialsProxy","resourceType","rewrittenUrl","parsed","hostname","endsWith","search","hash","rewriteForProxy","isProxyUrl","startsWith","useCredentials","isTomTomUrl","host","credentials","parsedUrl","injectTrafficTags","view","viewParameter","tileView","toTileView","applyGeopoliticalView","geopoliticalView","headers","newLayerProps","previousLayerProps","maxzoom","minzoom","setLayerZoomRange","getMinZoom","getMaxZoom","setLayoutProperty","paint","setPaintProperty","updateLayersAndSource","newLayersSpecs","oldLayersSpecs","newLayersMap","oldLayersMap","layersToAdd","layersToRemove","newLayersToUpdate","oldLayersToUpdate","forEach","layerSpecs","i","addedLayerSpecs","addLayers","_updateSourceAndLayerIDs","newLayerSpecs","oldLayerSpecs","recordedSpecs","movedLayerIDs","restack","layerSpec","recordedSpec","newLayerSpec","followers","pinned","restackMovedLayers","layerProps","changeLayersProps","beforeLayerID","layerID","moveLayer","applyLayersVisibility","visible","_removed","restackExisting","moduleSourcesAndLayers","for","layerIdsAlreadyOnMap","mapIdDependency","givenLayerIDs","layerIDsToPlace","idsWeCanProcess","error","updateStyleWithPart","stylePart","cannotAddStylePartToCustomStyle","addOrUpdateImage","imageId","imageToLoad","addOrUpdateToMap","imgElement","hasImage","addImage","existing","getImage","data","width","height","pixelRatio","updateImage","removeImage","ensureImageLoaded","complete","naturalWidth","onload","onerror","loadImage","syncImages","added","wanted","image","unchanged","darkStandardStyleIDs","styleID","getStyleLightDarkTheme","styleInput","declaredTheme","getDeclaredLightDarkTheme","standardStyleIdOf","getStandardStyleTheme","isCustomStyle","lightDarkTheme","UserEvents","sourcesWithLayers","resolveScope","predicate","mapping","resolver","scopeResolver","eventProxy","owner","registeredHandler","toRegisteredHandler","addEventHandler","removeHandler","toExposed","lngLat","features","inScope","candidate","exposed","AbstractMapModule","instanceCounters","_initializing","moduleReady","concreteClass","previousIndex","instanceIndex","eventsProxy","_eventsProxy","filterComposer","initSourcesWithLayers","applyConfig","onStyleChanged","resetState","resetOnNewStyle","restoreDataAndConfig","styleChangePriority","restore","_initSourcesWithLayers","_sourceAndLayerIDs","sourceAndLayerIDs","updateIfRegistered","waitUntilModuleReady","Promise","resolve","interval","setInterval","clearInterval","_applyConfig","emitConfigChange","updateConfig","partial","resetConfig","restoreDataAndConfigImpl","discardShownData","getConfig","_knobValue","_setKnobValue","_resetKnobValue","copy","end","configWithout","userEvents","sourceNames","userEventsOptions","sourcesNamed","events","lifecycleEvents","moduleEvents","moduleEventsWithShown","buildEvents","moduleWide","scopes","assign","EventStates","put","putEventState","clean","cleanEventState","cleanAll","cleanEventStates","ids","getEventStates","UserEventsWithStates","eventStates","AbstractDataOwnedMapModule","_args","eventStateSources","setVisible","applyVisibility","isVisible","userEventsWithStates","composersByMap","LayerVisibilityComposer","styleVisibility","getLayoutProperty","says","composeVisibility","AbstractStyleOwnedMapModule","visibilityComposer","AbstractEventProxy","interactiveLayerIDs","handlers","click","contextmenu","hover","findHandlers","types","bucket","ensureInteractiveLayerIDsAdded","layerIDs","releaseUnusedInteractiveLayerIDs","hasHandlerForLayer","indexHandler","unindexHandlers","touchedLayerIDs","dropHandlers","matches","dropped","size","registration","entry","removeAll","sourceWithLayersFor","restoredBySourceId","affected","restored","vacatedLayerIDs","renderedRefId","rendered","propId","findFeatureByRefId","TomTomMapSource","runtimeSource","ensureAddedToMap","AbstractSourceWithLayers","mapRemoved","isAnyLayerVisible","getLayerSpecs","isLayerVisible","areAllLayersVisible","every","queryRenderedFeatures","sourceID","setLayersVisible","filterLayersBySources","loadedMap","sourceIDs","StyleSourceWithLayers","AddedSourceWithLayers","sourceSpec","ensureLayersAddedToMap","ensureAddedToMapWithVisibility","addLayersToMap","emptyFeatureCollection","withEventState","eventState","GeoJSONSourceWithLayers","clusterOptions","cluster","promoteId","shownFeatures","showListeners","automaticVisibility","asDefined","setData","listener","onShow","findById","findFeature","showWithFeatures","shown","state","isCleaned","states","into","isClickEventType","isHoverEventState","featureId","featuresToUpdate","leaveEventState","fadeHover","leaveOtherFeatures","other","isSameEventFamily","removeEventStateAndShow","newEventType","rawFeature","previousFeaturesToUpdate","updatedIndex","updateEventState","eventFeature","previousEventFeature","previousSourceWithLayers","featureB","featureA","noRenderedFeatures","interactiveEventTypes","eventsProxyDefaultConfig","precisionMode","paddingBoxPx","cursorOnHover","cursorOnMouseDown","cursorOnMap","longHoverDelayAfterMapMoveMS","longHoverDelayOnStillMapMS","hoverGracePeriodMS","EventsProxy","enabled","recentHoverTimeouts","firstDelayedHoverSinceMapMove","mouseMoveFrameRequested","mapCanvas","getCanvas","cursor","lastCursorStyle","listenToEvents","hoveringFeature","ev","onMouseMove","onMouseStart","onMouseOut","onMouseDown","onMouseUp","onMapClick","enable","clearLongHoverTimeout","pendingMouseMove","toPaddedBounds","point","padding","x","y","isEnabled","isMoving","toCallerFeatures","firingFeature","scopedLayerIDs","scoped","seen","deduped","refId","dedupeRenderedFeatures","callerFeaturesFor","shared","getRenderedFeatures","precision","renderedFeatures","clearTimeout","longHoverTimeoutHandlerID","restartLongHoverTimeout","setTimeout","handleLongHoverTimeout","hoveringSourceWithLayers","longHoverHandlers","hoveringFeatures","firing","callerFeatures","hoveringLngLat","requestAnimationFrame","latestMove","handleMouseMove","hoveredTopFeature","hoverChanged","mouseInMotionOverHoveredFeature","hoveringPoint","previousHoveredPoint","previousHoveredFeature","detectHoverState","previousHoveredSourceWithLayers","dispatchHoverEnter","dispatchHoverMove","firstHandler","updateHoverCursor","hoverGracePeriodFor","clearRecentHoverLater","hoverHandlers","dispatchToHandlers","alreadyBuiltFeatures","hoverMoveHandlers","sharedFeatures","handlerFeatures","delayMS","clearRecentHover","clickType","clickedFeatures","previousClickedFeature","lastClickedFeature","previousClickedSourceWithLayers","lastClickedSourceWithLayers","clickHandlers","describe","String","expected","what","isFiniteNumber","isWithin","boundsText","listProblem","accepts","refused","validateKnobValue","knob","problem","component","problemOf","RangeError","knobEntryOf","catalogue","setKnob","module","getKnob","resetKnob","MAP_BOLD_FONT","MAP_REGULAR_FONT","MAP_MEDIUM_FONT","MAP_ITALIC_FONT","DEFAULT_TEXT_SIZE","mapFonts","PIN_ICON_SIZE","SELECTED_PIN_ICON_SIZE","mapStyleLayerIDs","country","lowestPlaceLabel","poi","lowestLabel","lowestRoadLine","lowestBuilding","BASE_MAP_SOURCE_ID","POI_SOURCE_ID","HILLSHADE_SOURCE_ID","TERRAIN_SOURCE_ID","TRAFFIC_INCIDENTS_SOURCE_ID","TRAFFIC_FLOW_SOURCE_ID","instancesByMap","sharedInstance","moduleClass","construct","applyToExisting","instances","instance","created","sourceOf","matchesOneOf","allowed","matchesLayerSelector","styleSources","metadata","group","metadataGroupOf","sourceTypes","sourceTypeOf","sourceLayers","sourceLayerOf","isInId","fragment","matchesIdFragments","matchesAnyLayerSelector","selectors","LayerPartToggles","matched","composer","valueOf","getStyleOwnedModule","isStyleLoaded","setStyle","getStyleInput","isSourceLoaded","waitUntilSourceIsLoaded","ensureAddedToStyle","styleSourceWithLayers","moduleName","poiLayerIDs","baseMapLayerGroupNames","layerGroupMappings","natureLabels","roads","railways","ferries","buildings2D","buildings3D","borders","roadLabels","roadShields","houseNumbers","allPlaceLabels","smallerTownLabels","cityLabels","capitalLabels","stateLabels","countryLabels","isMatching","baseMapLayerGroupSelector","buildLayerGroupFilter","inGroups","baseMapLayerFilter","roadPartSelectors","isGroupName","groupsVisible","groupEntries","groups","containsAll","groupsAsRecorded","layersByGroup","namedLayers","say","narrowed","BaseMapModule","vectorTiles","roadParts","writeVisibility","settings","layerSettings","layerGroups","recordAfter","groupedLayers","record","within","overrides","getLayers","getLayerIds","grouped","groupBaseMapLayers","resolveBaseMapEventScope","layerGroupContents","roadPartContents","exitNumbers","arrows","restricted","underConstruction","describeLayerGroup","buildCatalogue$2","baseMapKnobCatalogue","baseMapKnobIds","CustomGeoJSONModule","validateConfig","effectiveConfig","initialConfig","shownSourceNames","resolveIDs","registerImages","sourceName","resolvedSourceIDs","resolvedLayerSpecs","images","resolvedSourceID","layerIndex","previousConfig","previousLayerSpecs","updateLayersForExistingSources","previouslyShown","showOnSource","normalized","ensureFeatureIDs","getShown","needsRewrite","customGeoJSONKnobDefinitions","customGeoJSONKnobCatalogue","customGeoJSONKnobIds","mapAccentColor","mapColors","accent","onMapColorsChange","followed","_onMapColorsChange","colors","onAccentColorChange","beforeLayerTargetOf","all","beforeLayerIDOf","topLayerID","partBeforeLayerIDOf","defaultBeforeID","themeAdaptiveLabelColors","lightDark","textColor","haloColor","themeAdaptiveLabelShadeOf","lighterShadeOf","labelLayoutProperties","font","labelPaintProperties","haloWidth","opacity","featureTitle","title","defaultTitle","titleTextField","HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE","HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE","byOutlineFillStyle","outline","otherwise","BORDER_LABEL_OFFSET","getThemeAdaptiveGeometryColors","defaultColor","isClickEventState","hasActiveEventState","suffixNumber","text","numberToSuffix","prefixLayerID","layerName","layerIDPrefix","mergeLayer","override","mergeLayerOverride","_placedByBeforeLayerConfig","rest","fillOpacityByStyle","lineWidthByStyle","highlightedFillOpacity","highlight","fillOpacityFactor","highlightedLineWidth","lineWidthFactor","buildGeometryLayerSpecs","fillLayerId","outlineLayerId","configuredColor","fillBase","geometryFillSpec","outlineBase","outlineStyleColor","geometryOutlineSpec","fillLayerSpec","outlineLayerSpec","buildGeometryTitleLayerSpec","buildGeometryLineLabelLayerSpec","lineLabel","minZoom","symbolSpacing","offset","prepareGeometryForDisplay","effectiveFillStyle","fillStyle","processedFeature","masked","invertFeature","processedProperties","address","freeformAddress","buildTitle$1","palette","buildColor","geometryParts","layerOnlyConfigKeys","AbstractGeometriesModule","lastRawInput","display","displayConfig","updateLayers","styleLightDarkTheme","idPrefix","titleSourceID","layerIdPrefix","fillLayerID","outlineLayerID","lineLabelLayerID","titleLayerID","titleLayerSpec","titleLayerSpecs","geometryFillLayerSpecs","geometryOutlineLayerSpecs","lineLabelSpec","lineLabelLayerSpecs","geometryLabel","displayAgain","beforeLayerConfig","applyBeforeLayerConfig","partLayerIDs","newTitleLayerSpecs","newLineLabelSpec","prepared","shownProperties","withShownIdentity","prepare","prepareShown","geometries","coordinates","placeCoordinates","biggestPolygon","flat","ring","getLongestArray","bbox","prepareTitleForDisplay","showPolygons","GeometriesModule","transformFeaturesForDisplay","fillStyledGeometryConfig","geometryFillStyles","highlightKnobDefinitions","subject","fillStyleKnobDefinition","moduleKnobDefinitions","labelNames","labelKnobDefinitions","labelEntries","prefix","leaf","buildCatalogue$1","moduleEntries","isLineLabelKnob","geometriesKnobCatalogue","geometriesKnobIds","rangesModuleKnobDefinitions","buildRangesCatalogue","reachableRangesKnobCatalogue","reachableRangesKnobIds","titleOf","budget","invertedFill","_title","_fillStyle","layOutRanges","colorOf","largest","reachable","outermostRank","rank","fills","_id","invertedRanges","squareMeters","prepareReachableRanges","ranges","bandColors","byOrigin","origin","rankByOrigin","isBorder","ReachableRangesModule","count","ramp","innermost","outermost","_","bandColorRamp","landColor","directCategories","aboriginal_land","airport","amusement_park","atm","bank","bar","beach_resort","border_control","bus_station","cafe","camping_ground","car_rental","casino_and_gambling","cemetery","charging_location","cinema","clinic","clothing_shop","coffee_shop","college_or_university","commercial_area","conference_center","consumer_service","courthouse","cultural_center","dentist","department_store","doctor","emergency_ward_entrance","fast_food","ferry_terminal","fire_station","fitness_center","food_and_drinks_shop","foreign_exchange","fuel_station","gold_exchange","golf_course","government_office","heliport_or_helipad","historic_site","holiday_home","hospital","hotel_or_motel","ice_rink","industrial_area","landmark","mall","manufacturing_facility","marina","marketplace","media_facility","mountain_pass","museum","ngo_office","nightlife","park_and_recreation_area","park_and_recreation_facility","parking_facility","pharmacy","police_station","post_office","prison","protected_land","pub","public_library","public_transport_stop","railway_station","religious_site","rental_car_parking","research_facility","rest_and_service_area","restaurant","school_or_kindergarten","shop_or_store","sport_facility","stadium","supermarket","swimming_pool","taxi_stand","theater","toll_plaza","tourist_attraction","tourist_information","truck_stop","vehicle_dealer","vehicle_inspection","vehicle_repair","vehicle_wash","veterinary","viewpoint","weighbridge","zoo_or_aquarium","closestCategories","animal_shelter","cafeteria","childcare","comedy_club","horse_riding","ice_cream_shop","juice_bar","military_airfield","mourning_facility","personal_care_service","planetarium","post_depot","stock_exchange","tea_house","transport_access","winter_sports","baseMapCategories","table","baseMapCategory","categories","toBaseMapPOICategory","baseMapPOICategoryValues","baseMapCategoryGroups","eat_and_drink","shopping","finance","lodging","transport","driving","parking","healthcare","education","cultural","leisure","sport","outdoor","protected","public","military","religion","business","TITLE","pinIconBaseLayout","PIN_TRANSLATE","pinIconBasePaint","pinTextBaseLayout","pinTextBasePaint","pinLayerBaseSpec","isClusterFilter","isNotClusterFilter","CLUSTER_PROPERTY_BASE_MAP_ICON_IDS","isSingleCategoryClusterFilter","isMixedCategoryClusterFilter","DEFAULT_SINGLE_BADGE_OFFSET","singleBadgeOffset","badge","badgeCirclePaint","radius","strokeColor","strokeWidth","countTextLayout","clusteredPlacesAggregateProperties","buildKnownBaseMapSpriteImageExpr","dynamicNameExpr","arms","spriteId","calculateIconScale","markerType","dimensions","svgElement","viewBox","getAttribute","parts","split","parseFloat","naturalHeight","extractImageDimensions","isCenteredMarkerType","refHeight","refWidth","heightScale","widthScale","buildCustomIconOffsets","icons","iconOffsets","icon","offsetX","offsetY","buildIconOffsetExpression","matchedProperty","offsetCaseExpression","iconId","hasChargingAvailability","chargingPark","availability","isEVStationWithAvailability","place","getChargingPointAvailability","chargingPointAvailability","available","statusCounts","Available","availableCount","totalCount","ratio","defaultFormatAvailabilityText","total","getAvailabilityRatio","getAvailabilityColorExpression","threshold","round4","n","buildAnchorOffsets","topOffset","sideOffset","pinVerticalAdjustment","customTextOffset","hasCustomOffset","top","left","right","getTextOffset","iconSizeExpression","iconTextOffsetScales","iconScaleMultiplier","expression","lastValue","extractMaxIconScale","fallbackSideOffset","fallbackOffsets","fallbackAnchorOffset","scales","DEFAULT_TEXT_OFFSET_Y","offsets","buildLayoutConfig","textField","labelConfig","customLayer","hasCustomIcons","baseLayout","needsOffsetRecalculation","layoutWithTextOffset","iconOffset","buildPaintConfig","baseTextColor","baseHaloColor","basePaint","poiLookKnobIds","getTextSizeSpec","textSize","replaceAll","findStyleLayer","buildBaseMapPOILayerFoundation","poiLayer","buildBaseMapPOILayerSpec","foundation","buildPOIMicroLikeLayerSpec","buildCircleBaseLayerSpec","poiLayout","HIDDEN_LAYER_SPEC","HIDDEN_CIRCLE_LAYER_SPEC","hasNoActiveEventState","poiLookMarkerTypes","pinLayerSpec","toSelectedSpec","selectedColor","selectedPinLayerSpec","withConfig","evAvailabilityEnabled","evAvailability","buildTextFieldExpression","buildPlacesLayerSpecs","iconScales","customIcons","categoryIcons","hasOffset","suffixedIconId","availabilityLevel","availabilitySuffixedId","buildCustomIconScalesMap","main","selected","micro","clusterBadge","clusterCount","clusterMixedBadge","clusterMixedCount","buildBaseMapSelectedLayerSpec","buildCircleIconMainLayerSpec","buildCircleIconSelectedLayerSpec","microBase","single","buildClusterBadgeCircleLayerSpec","buildClusterCountTextLayerSpec","mixed","buildClusterMixedBadgeCircleLayerSpec","buildClusterMixedCountTextLayerSpec","microBeforeID","partAnchor","placesBeforeID","placesPartAnchors","additional","getThemeAdaptiveConnectionColors","dark","labelHaloColor","lineColor","labelColor","dashedLineSpec","dashArray","buildBaseLineSpec$1","buildBaseLabelSpec","isMainEntryPoint","buildBaseLineSpec","buildBasePointSpec","buildEntryPointLayerSpecs","lowestUnclusteredZoom","moduleColor","getThemeAdaptiveEntryPointColors","placesMarkerTypes","toEntryPointID","placeID","entryPointIndex","isShownFor","showFor","BADGE_RADIUS_RANGE","placesKnobDefinitions","placesKnobCatalogue","placesKnobIds","paintPin","pin","baseStyle","drawPin","painted","toPaintedPin","isSamePaint","customImagesByID","imageIDOf","addCustomImages","incidentColorKnobIds","minor","moderate","major","indefinite","onStyleKnobsApplied","knobIds","_onStyleKnobsApplied","applied","defaultPin","resolveEndpoint","endpoint","shownPlaces","isMapCategoryIcon","iconsByMapCategory","mapCategory","mappedImageID","categoryImageID","fn","supportedPinSubcategories","pinImageSubstitutes","missing","substitute","toPinImageID","categoryID","imageCategoryID","substring","buildPlaceTitle","toImageID","poiCategory","defaultPlaceIconID","getIconIDForPlace","resolveDefaultPlaceIconID","mappedID","evAvailabilityIconID","requiredLevel","customIconWithAvailability","customIcon","getEVAvailabilityIconID","matchingCustomIcon","findCategoryIcon","getEffectivePOICategory","getPOILayerCategoryForPlace","getPOIGroupForPlace","toBaseMapPOIGroup","toPlaces","places","mergeEVAvailabilityProps","extraFeatureProps","evAvailabilityConfig","hasEVStations","hasEVStationsWithAvailability","evAvailabilityText","formatText","buildAvailabilityText","evAvailabilityRatio","getBaseMapIconIDForPlace","preparePlacesForInstance","mergedExtraFeatureProps","prop","iconID","baseMapIconID","preparePlacesForDisplay","placeSources","PlacesModule","refreshingEntryPoints","updateLayersAndData","updatePlacesLayers","unclusteredSourceID","connectionSourceID","entryPointSourceID","shownConnections","addedCategoryIcons","buildLayerSpecs","buildClusterOptions","currentClusterOptions","splitSpecs","partitionSpecsForSources","clusteredSpecs","placesUnclustered","unclusteredSpecs","connectionLayerSpecs","buildConnectionLayerSpecs","connections","addOverlaySource","entryPointLayerSpecs","entryPoints","refreshEntryPoints","updateEntryPointsData","replaceLayerSpecs","newSpecs","oldSpecs","layerSpecTemplates","internalNames","clusterRadius","clusterMaxZoom","clusterProperties","specs","lineID","labelID","previousShownFeatures","previousConnections","showConnections","applyPlacesVisibility","applyVisibilityTo","setupImages","updateData","updateConnectionsData","restackLayers","rebuildSourcesAndLayers","newSplit","oldSplit","labelFn","connection","fromId","toId","prepareConnectionsForDisplay","previousEventStates","position","entryPoint","prepareEntryPointsForDisplay","hasData","paintedPin","clearConnectionsSync","clearConnections","toEntryPointEventFeature","poiCategoryGroups","FOOD_DRINKS_GROUP","SHOPPING_GROUP","TRANSPORTATION_GROUP","HEALTH_GROUP","PARKING_GROUP","HOLIDAY_TOURISM_GROUP","EV_CHARGING_STATIONS_GROUP","GAS_STATIONS_GROUP","ACCOMMODATION_GROUP","ENTERTAINMENT_GROUP","SPORTS_LEISURE_GROUP","EDUCATION_GROUP","GOVERNMENT_GROUP","poisKnobDefinitions","categoriesFilter","sizeFactor","zoomShift","poisKnobCatalogue","poisKnobIds","ZOOM_BOUNDS","maxZoom","LAYOUT_PROPERTIES","SPEC_DEFAULTS","isLayerMechanism","mechanism","targetKey","NO_TRANSITION","duration","enginesByMap","StyleKnobs","stale","registered","originalValues","resolved","scaleKnobsByTarget","warned","indexStyle","extension","definition","resolveKnob","indexedStyle","update","touched","refreshKnob","publish","derivedValueOf","capture","styleValueOf","originalValueOf","setProperty","writer","_styleKnobSettings","_setStyleKnobSettings","_isStyleLayer","projection","sky","holdTransitionsUntilFirstFrame","stylesheet","transition","shipped","mechanisms","resolveMechanism","resolvedMechanism","shadeLayerIds","captureAll","shadeSelectors","shades","layerFilterOf","resolvedKnob","warnOnce","refreshMechanism","refreshLayer","refreshColor","refresh","refreshScaledProperty","refreshZoomBound","refreshZoomOffset","bound","styleZoomBound","original","product","knobId","primaryOriginal","shadeOriginal","shaded","first","message","StyleKnobScope","knobIdsByLocalId","colorKnobIds","engineOf","engine","local","styleKnobScope","hasSpecDefault","scaleKnob","colorKnob","styleModuleLayerGroupNames","styleModuleLayerGroupSelectors","pois","hillshade","styleLayerGroupSelector","isStyleModuleLayerGroup","poisStyleKnobs","poiLayers","poisLook","poisMapping","getStyleCategories","categoryIds","isCategoryGroup","categoryId","iconLayoutProperties","iconImageExpression","imageByCategory","styleIconImage","styleArm","iconImage","tokenStringToExpression","toIconImageID","POIsModule","addedIcons","microMarkers","styleIconLayout","_setVisible","applyCategoriesFilter","applyCategoryIcons","poiFilter","offsetByCategory","styleIconOffset","resolveCountryCrossingColors","plaque","readable","generatedSectionTypes","bespokeSectionTypes","drawnSectionTypes","ALL_SECTION_KNOBS","SECTION_REGISTRY","carpool","sourceKey","pattern","knobs","carTrain","importantRoadStretch","lowEmissionZone","motorway","pedestrian","speedLimit","tollVignette","unpaved","urban","ferry","tollRoad","traffic","tunnel","vehicleRestricted","sectionSupportsKnob","knobName","sectionEntry","isGeneratedSectionType","sectionDrawsByDefault","postsSign","sectionSourceKey","mapLayerSpecs","routeDisplayUnits","displayUnits","zoomRamp","ROUTE_LINE_OUTLINE_COLOR","SELECTED_ROUTE_FILTER","DESELECTED_ROUTE_FILTER","selectedRouteLine","selectedRoutePOISymbol","instanceImageID","ROUTE_WIDTH_FACTOR_RANGE","ROUTE_WAYPOINT_SIZE_FACTOR_RANGE","ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE","SECTION_ICON_SIZE_FACTOR_RANGE","ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE","isExpression","scaleRamp","getSectionIconSize","layerIconSize","iconSize","scaledWidth","profiles","lineWidth","widthFactor","LINE_FOREGROUND_WIDTHS","m","LINE_OUTLINE_WIDTHS","WAYPOINT_ICON_SIZE","WAYPOINT_SIZE_PRESET_FACTORS","getLineForegroundWidth","waypointScale","getWaypointIndexTextSize","STOP_MARK_TEXT_OFFSET","SECTION_HALO_WIDTHS","getSectionHaloWidth","INSTRUCTION_LINE_WIDTHS","INSTRUCTION_OUTLINE_WIDTHS","getInstructionLineWidth","getInstructionOutlineWidth","INSTRUCTION_ARROW_SIZE","getInstructionArrowSize","DEFAULT_CROSSING_COLORS","COUNTRY_CROSSING_LAYER_ID","CROSSING_TEXT_SIZE","CROSSING_ROUTE_ROTATION","countryCrossingSymbol","alignedToRoute","alignment","GENERATED_SECTION_PAINT","DASH_ARRAYS","solid","dashed","dotted","isSectionVisible","sectionDrawStyle","SECTION_STYLE_ANCHOR","halo","inline","sectionStyleAnchor","drawStyle","withSectionLineKnobs","routeLineWidth","role","applyLinePatternKnob","withSectionSymbolKnobs","placement","sectionLine","SIGN_ICON_SIZE","POSTED_SIGN_ICON_SIZE","POSTED_SIGN_TEXT_SIZE","SIGN_SORT_KEY","SIGN_TEXT_OFFSET","signPlacement","sectionSign","SECTION_START_SIGN_IMAGE_IDS","sectionStartSign","sectionLayerID","charAt","toUpperCase","sectionSignLayerID","START_INDEX","MIDDLE_INDEX","FINISH_INDEX","STOP_DISPLAY_LABEL","routeWidths","routeKnobDefinitions","sectionKnobDefinitions","waypointPinNames","start","middle","finish","waypointPinStyleKnobDefinitions","pins","sectionKnobId","buildCatalogue","sectionKnobLeaves","leavesOf","routingKnobCatalogue","routingKnobIds","stopDurationLabelSection","hasStopDuration","chargingStopTextField","chargingStopSymbol","commonProps","commonLineLayout","instructionOutline","instructionLine","routeFerriesLine","ferryTerminalSymbol","routeFerriesSymbol","outlineWidth","highlightFactor","getLineOutlineWidth","routeLineBaseTemplate","routeDeselectedOutline","routeDeselectedLine","routeOutline","routeMainLine","routeColor","routeLineArrows","routeTollRoadsOutline","routeTollRoadsSymbol","severityLineDashColor","trafficDelayColorKnobIds","trafficDelayColors","matchMagnitudeOfDelay","magnitudeOfDelayTextColor","routeIncidentsDashedLine","routeIncidentsSymbol","iconIDProperty","routeIncidentsJamSymbol","routeIncidentsCauseSymbol","routeTunnelsLine","routeVehicleRestrictedBackgroundLine","routeVehicleRestrictedDottedLine","hasFormattedTraffic","buildSummaryBubbleSymbolPoint","baseImageID","summaryBubbleSymbolPoint","waypointLabelTextField","waypointSymbols","waypoints","waypointLabels","prefixBeforeID","prefixBeforeIDs","buildRoutingLayers","trafficColors","partBeforeID","mainLinesBeforeID","instructionLinesBeforeID","routeIconsBeforeID","configLayers","configSectionLayers","sections","outlineWidthFactor","routeInstructionArrow","sectionConfigs","routeWaypointSymbol","startIconPadding","waypointIconAnchor","facing","side","sectionStartIconPadding","withStartIconPadding","bespokeSectionLine","bespokeSectionSymbol","generatedSections","sectionConfig","lineLayerID","sectionLineLayerID","symbolLayerID","sectionSymbolLayerID","advancedConfig","signLayerID","symbolLayer","sectionSymbol","mainLines","routeLine","routeWaypointLabel","chargingStops","routeChargingStopSymbol","routeIncidentJamSymbol","routeIncidentCauseSymbol","routeIncidentBackgroundLine","routeIncidentDashedLine","routeFerryLine","routeFerrySymbol","routeTollRoadOutline","routeTollRoadSymbol","routeTunnelLine","routeTunnelSymbol","routeVehicleRestrictedForegroundLine","routeVehicleRestrictedSymbol","instructionLines","routeInstructionLine","routeInstructionOutline","instructionArrows","routeInstructionArrowSymbol","summaryBubbles","routeSummaryBubbleSymbol","countryCrossings","defaultRoutingLayers","svgImg","buildSvg","instructionArrowIconImg","summaryMapBubbleImg","svg","summaryBubbleImageOptions","stretchX","stretchY","content","countryCrossingPlaqueImageOptions","countryCrossingPlaqueImg","roundSignSvg","face","ringWidth","WHITE_DISC_SVG","SPEED_LIMIT_SIGN_SVGS","whiteDisc","yellowDisc","SECTION_START_SIGN_SVGS","signImg","sectionStartSignImg","speedLimitSignImg","trafficImg","waypointGlyphs","bearingAtCrossing","before","after","toCrossingFeature","departed","entered","routeProps","startPointIndex","fromCountryCode","countryCodeISO2","toCountryCode","fromSectionStartPointIndex","toSectionStartPointIndex","routeState","routeIndex","crossingsOfRoute","route","countries","ordered","sort","toDisplayCountryCrossings","routes","getIconID","chargingStop","iconConfig","basedOn","chargingConnectionInfo","chargingSpeed","formatTitle","chargingParkName","chargingParkOperatorName","hasJam","sectionProps","toDisplayTrafficSectionProps","buildTitle","jamIconID","toTrafficJamIconSuffix","toJamIconID","causeIconID","firstNonJamCategory","incidentIconID","toCauseIconID","buildWaypointTitle","waypoint","placeProperties","WAYPOINT_IMAGE_IDS","getImageIDForWaypoint","indexType","stopDisplayLabel","stopNumber","numbering","letters","floor","fromCodePoint","toLetters","asWaypoint","waypointInput","toDisplayWaypoints","timeUnits","arrayLength","indexTypeFor","stopDuration","pauseDurationSeconds","displayWaypoint","useEntryPoint","stopDisplayIndex","stopNumbering","toDisplayRouteSections","sectionType","displaySectionPropsBuilder","endPointIndex","buildRouteSectionsFromRoute","showFeaturesWithRouteSelection","routesWithSelection","rebuildFeaturesWithRouteSelection","toDisplayRoutes","selectedIndex","routesCollection","toDisplayRouteSummaries","summary","routeCoordinates","formattedTraffic","trafficDelayInSeconds","time","trafficSections","magnitude","section","summaryDelayMagnitude","formattedDistance","lengthInMeters","distance","formattedDuration","travelTimeInSeconds","SIGN_BY_COUNTRY","GBR","unit","LBR","MMR","USA","SWE","FIN","ISL","JPN","numerals","SIGN_BY_DISPLAY_UNITS","metric","imperial_uk","imperial_us","SPEED_LIMIT_IMAGE_ID_BY_FACE","postedSpeedLimit","maxSpeedLimitInKmh","RoutingModule","repaintTrafficSections","incidents","buildLayersSpecs","layersSpecs","routeColorOf","layerConfigs","tollRoads","tunnels","createLayersSpecs","createSourcesWithLayers","sourcePrefix","routingSourcesWithLayers","instructionArrowImageId","selectedSummaryPopupImageId","deselectedSummaryPopupImageId","trafficClearImageId","trafficMajorImageId","trafficModerateImageId","trafficMinorImageId","addImageIfNotExisting","appliedCrossingColors","crossingColors","displayUnitsChanged","appliedDisplayUnits","addConfiguredImages","layersSpecID","listOfSources","reshowSpeedLimitSigns","applySignPriority","reshowWaypoints","summaryBubblesVisible","visibilityChanged","hasSummaryBubbles","hasRoutes","applyCountryCrossingConfig","glyph","appendChild","waypointPinImg","paintedWaypointPins","shownWaypoints","displayWaypoints","displayWaypointsOf","showSpeedLimitSections","displayRoutes","speedLimitSections","pointIndex","countrySections","countryCodeISO3","convention","signImageID","signLabel","signFace","signNumeralsColor","crossingsVisible","crossingLayerID","signAnchorLayerID","signPriority","lineArrowLayerIDs","showRoutes","displayChargingStops","leg","chargingInformationAtEndOfLeg","stopTimeInSeconds","chargingTimeInSeconds","chargingParkId","chargingPower","chargingPowerInkW","chargingDuration","toDisplayChargingStops","guidance","instructions","instruction","routePath","pathPoint","toDisplayInstructions","instructionLastSegment","lastPointBearingDegrees","toDisplayInstructionArrows","clearRoutes","selectRoute","updatedRoutes","showWaypoints","toIdentifiedWaypoints","clearWaypoints","generatedSectionScopes","shownRoutes","sectionAt","fromSection","toSection","toCountryCrossingEventFeature","getLayerToRenderLinesUnder","arrowsLayerID","appliesTo","mapColor$1","knobDefinitions","stylingFoundationsKnobIds","stylingFoundationsKnobCatalogue","LayerSelection","registry","query","setPaint","setLayout","overrideFilter","reset","LayerOverrides","selectorsOf","perLayer","read","write","reapply","getPaintProperty","SKY_DEFAULTS","blends","light","SPACE_COLOR_DEFAULTS","UNPAINTED_BACKGROUNDS","CLEAR_SKY","extensionsByMap","MapLookKnobs","mapColorTargetsByKey","mapColorTargetsByKnob","mapColorAnchors","landDerivedColors","resolveMapColor","refreshProjection","refreshSky","refreshSpace","refreshMapColor","mapLevelDefaultOf","mapColorsOf","mapColorsInForce","container","getContainer","originalSpaceColor","paintedBackgroundOf","backgroundColor","defaultSpaceColor","ownershipOf","anchorLayer","lastWins","best","saturation","tie","pickAnchorLiteral","refreshMapColorProperty","setProjection","styleProjection","styleSky","themeSky","setSky","getComputedStyle","mapColor","stylingFoundationsLook","baseMapSymbols","roadLines","skyColor","StylingFoundationsModule","styleKnobs","layerOverrides","withoutUndefined","_setMapColors","assertKnob","setMapColors","exportStyle","structuredClone","mapPresets","stylingFoundations","baseMap","minimal","globe","mapPresetIds","mapPresetCatalogue","applyPresetPart","resetIds","applyMapPreset","preset","styling","merge","presetKnobIds","hillshadeLook","hillshadeLayers","hillshadeLiteral","trafficLook","lines","levelColor","closedColor","trafficFlowStyleKnobs","flow","trafficIncidentsStyleKnobs","trafficFlowLook","trafficIncidentsLook","styleKnobScopes","styleColorKnobIds","getStyleColor","stylingFoundationsKnobKinds","stylingFoundationsKnobAppliesTo","isLoading","ensureMapLibreCSS","probe","className","pointerEvents","isMapLibreCSSApplied","import","css","layeredCSS","Reflect","sheet","CSSStyleSheet","replaceSync","adoptedStyleSheets","textContent","head","applyLayered","finally","catch","TomTomMap","_styleLightDarkTheme","_mapColors","mapColorsHandlers","styleKnobSettings","styleKnobsAppliedHandlers","tomtomMapParams","styleChangeHandlers","styleRequestSequence","styleLayerIds","requestNumber","effectiveStyle","previousStyle","withPreviousStyleParts","_params","applyStyle","styleGivenToMapLibre","validateStyle","maxTileCacheZoomLevels","cancelPendingTileRequestsWhileZooming","mapLibre","attributionControl","compact","recordStyleLayers","handleStyleData","notifyHandlers","isSuperseded","styleLoadInFlight","styleLoad","loadIntoMapLibre","carryModuleLayers","reject","onStyleLoad","onError","event","transformStyle","callback","a","_setTomTomUserAgent","userAgent","setTransformRequest","_setLanguage","language","mapLanguage","isLayerLocalizable","textFieldValue","setLanguage","setGeopoliticalView","updateEventsConfig","getBBox","getBounds","toArray","background","detectStyleLightDarkTheme","theme","getSprite","sprite","URLSearchParams","addSprite","addPinCategoriesSpriteToStyle","disposed","hillshadeMethods","terrainKnobCatalogue","terrainKnobIds","TerrainModule","mapTerrain","getTerrain","showHillshade","showElevation","elevation","exaggeration","setHillshadeVisible","storeConfig","isHillshadeVisible","setElevationVisible","isElevationVisible","overridesMapTerrain","setTerrain","change","areaAnalyticsPalettes","AREA_ANALYTICS_DEFAULTS","displayMode","activeMetric","metrics","congestionLevel","maxHeightMeters","minHeightMeters","scaleMode","speed","travelTime","freeFlowSpeed","networkLength","regionPolygon","getThemeAdaptiveRegionPolygonColor","PALETTE_COLORS","trafficLight","heat","monochrome","viridis","plasma","DEFAULT_METRIC_RANGES","paletteScaleMode","getActiveMetric","resolveColorStops","metricConfig","colorStops","isSpeedMetric","DEFAULT_BEFORE_LAYER$1","heatmap","beforeIDOf","interpolationStops","sorted","buildColorExpression","computedRange","buildHeatmapColorExpression","densityStops","minValue","buildHeightExpression","heightConfig","defaultHeight","minHeight","metersPerUnit","maxHeight","buildHeatmapLayerSpec","buildCellFillLayerSpec","buildCellExtrusionLayerSpec","buildHexFillLayerSpec","hexgridFill","buildHexExtrusionLayerSpec","hexgridExtrusion","buildSquareFillLayerSpec","squareFill","buildSquareExtrusionLayerSpec","squareExtrusion","resolveRegionConfig","region","defaults","buildRegionFillLayerSpec","regionFill","buildRegionLineLayerSpec","regionLine","buildPolygonOutlineLayerSpec","buildHexOutlineLayerSpec","hexgridOutline","buildSquareOutlineLayerSpec","squareOutline","buildExtrusionHighlightLayerSpec","buildHexExtrusionHighlightLayerSpec","hexgridExtrusionHighlight","buildSquareExtrusionHighlightLayerSpec","squareExtrusionHighlight","svgMarkup","INCIDENT_HIGHLIGHT_WIDTH_FACTOR_RANGE","INNER_WIDTH","OUTLINE_WIDTH","buildColorExpr","unknown","INNER_PATTERN","getThemeAdaptiveHighlightOutlineColor","HALO_WIDTH","isFocusedOrActive","haloOpacity","highlightWidth","SOLID_FILTER","PATTERN_FILTER","buildCanonicalIncidentLineLayers","highlightHalo","innerSolid","innerChevron","innerPattern","ICON_SIZE","JAM_TEXT_SIZE","INCIDENT_MARKER_ICON","INCIDENT_MARKER_FILTER","JAM_MARKER_FILTER","CLOSED_ROAD_FILTER","JAM_ICON","JAM_TEXT","SORT_KEY","buildCanonicalIncidentSymbolLayers","incidentMarker","jamMarker","closedRoadMarker","roadCategories","streetRoadSubCategories","serviceRoadSubCategories","HEIGHT_METERS_RANGE","RAW_HEIGHT_METERS_PER_UNIT_RANGE","OUTLINE_WIDTH_RANGE","roadCategoriesFilter","roadSubCategoriesFilter","roadFilterKnobDefinitions","incidentCategoriesFilter","magnitudesFilter","flowKnobDefinitions","incidentsKnobDefinitions","incidentDetailsKnobDefinitions","areaAnalyticsKnobDefinitions","metricKnobDefinitions","buildAreaAnalyticsCatalogue","metricKnobLeaves","trafficFlowKnobCatalogue","trafficFlowKnobIds","trafficIncidentsKnobCatalogue","trafficIncidentsKnobIds","trafficIncidentDetailsKnobCatalogue","trafficIncidentDetailsKnobIds","trafficAreaAnalyticsKnobCatalogue","trafficAreaAnalyticsKnobIds","tilesToPointFeatures","analytics","tiledData","lon","lat","tileCentre","aggregateTilesIntoCells","grid","cellAggregates","cellIndex","cell","congestionSum","speedSum","freeFlowSum","travelTimeSum","networkLengthSum","cellAggregatesToFeatures","buildCellAggregates","tilesToGridFeatures","cellSideKm","buildGridFn","allCells","featureIndex","analyticsFeature","tilesToHexFeatures","km","units","tilesToSquareFeatures","isRegionInverted","dataSources","TrafficAreaAnalyticsModule","lastAnalytics","_restore","heatmapSourceId","hexgridSourceId","squareSourceId","regionSourceId","heatmapLayerId","hexFillLayerId","hexExtrusionLayerId","hexOutlineLayerId","hexExtrusionHighlightLayerId","squareFillLayerId","squareExtrusionLayerId","squareOutlineLayerId","squareExtrusionHighlightLayerId","regionFillLayerId","regionLineLayerId","heatmapLayerSpec","hexFillLayerSpec","hexExtrusionLayerSpec","squareFillLayerSpec","squareExtrusionLayerSpec","regionFillLayerSpec","regionLineLayerSpec","hexOutlineLayerSpec","hexExtrusionHighlightLayerSpec","squareOutlineLayerSpec","squareExtrusionHighlightLayerSpec","hexgrid","square","addHighlightLayersToMap","defaultKeys","filledMetricConfig","merged","applyLayerConfig","applyRegionConfig","moveLayersToSpecPositions","buildRegionFeatureCollection","setPalette","setMetricField","setColorStops","setHeight","setFilters","applyFiltersToLayers","field","updated","hideAllDataLayers","getDisplayMode","hasHeatmapData","hasHexData","hasSquareData","hasRegionData","withSource","newHeatmapSpec","newHexFillSpec","newHexExtrusionSpec","newHexExtrusionHighlightSpec","newSquareFillSpec","newSquareExtrusionSpec","newSquareExtrusionHighlightSpec","newHexOutlineSpec","newSquareOutlineSpec","newRegionFillSpec","newRegionLineSpec","inverted","conditions","own","addValuesFilter","valuesFilter","expressions","valuesMapping","buildValuesFilter","addCommonFilterExpressions","sdkFilter","roadSubCategories","buildMapLibreIncidentsFilter","incidentCategories","magnitudes","delayFilter","delays","mustHaveDelay","minDelayMinutes","atLeastMinDelay","delayFilterToMapLibre","buildMapLibreAnyFilters","any","buildOne","mapLibreFilter","buildMapLibreFlowFilter","roadClosures","applyTrafficFilters","build","previousFilters","applyFilter","FLOW_FILTERS","flowFilters","TrafficFlowModule","LAYER_IDS","FALLBACK_INCIDENT_PALETTE","readIncidentPalette","byId","nonLiteralLayerIds","readLineColor","nonLiteralSink","TrafficIncidentDetailsModule","lastFeatureIds","lastFocusIds","resolveHighlightStyle","stackingOrder","chevronImg","getIncidentDirectionChevronImage","applyBeforeLayer","beforeId","lid","setFocus","clearFeatureState","f","focusSet","setFeatureState","focused","removeFeatureState","INCIDENTS_FILTERS","incidentFilters","TrafficIncidentsModule","_setIconsVisible","setIconsVisible","otherIconSettings","isIconsVisible","getRelativeElementBounds","elementRect","containerRect","bottom","isElementOutsideContainer","containerWidth","containerHeight","adjustForHorizontalBar","currentVisible","adjustForVerticalBar","adjustVisibleBoundsForElement","mapWidthPX","mapHeightPX","clampedBounds","clampBoundsToContainer","spansFullWidth","spansFullHeight","touchesLeft","touchesRight","touchesTop","touchesBottom","distanceFromLeft","distanceFromRight","distanceFromTop","distanceFromBottom","minDistance","adjustForFloatingElement","toElement","reference","getMapLibreMap","calculateVisibleAreaBounds","surroundingElements","paddingPX","getBoundingClientRect","visibleAreaBounds","elementReference","elementBounds","calculatePaddedBBox","southWest","unproject","northEast","lng","calculatePaddedCenter","calculateFittingBBox","toBeContainedBBox","leftRatio","rightRatio","topRatio","widthRatio","heightRatio","west","south","east","north","fullWidth","fullHeight","expandedWest","expandedNorth"],"sources":["../src/shared/CombinedEvents.ts","../src/shared/eventScope.ts","../src/shared/types/filters.ts","../src/shared/knobUtils.ts","../src/shared/mapLibreFilterUtils.ts","../src/shared/ofMap.ts","../src/shared/layers/abstractLayerComposer.ts","../src/shared/layers/layerFilterComposer.ts","../src/shared/ModuleEvents.ts","../src/init/types/mapInit.ts","../src/shared/errorMessages.ts","../src/init/styleInputBuilder.ts","../src/shared/layers/expressionTransforms.ts","../src/utils/colorUtils.ts","../src/styling/mapColorCatalogue.ts","../src/styling/mapColorDerivation.ts","../src/styling/colorRewrite.ts","../src/traffic/util/trafficFlowMapping.ts","../src/traffic/util/trafficIncidentMapping.ts","../src/shared/imageUtils.ts","../src/shared/ModuleSourcesAndLayers.ts","../src/shared/resources/index.ts","../src/shared/mapUtils.ts","../src/shared/UserEvents.ts","../src/shared/AbstractMapModule.ts","../src/shared/EventStates.ts","../src/shared/UserEventsWithStates.ts","../src/shared/AbstractDataOwnedMapModule.ts","../src/shared/layers/layerVisibilityComposer.ts","../src/shared/AbstractStyleOwnedMapModule.ts","../src/shared/AbstractEventProxy.ts","../src/shared/featureId.ts","../src/shared/assertionUtils.ts","../src/shared/TomTomMapSource.ts","../src/shared/SourceWithLayers.ts","../src/shared/eventUtils.ts","../src/shared/EventsProxy.ts","../src/shared/knobValues.ts","../src/shared/layers/commonLayerProps.ts","../src/shared/layers/layerIDs.ts","../src/shared/layers/sourcesIDs.ts","../src/shared/sharedInstance.ts","../src/shared/layers/layerSelector.ts","../src/shared/layers/layerPartToggles.ts","../src/shared/styleOwnedModules.ts","../src/pois/layers/poisLayers.ts","../src/base/types/baseMapModuleConfig.ts","../src/base/layerGroups.ts","../src/base/BaseMapModule.ts","../src/base/knobCatalogue.ts","../src/custom/CustomGeoJSONModule.ts","../src/custom/knobCatalogue.ts","../src/shared/accentColor.ts","../src/shared/layers/beforeLayer.ts","../src/shared/layers/labelProperties.ts","../src/geometry/layers/constants.ts","../src/shared/layers/eventState.ts","../src/shared/layers/utils.ts","../src/geometry/layers/geometryLayers.ts","../src/geometry/prepareGeometryForDisplay.ts","../src/geometry/AbstractGeometriesModule.ts","../src/geometry/GeometriesModule.ts","../src/geometry/geometryConfigs.ts","../src/geometry/types/geometryFillStyle.ts","../src/geometry/knobCatalogue.ts","../src/geometry/prepareReachableRanges.ts","../src/geometry/ReachableRangesModule.ts","../src/pois/util/poiCategoryMapping.ts","../src/shared/layers/symbolLayers.ts","../src/places/layers/clusterLayers.ts","../src/places/utils/customIconScales.ts","../src/shared/layers/iconOffset.ts","../src/places/utils/evAvailabilityHelpers.ts","../src/places/utils/textOffsetCalculator.ts","../src/places/utils/layerConfiguration.ts","../src/places/utils/layerSpecBuilders.ts","../src/places/layers/placesLayers.ts","../src/places/layers/connectionsLayers.ts","../src/places/layers/entryPointsLayers.ts","../src/places/types/placesModuleConfig.ts","../src/places/utils/prepareEntryPointsForDisplay.ts","../src/places/knobCatalogue.ts","../src/shared/pinImages.ts","../src/shared/styleKnobsApplied.ts","../src/places/resources/index.ts","../src/places/utils/prepareConnectionsForDisplay.ts","../src/pois/util/categoryIcons.ts","../src/places/utils/iconMapping.ts","../src/places/utils/toPinImageID.ts","../src/places/utils/preparePlacesForDisplay.ts","../src/places/PlacesModule.ts","../src/pois/types/poiCategoryGroups.ts","../src/pois/knobCatalogue.ts","../src/styling/StyleKnobs.ts","../src/styling/styleKnobDefinitions.ts","../src/styling/styleLayerGroups.ts","../src/pois/poisStyleKnobs.ts","../src/pois/util/poisMapping.ts","../src/pois/POIsModule.ts","../src/routing/util/countryCrossingColors.ts","../src/routing/layers/sectionRegistry.ts","../src/routing/util/config.ts","../src/routing/layers/shared.ts","../src/routing/layers/countryCrossingLayers.ts","../src/routing/layers/sectionLayers.ts","../src/routing/types/waypointDisplayProps.ts","../src/routing/knobCatalogue.ts","../src/routing/util/stopDuration.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/layers/waypointLayers.ts","../src/routing/layers/routingLayers.ts","../src/routing/resources/index.ts","../src/routing/util/countryCrossings.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/util/speedLimitSigns.ts","../src/routing/RoutingModule.ts","../src/styling/knobCatalogue.ts","../src/styling/layerQuery.ts","../src/styling/MapLookKnobs.ts","../src/styling/stylingFoundationsStyleKnobs.ts","../src/styling/StylingFoundationsModule.ts","../src/styling/presets.ts","../src/terrain/hillshadeStyleKnobs.ts","../src/traffic/trafficStyleKnobs.ts","../src/styling/styleColors.ts","../src/styling/types/stylingTypes.ts","../src/init/buildMapOptions.ts","../src/init/mapLibreCSS.ts","../src/shared/localization.ts","../src/TomTomMap.ts","../src/terrain/types/terrainModuleConfig.ts","../src/terrain/knobCatalogue.ts","../src/terrain/TerrainModule.ts","../src/traffic/types/trafficAreaAnalyticsConfig.ts","../src/traffic/layers/areaAnalyticsLayers.ts","../src/traffic/util/incidentDirectionChevron.ts","../src/traffic/layers/incidentDetailsLayers.ts","../src/traffic/types/trafficCommonConfig.ts","../src/traffic/knobCatalogue.ts","../src/traffic/util/areaAnalyticsGeoJSONTiles.ts","../src/traffic/TrafficAreaAnalyticsModule.ts","../src/traffic/filters/trafficFilters.ts","../src/traffic/TrafficFlowModule.ts","../src/traffic/util/readIncidentPalette.ts","../src/traffic/TrafficIncidentDetailsModule.ts","../src/traffic/TrafficIncidentsModule.ts","../src/utils/paddedBounds.ts"],"sourcesContent":["import type { EventScope } from './eventScope';\nimport type { ModuleEvents, ShownFeaturesType } from './ModuleEvents';\nimport type { EventHandlerConfig, EventType, UserEventHandler } from './types';\nimport type { 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 * Named scopes, where a module has them, hang off this object as properties and are themselves\n * `CombinedEvents` — so a scope carries the lifecycle events that module actually scopes.\n * {@link where} narrows ad hoc instead, and returns a plain {@link UserEvents}: a filter you build\n * on the spot has no lifecycle of its own.\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 * @typeParam WHERE_SCOPE - Module-specific scope object accepted by {@link where}, in addition to a\n * feature predicate. `never` for modules that only support predicates. Unrelated to a module's\n * *named* scopes, which are properties on this object rather than an argument to it.\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, WHERE_SCOPE = never> {\n constructor(\n private readonly userEventsInstance: UserEvents<T, WHERE_SCOPE>,\n private readonly moduleEventsInstance: ModuleEvents<CFG, TShown>,\n ) {}\n\n /**\n * Narrows these events to part of what they cover — see {@link UserEvents.where}.\n *\n * @param scope See {@link EventScope} — a predicate over this module's feature type, or, where\n * the module supports one, a scope object such as `{ layerGroups }` on {@link BaseMapModule}.\n * @param config Event configuration for handlers registered through the returned instance, so\n * each scope can have its own hover cursor.\n * @returns A {@link UserEvents} covering only the narrowed set. Lifecycle events stay on the\n * module (or named scope) they belong to.\n *\n * @example\n * ```typescript\n * trafficIncidents.events.where((incident) => incident.properties.magnitude === 'major')\n * .on('click', showIncidentDetails);\n * ```\n */\n where(scope: EventScope<T, WHERE_SCOPE>, config?: EventHandlerConfig): UserEvents<T, WHERE_SCOPE> {\n return this.userEventsInstance.where(scope, config);\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: ShownFeaturesType<TShown>, 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 // The overload keeps `shown-features` off modules with no `show`; inside the class the\n // literal has to be re-asserted, since `ShownFeaturesType` is still generic here.\n return this.moduleEventsInstance.on(\n 'shown-features' as ShownFeaturesType<TShown>,\n handler as (features: TShown) => void,\n );\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 type { LayerSpecification, MapGeoJSONFeature } from 'maplibre-gl';\nimport type { LayerSpecFilter, SourceWithLayers } from './types';\n\n/**\n * What `where()` accepts: a predicate over the feature type this surface exposes, or — where the\n * module defines one — its own scope object. {@link UserEvents.where} turns either into a\n * {@link ResolvedEventScope}.\n *\n * @typeParam T - Feature type this surface exposes, which a predicate therefore receives.\n * @typeParam WHERE_SCOPE - The module's own scope object, such as {@link BaseMapEventScope}.\n * `never` on a module that only supports predicates, leaving just the predicate arm.\n *\n * @ignore\n */\nexport type EventScope<T, WHERE_SCOPE> = ((feature: T) => boolean) | WHERE_SCOPE;\n\n/**\n * A scope narrowed down to the two things the event proxy can act on.\n *\n * Every public way of expressing a scope — a feature predicate, `{ layerGroups }` on\n * {@link BaseMapModule}, a module's named scope — resolves to one of these before it reaches\n * {@link AbstractEventProxy}. Both parts are optional: an unscoped handler carries neither.\n *\n * @ignore\n */\nexport type ResolvedEventScope = {\n /**\n * Selects which of the source's layers the handler listens to. Applied fresh whenever the\n * layer set changes, so a scope keeps meaning the same thing across style changes.\n */\n layerFilter?: LayerSpecFilter;\n /**\n * Decides whether a rendered feature belongs to this scope. Receives the raw MapLibre\n * feature — a module's `mapping` is composed into this function at `where()` time, so the\n * caller's predicate still sees the module's own feature type.\n */\n featureMatches?: (feature: MapGeoJSONFeature) => boolean;\n};\n\n// ANDs two optional predicates, returning undefined only when neither is set, so an unscoped\n// registration stays genuinely unscoped rather than paying for an always-true call.\nconst andPredicates = <A extends unknown[]>(\n outer: ((...args: A) => boolean) | undefined,\n inner: ((...args: A) => boolean) | undefined,\n): ((...args: A) => boolean) | undefined => {\n if (!outer) return inner;\n if (!inner) return outer;\n\n return (...args: A) => outer(...args) && inner(...args);\n};\n\n/**\n * Combines an existing scope with a narrower one, so `where(a).where(b)` means \"a and b\".\n * Both axes intersect independently.\n *\n * Chaining is not a feature to reach for — two predicates read better as one `a(f) && b(f)`, and\n * layer plus feature narrowing is a single {@link BaseMapEventScope}. It exists because `where()`\n * returns a `UserEvents`, which has a `where()` of its own: without intersecting here, a second\n * call would silently replace the first and *widen* the scope.\n * @ignore\n */\nexport const combineEventScopes = (\n outer: ResolvedEventScope | undefined,\n inner: ResolvedEventScope | undefined,\n): ResolvedEventScope | undefined => {\n if (!outer) return inner;\n if (!inner) return outer;\n\n const layerFilter = andPredicates<[LayerSpecification]>(outer.layerFilter, inner.layerFilter);\n const featureMatches = andPredicates<[MapGeoJSONFeature]>(outer.featureMatches, inner.featureMatches);\n return layerFilter || featureMatches ? { layerFilter, featureMatches } : undefined;\n};\n\n/**\n * The layer specs a scope selects out of a source, resolved against the source's *current*\n * layers. Called again on every style change so a scope never holds stale layer IDs.\n * @ignore\n */\nexport const scopedLayerSpecs = (\n sourceWithLayers: SourceWithLayers,\n scope: ResolvedEventScope | undefined,\n): LayerSpecification[] =>\n scope?.layerFilter ? sourceWithLayers._layerSpecs.filter(scope.layerFilter) : sourceWithLayers._layerSpecs;\n\n/**\n * Names a module cannot use for a named event scope, because a scope is attached as a property\n * onto the module-wide events object and would shadow one of its methods.\n * @ignore\n */\nexport const reservedScopeNames: readonly string[] = ['on', 'off', 'where'];\n\n/**\n * Guards {@link reservedScopeNames}. SDK-authored scope names are covered by a unit test, but\n * {@link CustomGeoJSONModule} takes its scope names from the caller's own `sources`, so this\n * has to hold at runtime too.\n * @ignore\n */\nexport const assertNoReservedScopeNames = (names: string[], context: string): void => {\n const offender = names.find((name) => reservedScopeNames.includes(name));\n if (offender) {\n throw new Error(\n `${context} uses the reserved event scope name \"${offender}\". Reserved: ${reservedScopeNames.join(', ')}.`,\n );\n }\n};\n","/**\n * Every {@link FilterShowMode}, for validating one at runtime.\n *\n * @group Shared\n */\nexport const filterShowModes = ['only', 'all-except'] as const;\n\n/**\n * How a {@link ValuesFilter} reads its `values`:\n * - `only`: show only these values, hide everything else\n * - `all-except`: show everything except these values\n *\n * @group Shared\n */\nexport type FilterShowMode = (typeof filterShowModes)[number];\n\n/**\n * Shows or hides items by value — POI categories, traffic road categories, incident magnitudes.\n *\n * @typeParam T - The type of values being filtered\n *\n * @example\n * ```typescript\n * // Show only these categories\n * const includeFilter: ValuesFilter<string> = { show: 'only', values: ['RESTAURANT', 'CAFE', 'BAR'] };\n *\n * // Hide these categories\n * const excludeFilter: ValuesFilter<string> = { show: 'all-except', values: ['PARKING_GARAGE'] };\n * ```\n *\n * @group Shared\n */\nexport type ValuesFilter<T> = {\n /** Whether `values` is the set to show or the set to hide. */\n show: FilterShowMode;\n /** The values to show or hide, depending on `show`. */\n values: T[];\n};\n","import { clamp, clone, get, has, isEmpty, isObject, setWith, toPath, unset } from 'lodash-es';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport type { KnobRange } from './knobs';\nimport { filterShowModes, type ValuesFilter } from './types/filters';\n\n/**\n * A factor clamped to its published range, `fallback` when unset or not a finite number.\n * @ignore\n */\nexport const clampFactor = (factor: number | undefined, range: KnobRange, fallback = 1): number =>\n factor !== undefined && Number.isFinite(factor) ? clamp(factor, range.min, range.max) : fallback;\n\n/**\n * The value at a dotted path into T, or `never` where the path names nothing. A path that is a key\n * of T as a whole names that key, as the flat ids of `StylingFoundationsModuleConfig` do. A union is read member by\n * member, so `highlight.widthFactor` resolves through `false | IncidentHighlightConfig`. No path reaches\n * into a list.\n * @ignore\n */\nexport type ValueAt<T, PATH extends string> = T extends unknown\n ? PATH extends keyof T\n ? NonNullable<T[PATH]>\n : PATH extends `${infer HEAD}.${infer REST}`\n ? HEAD extends keyof T\n ? ValueAt<NonNullable<T[HEAD]>, REST>\n : never\n : never\n : never;\n\n// Copies each object along the path, so the nested objects of a configuration, which `getConfig`\n// copies one level deep only, are never written to.\nconst copyOf = (nested: unknown) => (isObject(nested) ? clone(nested) : undefined);\n\n/**\n * The configuration with the value at a dotted path, each object along the path copied.\n * @ignore\n */\nexport const configWith = <CONFIG extends object>(config: CONFIG, path: string, value: unknown): CONFIG =>\n setWith(clone(config), path, value, copyOf);\n\n/**\n * The configuration without the value at a dotted path, each object along the path copied and each\n * one it leaves empty dropped, so a value set and then removed leaves the configuration as it was.\n * @ignore\n */\nexport const configWithout = <CONFIG extends object>(config: CONFIG, path: string): CONFIG => {\n if (!has(config, path)) return config;\n\n const copy = configWith(config, path, undefined);\n const keys = toPath(path);\n unset(copy, keys);\n for (let end = keys.length - 1; end > 0 && isEmpty(get(copy, keys.slice(0, end))); end--) {\n unset(copy, keys.slice(0, end));\n }\n return copy;\n};\n\n/**\n * The alternatives of VALUE a knob holds: plain values, lists and `[x, y]` pairs, without the\n * objects, MapLibre expressions and callbacks the config also takes there.\n * @ignore\n */\nexport type KnobPlainValue<VALUE> = VALUE extends ExpressionSpecification\n ? never\n : VALUE extends readonly unknown[]\n ? VALUE\n : VALUE extends object\n ? never\n : VALUE;\n\n/**\n * What a catalogue says of a knob whose config value is VALUE: the kind follows from the value, a\n * numeric knob carries its range and an enum its options. The alternatives\n * {@link KnobPlainValue} leaves out stay with the config.\n * @ignore\n */\nexport type KnobDefinition<VALUE, PLAIN = KnobPlainValue<VALUE>> = [PLAIN] extends [never]\n ? never\n : { description: string; default?: Readonly<PLAIN> } & ([PLAIN] extends [boolean]\n ? { kind: 'toggle' }\n : [PLAIN] extends [number]\n ? { kind: 'factor' | 'number'; range: KnobRange }\n : [PLAIN] extends [readonly [number, number]]\n ? { kind: 'offset'; range: KnobRange }\n : [PLAIN] extends [readonly (infer ITEM extends string)[]]\n ? string extends ITEM\n ? { kind: 'colors'; options?: readonly string[] }\n : { kind: 'enums'; options: readonly ITEM[] }\n : string extends PLAIN\n ? { kind: 'color' | 'image' | 'text' }\n : [PLAIN] extends [string]\n ? { kind: 'enum'; options: readonly PLAIN[] }\n : never);\n\n/**\n * Every member of OPTION, so a value added to the type cannot be missing from a knob's options.\n * @ignore\n */\nexport const everyOf =\n /* @__NO_SIDE_EFFECTS__ */\n <OPTION extends string>() =>\n /* @__NO_SIDE_EFFECTS__ */\n <const OPTIONS extends readonly OPTION[]>(\n options: OPTIONS & ([Exclude<OPTION, OPTIONS[number]>] extends [never] ? unknown : never),\n ): OPTIONS =>\n options;\n\n/**\n * The two knob ids of the {@link ValuesFilter} at PATH.\n * @ignore\n */\nexport type ValuesFilterKnobId<PATH extends string> = `${PATH}.${keyof ValuesFilter<unknown>}`;\n\n/**\n * The definitions of the `show` and `values` knobs of the {@link ValuesFilter} at `path`, whose\n * values are `noun`; `valuesNote` closes the `values` description.\n * @ignore\n */\nexport const valuesFilterKnobs =\n /* @__NO_SIDE_EFFECTS__ */\n <const OPTIONS extends readonly string[]>(path: string, noun: string, options: OPTIONS, valuesNote = '') =>\n ({\n show: {\n kind: 'enum',\n description: `\\`only\\` keeps just the \\`${path}.values\\` ${noun}, \\`all-except\\` every one but them; the filter applies once both are set.`,\n options: filterShowModes,\n },\n values: {\n kind: 'enums',\n description: `The ${noun} \\`${path}.show\\` keeps alone or drops.${valuesNote}`,\n options,\n },\n }) as const;\n\n/**\n * A catalogue's definitions in their declared order, each with its id.\n * @ignore\n */\nexport const withIds = <ID extends string, DEFINITION extends object>(definitions: Record<ID, DEFINITION>) =>\n (Object.keys(definitions) as ID[]).map((id) => ({ id, ...definitions[id] }));\n\n/**\n * A definition's `default`, or nothing where no one value holds.\n * @ignore\n */\nexport const defaultOf = <VALUE>(value: VALUE | undefined) => (value === undefined ? {} : { default: value });\n\n/**\n * The range of a factor knob over the loaded style's own sizes, as TomTom's style editor offers it.\n * @ignore\n */\nexport const FACTOR_RANGE = { min: 0.5, max: 1.5, step: 0.1, bounds: 'hard' } as const satisfies KnobRange;\n\n/**\n * The range of a factor knob over a traffic line's width, which reads well wider than a road's.\n * @ignore\n */\nexport const WIDE_FACTOR_RANGE = { ...FACTOR_RANGE, max: 2 } as const satisfies KnobRange;\n\n/**\n * The range of an opacity knob.\n * @ignore\n */\nexport const OPACITY_RANGE: KnobRange = { min: 0, max: 1, step: 0.05, bounds: 'hard' };\n\n/**\n * The range of a label size knob, in pixels.\n * @ignore\n */\nexport const LABEL_SIZE_RANGE: KnobRange = { min: 6, max: 48, step: 1, bounds: 'hard-min' };\n\n/**\n * The range of a label halo width knob, in pixels.\n * @ignore\n */\nexport const LABEL_HALO_WIDTH_RANGE: KnobRange = { min: 0, max: 10, step: 0.5, bounds: 'hard-min' };\n\n/**\n * The range of a zoom-level knob.\n * @ignore\n */\nexport const ZOOM_RANGE: KnobRange = { min: 0, max: 22, step: 1, bounds: 'hard' };\n","import type { ExpressionFilterSpecification, ExpressionSpecification, FilterSpecification } from 'maplibre-gl';\nimport type { FilterShowMode, ValuesFilter } from './types';\n\n// Recognizes MapLibre's expression filter syntax, so that a style's own filter can be\n// converted before the SDK merges its filters into it. Ported from MapLibre's\n// `isExpressionFilter`, which the style spec keeps internal.\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 filterItem of filter.slice(1)) {\n if (!isExpressionFilter(filterItem as FilterSpecification) && typeof filterItem !== 'boolean') {\n return false;\n }\n }\n return true;\n\n default:\n return true;\n }\n};\n\ntype LegacyValue = string | number | boolean | null;\n\n// A legacy filter names a feature property directly. An expression reads it with `get`,\n// except for the two reserved keys: `$type` is the geometry type and `$id` the feature id.\nconst propertyAccessor = (property: string): ExpressionSpecification => {\n if (property === '$type') {\n return ['geometry-type'];\n }\n if (property === '$id') {\n return ['id'];\n }\n\n return ['get', property];\n};\n\n// Neither reserved key is a property, so neither can be looked up with `has`: every feature\n// has a geometry type, and a feature id needs its own presence test.\nconst convertHas = (property: string): ExpressionFilterSpecification => {\n if (property === '$type') {\n return true;\n }\n if (property === '$id') {\n return ['!=', ['id'], null];\n }\n\n return ['has', property];\n};\n\n// An ordering comparison evaluates to null, and logs a runtime type error on every feature,\n// when the property is absent or holds another type. The legacy filter was simply false\n// there, so the comparison is guarded by the type the style compares against.\nconst convertOrdering = (\n operator: '>' | '>=' | '<' | '<=',\n property: string,\n value: LegacyValue,\n): ExpressionFilterSpecification => [\n 'all',\n ['==', ['typeof', propertyAccessor(property)], typeof value],\n [operator, propertyAccessor(property), value] as ExpressionSpecification,\n];\n\n// `get` yields null for a property that is absent as well as one that holds null, but a legacy\n// comparison against null only matched a property that was actually present. `$id` has no such\n// ambiguity, so it compares directly.\nconst convertNullComparison = (operator: '==' | '!=', property: string): ExpressionFilterSpecification | undefined => {\n if (property === '$id' || property === '$type') {\n return undefined;\n }\n if (operator === '==') {\n return ['all', ['has', property], ['==', ['get', property], null]];\n }\n\n return ['any', ['!', ['has', property]], ['!=', ['get', property], null]];\n};\n\nconst convertIn = (property: string, values: LegacyValue[]): ExpressionFilterSpecification => {\n if (!values.length) {\n return false;\n }\n\n return ['in', propertyAccessor(property), ['literal', values]];\n};\n\n// Converts one legacy filter node. The node is read as a plain array because the legacy and\n// expression tuple unions overlap, which stops TypeScript from narrowing on the operator.\n// A combining node counts as legacy as soon as one of its children is, so its other children can\n// reach here already expressed, and one of them can be a boolean rather than a node at all. Each\n// child therefore goes back through `toExpressionFilter` instead of straight into this function.\nconst convertLegacyFilter = (filter: readonly unknown[]): ExpressionFilterSpecification => {\n const operator = filter[0];\n // MapLibre treats a legacy filter without arguments as a constant.\n if (filter.length <= 1) {\n return operator !== 'any';\n }\n\n const property = filter[1] as string;\n const values = filter.slice(2) as LegacyValue[];\n const subFilters = filter.slice(1) as readonly FilterSpecification[];\n\n switch (operator) {\n case 'has':\n return convertHas(property);\n case '!has':\n return ['!', convertHas(property)];\n case '==':\n case '!=':\n return (\n (values[0] === null ? convertNullComparison(operator, property) : undefined) ??\n ([operator, propertyAccessor(property), values[0]] as ExpressionSpecification)\n );\n case '>':\n case '>=':\n case '<':\n case '<=':\n return convertOrdering(operator, property, values[0]);\n case 'in':\n return convertIn(property, values);\n case '!in':\n return ['!', convertIn(property, values)];\n case 'all':\n return ['all', ...subFilters.map((subFilter) => toExpressionFilter(subFilter))];\n case 'any':\n return ['any', ...subFilters.map((subFilter) => toExpressionFilter(subFilter))];\n case 'none':\n return ['!', ['any', ...subFilters.map((subFilter) => toExpressionFilter(subFilter))]];\n default:\n return true;\n }\n};\n\n/**\n * Converts a filter to MapLibre's expression syntax, leaving one that already uses it untouched.\n *\n * MapLibre does not support a filter that mixes the deprecated syntax with the expression syntax,\n * so a style layer's own filter has to be converted before an SDK filter is merged into it. What\n * such a mix costs depends on where the deprecated node sits: `featureFilter` converts a filter\n * whose root is deprecated node by node, but one whose root is an expression only earns a\n * deprecation warning, and then fails to compile when the deprecated node reaches\n * `createExpression`.\n *\n * A filter that mixes the two therefore has to be converted branch by branch, which is what\n * MapLibre's own converter does, so this one recurses through here rather than through\n * `convertLegacyFilter`.\n *\n * @ignore\n */\nexport const toExpressionFilter = (filter: FilterSpecification): ExpressionFilterSpecification =>\n isExpressionFilter(filter) ? filter : convertLegacyFilter(filter as readonly unknown[]);\n\n/**\n * The given filters combined under `'any'` (OR) or `'all'` (AND), or `null` when there are none. A\n * lone filter stands on its own, so the expression never wraps what it does not combine.\n *\n * @ignore\n */\nexport const getMergedFilter = (\n combinator: 'any' | 'all',\n filters: ExpressionFilterSpecification[],\n): ExpressionFilterSpecification | null => {\n if (!filters?.length) {\n return null;\n }\n if (filters.length === 1) {\n return filters[0];\n }\n\n return [combinator, ...filters] as ExpressionSpecification;\n};\n\n/**\n * Whether a {@link ValuesFilter} has both its halves. A knob writes `show` and `values` one at a\n * time, so a filter missing either filters nothing until the other arrives.\n * @ignore\n */\nexport const isCompleteValuesFilter = <T>(filter: Partial<ValuesFilter<T>> | undefined): filter is ValuesFilter<T> =>\n filter?.show !== undefined && filter.values !== undefined;\n\n/**\n * @ignore\n */\nexport const buildMappedValuesFilter = <T>(\n propertyName: string,\n showMode: FilterShowMode,\n values: T[],\n): ExpressionFilterSpecification => {\n if (values.length === 1) {\n const comparator = showMode === 'only' ? '==' : '!=';\n return [comparator, ['get', propertyName], values[0]] as ExpressionSpecification;\n }\n\n const membership = ['in', ['get', propertyName], ['literal', values]] as ExpressionSpecification;\n return showMode === 'only' ? membership : ['!', membership];\n};\n\n/**\n * @ignore\n */\nexport const buildValuesFilter = <T>(\n propertyName: string,\n filter: ValuesFilter<T>,\n valuesMapping?: (value: T) => unknown,\n): ExpressionFilterSpecification =>\n buildMappedValuesFilter(\n propertyName,\n filter.show,\n valuesMapping ? filter.values.map(valuesMapping) : filter.values,\n );\n","/**\n * What `byMap` holds for this map, created on first use.\n * @ignore\n */\nexport const ofMap = <MAP extends object, VALUE>(byMap: WeakMap<MAP, VALUE>, map: MAP, create: () => VALUE): VALUE => {\n let value = byMap.get(map);\n if (!value) {\n value = create();\n byMap.set(map, value);\n }\n return value;\n};\n","import type { Map as MapLibreMap } from 'maplibre-gl';\nimport type { TomTomMap } from '../../TomTomMap';\n\n/**\n * The single owner of one property of a style layer that several modules write. Each contributor\n * registers what it wants under a key of its own, and the composer writes the one value that\n * carries all of it, next to what the style itself gave the layer.\n *\n * The snapshot and the contributions belong to the style they were taken from, so both are dropped\n * when a new style arrives; every module re-registers what it still wants as it restores itself.\n *\n * @ignore\n */\nexport abstract class AbstractLayerComposer<CONTRIBUTIONS> {\n protected readonly mapLibreMap: MapLibreMap;\n protected readonly byLayer = new Map<string, CONTRIBUTIONS>();\n\n protected constructor(tomtomMap: TomTomMap) {\n this.mapLibreMap = tomtomMap.mapLibreMap;\n tomtomMap.addStyleChangeHandler({\n onStyleAboutToChange: () => this.byLayer.clear(),\n // Ahead of every module's own handler, so the clear can never wipe a contribution a\n // module has already made for the style that is arriving.\n priority: Number.NEGATIVE_INFINITY,\n });\n }\n}\n\n/**\n * Records a contributor's say in one of a layer's contribution maps, or drops it when the\n * contributor withdraws it.\n * @ignore\n */\nexport const setOrDelete = <VALUE>(\n entries: Map<string, VALUE>,\n contributor: string,\n value: VALUE | undefined,\n): void => {\n if (value === undefined) {\n entries.delete(contributor);\n return;\n }\n entries.set(contributor, value);\n};\n","import type { ExpressionFilterSpecification, FilterSpecification } from 'maplibre-gl';\nimport type { TomTomMap } from '../../TomTomMap';\nimport { toExpressionFilter } from '../mapLibreFilterUtils';\nimport { ofMap } from '../ofMap';\nimport { AbstractLayerComposer, setOrDelete } from './abstractLayerComposer';\n\n/**\n * A rewrite of the filter a style gave one of its layers: the whole filter in, the whole filter out.\n * @ignore\n */\nexport type LayerFilterTransform = (filter: unknown) => unknown;\n\n// Everything that has a say in one layer's filter, next to the filter the style itself gave it.\ntype LayerFilterContributions = {\n styleFilter: unknown;\n transforms: Map<string, LayerFilterTransform>;\n clauses: Map<string, ExpressionFilterSpecification>;\n};\n\nconst composersByMap = new WeakMap<TomTomMap, LayerFilterComposer>();\n\n// The transformed filter, narrowed by every clause. The base is converted to expression syntax\n// before the clauses join it: MapLibre rejects a filter that mixes the deprecated syntax with the\n// expression syntax, and the style's own filter may still use the deprecated one.\nconst composeFilter = (base: unknown, clauses: ExpressionFilterSpecification[]): FilterSpecification | undefined => {\n if (!clauses.length) return base as FilterSpecification | undefined;\n\n const terms =\n base === undefined || base === null ? clauses : [...clauses, toExpressionFilter(base as FilterSpecification)];\n return (terms.length === 1 ? terms[0] : ['all', ...terms]) as FilterSpecification;\n};\n\n/**\n * The single owner of a style layer's `filter`.\n *\n * Several modules narrow the same layer: the {@link POIsModule} category filter adds a clause\n * to the POI layer, and the styling knob `pois.zoomShift` rewrites the density term inside that same\n * layer's filter. Each rebuilding the filter from its own snapshot of the style means whichever\n * writes last drops the other's work, with nothing to show that it happened.\n *\n * So no module writes a style layer's filter itself. Each registers what it wants under a key of its\n * own, and the composer writes the one filter that carries all of it: the filter the style shipped,\n * run through every **transform**, then narrowed by every **clause**.\n *\n * @ignore\n */\nexport class LayerFilterComposer extends AbstractLayerComposer<LayerFilterContributions> {\n private constructor(tomtomMap: TomTomMap) {\n super(tomtomMap);\n }\n\n /** The composer of this map, created on first use. */\n static for(tomtomMap: TomTomMap): LayerFilterComposer {\n return ofMap(composersByMap, tomtomMap, () => new LayerFilterComposer(tomtomMap));\n }\n\n /**\n * Registers a rewrite of the filter the style gave this layer, replacing the one this\n * contributor registered before. Pass `undefined` to withdraw it and leave the filter to\n * the other contributors.\n *\n * @param layerId - The style layer whose filter is rewritten.\n * @param contributor - A key unique to the contributor, e.g. `'styling.zoomShift'`.\n * @param transform - Receives the style's own filter, returns the filter to use instead.\n */\n setTransform(layerId: string, contributor: string, transform: LayerFilterTransform | undefined): void {\n this.contribute(layerId, ({ transforms }) => setOrDelete(transforms, contributor, transform));\n }\n\n /**\n * Registers a clause that narrows this layer further, `and`-ed with the style's own filter and\n * with every other contributor's clause. Pass `undefined` to withdraw it.\n *\n * @param layerId - The style layer to narrow.\n * @param contributor - A key unique to the contributor, e.g. `'pois.categories'`.\n * @param clause - The clause to add, in expression syntax.\n */\n setClause(layerId: string, contributor: string, clause: ExpressionFilterSpecification | undefined): void {\n this.contribute(layerId, ({ clauses }) => setOrDelete(clauses, contributor, clause));\n }\n\n // Records one contribution and writes the filter it composes into. A layer the loaded style does\n // not have is left alone: there is no filter to snapshot, and MapLibre has nothing to set it on.\n private contribute(layerId: string, register: (contributions: LayerFilterContributions) => void): void {\n if (!this.mapLibreMap.getLayer(layerId)) return;\n\n let contributions = this.byLayer.get(layerId);\n if (!contributions) {\n contributions = {\n styleFilter: this.mapLibreMap.getFilter(layerId),\n transforms: new Map(),\n clauses: new Map(),\n };\n this.byLayer.set(layerId, contributions);\n }\n register(contributions);\n this.apply(layerId, contributions);\n }\n\n private apply(layerId: string, contributions: LayerFilterContributions): void {\n const base = [...contributions.transforms.values()].reduce(\n (filter, transform) => transform(filter),\n contributions.styleFilter,\n );\n const filter = composeFilter(base, [...contributions.clauses.values()]);\n this.mapLibreMap.setFilter(layerId, filter ?? null, { validate: false });\n }\n}\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 */\n/**\n * `'shown-features'`, except on a module with no `show` — where `TShownFeatures` is `never` and\n * this collapses to `never`, making the overload uncallable. Without it a handler could be\n * registered for an event the module can never emit.\n *\n * @group Events\n */\nexport type ShownFeaturesType<TShownFeatures> = [TShownFeatures] extends [never] ? never : 'shown-features';\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: ShownFeaturesType<TShownFeatures>, 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 { GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport type { MapOptions, StyleSpecification } from 'maplibre-gl';\nimport type { LightDark } from '../../shared/types/style';\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 => standardStyleIDs.some((styleId) => styleId === id);\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 style parts are included.\n *\n * @example\n * ```typescript\n * // Standard style with every style part\n * const style: StandardStyle = {\n * id: 'standardLight'\n * };\n *\n * // Leave the traffic parts out\n * const styleNoTraffic: StandardStyle = {\n * id: 'standardLight',\n * include: ['terrain']\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 * Which {@link StylePart}s to load. Leave it out for every part; `[]` loads none.\n *\n * @default All style parts\n */\n include?: StylePart[];\n};\n\n/**\n * Where a custom style comes from: a URL to fetch it from, or the specification itself. Exactly one.\n *\n * @example\n * ```typescript\n * const fromUrl: CustomStyleSource = { url: 'https://example.com/my-custom-style.json' };\n *\n * const fromJson: CustomStyleSource = {\n * json: { version: 8, sources: { ... }, layers: [ ... ] },\n * };\n * ```\n *\n * @group Map Style\n */\nexport type CustomStyleSource =\n | {\n /**\n * URL to a MapLibre style JSON, without an API key — the SDK adds the configured one.\n */\n url: string;\n json?: never;\n }\n | {\n /**\n * The complete style, as a\n * [MapLibre Style Specification](https://maplibre.org/maplibre-style-spec/) object.\n */\n json: StyleSpecification;\n url?: never;\n };\n\n/**\n * Configuration for a custom map style.\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 = CustomStyleSource & {\n /**\n * Whether the style draws a light or a dark map.\n *\n * Overlays that are drawn on top of the style — places, routes, waypoint labels, the pin\n * sprite — pick their colours from this, so that their text stays legible on the canvas\n * underneath. {@link TomTomMap.styleLightDarkTheme} reports the value in effect.\n *\n * Leave it out and the SDK reads the theme off the loaded style, from the colour its\n * `background` layer paints the canvas with. Set it when that guess is wrong or when you\n * would rather not depend on a guess: a style whose canvas is hidden behind full-coverage\n * imagery, or one whose background colour does not represent what the map ends up looking\n * like.\n *\n * @default Read from the background colour of the loaded style, falling back to `'light'`\n *\n * @example\n * ```typescript\n * style: { type: 'custom', url: 'https://example.com/midnight.json', lightDarkTheme: 'dark' }\n * ```\n */\n lightDarkTheme?: LightDark;\n};\n\n/**\n * Every {@link StylePart}.\n *\n * @group Map Style\n */\nexport const styleParts = ['trafficIncidents', 'trafficFlow', 'terrain'] as const;\n\n/**\n * Optional part of a standard style, included or left out with {@link StandardStyle.include}.\n *\n * - `trafficIncidents`: real-time traffic incidents\n * - `trafficFlow`: real-time traffic flow\n * - `terrain`: elevation data, drawn by {@link TerrainModule} as hillshade and 3D terrain\n *\n * @group Map Style\n */\nexport type StylePart = (typeof styleParts)[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', 'terrain']\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 * How user events are detected and timed, and which cursors they show, for every module.\n * Change it later with {@link TomTomMap.updateEventsConfig}.\n *\n * @example\n * ```typescript\n * events: { paddingBoxPx: 10, longHoverDelayOnStillMapMS: 500, hoverGracePeriodMS: 1000 }\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 * 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","import type { StylePart } 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 customStyleWithoutSource = (): Error =>\n new Error(\n \"A custom map style takes exactly one of 'url' or 'json', and this one carries neither. \" +\n 'Give the URL the style is served from, or the style specification itself.',\n );\n\n/**\n * @ignore\n */\nexport const cannotAddStylePartToCustomStyle = (stylePart: StylePart): Error =>\n new Error(\n `The custom map style has no '${stylePart}' part and the SDK cannot add one to it. ` +\n 'Style-owned modules work on a custom style only when it already ships the matching source ' +\n '(e.g. built with that part enabled) — or switch to a standard style.',\n );\n","import type { StyleSpecification } from 'maplibre-gl';\nimport { customStyleWithoutSource } from '../shared/errorMessages';\nimport type { InternalTomTomMapParams, StandardStyle, StandardStyleID, StyleInput, StylePart } from './types/mapInit';\nimport { styleParts } from './types/mapInit';\n\nexport const DEFAULT_STANDARD_STYLE_ID: StandardStyleID = 'standardLight';\n\n/**\n * The version of the TomTom Orbis map styles the SDK loads. Everything in the SDK that relies on the\n * structure of a standard style — the base-map layer groups, the styling knobs — is curated and\n * verified against this version, so a bump here is a bump of those tables too.\n *\n * The style-fixture refresh script reads this declaration from source rather than restating it.\n */\nexport const STYLE_VERSION = '0.6.0-0';\n\n// The style URL query parameter each style part is requested with.\nconst stylePartURLParams: Record<StylePart, string> = {\n trafficIncidents: 'trafficIncidents',\n trafficFlow: 'trafficFlow',\n terrain: 'hillshade',\n};\n\nconst lightStylePartValues: Record<StylePart, string> = {\n trafficIncidents: 'incidents_light',\n trafficFlow: 'flow_relative-light',\n terrain: 'hillshade_light',\n};\n\nconst darkStylePartValues: Record<StylePart, string> = {\n trafficIncidents: 'incidents_dark',\n trafficFlow: 'flow_relative-dark',\n terrain: 'hillshade_dark',\n};\n\nconst standardStylePartValues: Record<StandardStyleID, Record<StylePart, string>> = {\n standardLight: lightStylePartValues,\n standardDark: darkStylePartValues,\n drivingLight: lightStylePartValues,\n drivingDark: darkStylePartValues,\n monoLight: { ...lightStylePartValues, terrain: 'hillshade_mono-light' },\n monoDark: { ...darkStylePartValues, terrain: 'hillshade_mono-dark' },\n satellite: { ...lightStylePartValues, terrain: 'hillshade_satellite' },\n};\n\n/**\n * The Orbis flavour each standard style loads: the `map` of the style URL, and the name of its theme.\n * @ignore\n */\nexport const 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\n/**\n * Sets the `key` query parameter to the API key, unless it is empty.\n * * Proxy deployments leave the API key empty and let the proxy inject the real one server-side:\n * an empty `key=` would reach the proxy instead.\n * @ignore\n */\nexport const setApiKeyParameter = (searchParameters: URLSearchParams, apiKey: string): void => {\n if (apiKey) {\n searchParameters.set('key', apiKey);\n }\n};\n\nconst buildStandardStyleUrl = (standardStyle: StandardStyle, baseUrl: string, apiKey: string): string => {\n const standardStyleID = standardStyle.id ?? DEFAULT_STANDARD_STYLE_ID;\n\n const styleURL = new URL(`${baseUrl}/maps/orbis/assets/styles/${STYLE_VERSION}/style.json`);\n // Param order is asserted by tests and matches the order historically\n // baked into URL_PREFIX: apiVersion, key, map, then style parts.\n styleURL.searchParams.set('apiVersion', '1');\n setApiKeyParameter(styleURL.searchParams, apiKey);\n styleURL.searchParams.set('map', mapSuffixes[standardStyleID]);\n\n for (const part of standardStyle.include ?? styleParts) {\n styleURL.searchParams.append(stylePartURLParams[part], standardStylePartValues[standardStyleID][part]);\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 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 } else {\n setApiKeyParameter(url.searchParams, apiKey);\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') {\n if (style.url !== undefined) {\n return withApiKey(style.url, apiKey);\n }\n\n // The union rejects a custom style with neither field, but an untyped caller reaches here.\n if (style.json === undefined) {\n throw customStyleWithoutSource();\n }\n\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.include,\n };\n }\n }\n return style;\n};\n","/**\n * Rewrites of MapLibre style values that need no knowledge of the style they run on: they wrap or\n * scale what the style already does, so they survive style-version bumps and work on custom styles.\n *\n * The one rule they all respect: a `[\"zoom\"]` expression may only be the input of a top-level\n * `interpolate`/`step`. A value with `zoom` anywhere else cannot be wrapped in `[\"*\", factor, …]`,\n * so the transform returns `undefined` and the caller keeps the style's own value.\n *\n * @module\n * @ignore\n */\n\n// Scaled sizes land on 1.2000000000000002 as readily as 1.2 in binary floating point; thousandths\n// are already finer than a pixel fraction the renderer can draw.\nconst roundScaled = (value: number): number => Math.round(value * 1000) / 1000;\n\nconst mentionsZoom = (value: unknown): boolean => JSON.stringify(value).includes('\"zoom\"');\n\n/**\n * A pre-expression style function: `{ stops: [[input, output], …] }`, still used by compiled styles\n * for zoom ramps.\n * @ignore\n */\nexport type LegacyFunction = { stops: [unknown, unknown][]; base?: number; property?: string };\n\n/** @ignore */\nexport const isLegacyFunction = (value: unknown): value is LegacyFunction =>\n typeof value === 'object' &&\n value !== null &&\n !Array.isArray(value) &&\n Array.isArray((value as LegacyFunction).stops);\n\n/**\n * The style's own numeric value multiplied by `factor`, keeping its shape:\n * - a constant scales directly;\n * - an `interpolate`/`step` keeps its zoom ramp with every stop's output scaled (outputs recurse,\n * so a `match` inside a stop is wrapped as a whole) — a motorway stays wider than a footpath and\n * the whole hierarchy thickens in proportion;\n * - a legacy `{ stops: [...] }` function scales its outputs the same way;\n * - any other zoom-free expression is wrapped in a top-level `[\"*\", factor, value]`.\n *\n * Returns `undefined` when the value cannot be scaled (a string, or an expression that still holds\n * `zoom` where a wrapper would be illegal), so the caller can leave the style's value untouched.\n * @ignore\n */\nexport const scaleNumericValue = (value: unknown, factor: number): unknown => {\n if (typeof value === 'number') return roundScaled(value * factor);\n\n if (isLegacyFunction(value)) return scaleLegacyStops(value, factor);\n\n if (!Array.isArray(value)) return undefined;\n\n if (value[0] === 'interpolate' || value[0] === 'step') return scaleRampOutputs(value, factor);\n\n return mentionsZoom(value) ? undefined : ['*', factor, value];\n};\n\n// A stop whose output cannot be scaled takes the whole ramp with it: a partly scaled ramp would\n// break the hierarchy the style draws, so the caller keeps the style's value instead.\nconst scaleLegacyStops = (value: LegacyFunction, factor: number): unknown => {\n const stops: [unknown, unknown][] = [];\n for (const [input, output] of value.stops) {\n const scaled = scaleNumericValue(output, factor);\n if (scaled === undefined) return undefined;\n\n stops.push([input, scaled]);\n }\n return { ...value, stops };\n};\n\n// Outputs sit every other slot: from index 4 for interpolate\n// ([interpolate, curve, input, stop, out, …]), from 2 for step ([step, input, out, stop, out, …]).\nconst scaleRampOutputs = (value: unknown[], factor: number): unknown => {\n const scaled = [...value];\n for (let index = value[0] === 'interpolate' ? 4 : 2; index < scaled.length; index += 2) {\n const output = scaleNumericValue(scaled[index], factor);\n if (output === undefined) return undefined;\n\n scaled[index] = output;\n }\n return scaled;\n};\n\nconst branchOn = (condition: unknown, value: unknown, factor: number, max: number | undefined): unknown => {\n const scaled = scaleNumericValue(value, factor);\n if (scaled === undefined) return undefined;\n\n if (max === undefined) return ['case', condition, scaled, value];\n\n return ['case', condition, typeof scaled === 'number' ? Math.min(max, scaled) : ['min', max, scaled], value];\n};\n\n/**\n * The numeric value drawn `factor` times larger, at most `max`, on the features `condition` holds\n * for, and as it is on the rest. A zoom ramp keeps its single ramp and branches in each stop's\n * output; a factor of `1`, or a value that cannot be scaled (see {@link scaleNumericValue}), returns\n * the value unchanged.\n * @ignore\n */\nexport const scaledWhen = <T>(condition: unknown, value: T, factor: number, max?: number): T => {\n if (factor === 1) return value;\n\n if (!Array.isArray(value) || (value[0] !== 'interpolate' && value[0] !== 'step')) {\n return (branchOn(condition, value, factor, max) ?? value) as T;\n }\n\n const branched = [...value];\n for (let index = value[0] === 'interpolate' ? 4 : 2; index < branched.length; index += 2) {\n const output = branchOn(condition, branched[index], factor, max);\n if (output === undefined) return value;\n\n branched[index] = output;\n }\n return branched as T;\n};\n\n/**\n * Shifts the zoom offset the TomTom styles use to stagger feature density: a filter term of the\n * form `[\"-\", [\"zoom\"], offset]` compared against a feature's `display_class`. Adding to the offset\n * makes features appear later (a sparser map), subtracting makes them appear earlier.\n *\n * Returns the rewritten filter and whether any offset was found; the tree is copied, never mutated.\n * @ignore\n */\nexport const shiftZoomOffset = (filter: unknown, shift: number): { filter: unknown; patched: boolean } => {\n let patched = false;\n const walk = (node: unknown): unknown => {\n if (!Array.isArray(node)) return node;\n if (\n node[0] === '-' &&\n node.length === 3 &&\n Array.isArray(node[1]) &&\n node[1][0] === 'zoom' &&\n typeof node[2] === 'number'\n ) {\n patched = true;\n return ['-', ['zoom'], node[2] + shift];\n }\n return node.map(walk);\n };\n return { filter: walk(filter), patched };\n};\n","import type { Color } from 'culori/fn';\nimport {\n clampRgb,\n differenceHueNaive,\n formatHex,\n formatHsl,\n interpolate,\n modeHsl,\n modeLrgb,\n modeRgb,\n parse,\n useMode as registerColorSpace,\n round,\n wcagLuminance,\n} from 'culori/fn';\nimport { clamp } from 'lodash-es';\n\n// `culori/fn` parses, converts and measures only in registered colour spaces: sRGB, HSL, and linear\n// sRGB for luminance. A module calling those culori functions must import from this one too, or a\n// bundler can keep the call and drop the registration.\nconst toRgb = registerColorSpace(modeRgb);\nconst toHslSpace = registerColorSpace(modeHsl);\nregisterColorSpace(modeLrgb);\n\n/**\n * A number rounded to hundredths: the whole percents the map styles write saturation and lightness\n * in, and the precision the themes read luminance at.\n * @ignore\n */\nexport const toHundredths = round(2);\n\n// The syntaxes a map style can paint. culori's `parse` also reads `color(srgb …)`, and every colour\n// space an app registers on the shared culori copy, none of which MapLibre renders.\nconst STYLE_COLOR_SYNTAX = /^(#|rgba?\\(|hsla?\\(|[a-z]+$)/;\n\n/**\n * A CSS colour read the way the SDK reads style values and caller input — trimmed, case-insensitive,\n * clamped to the sRGB gamut — or `undefined` for a string that is not one a map style can paint.\n * @ignore\n */\nexport const parseColor = (cssColor: string): Color | undefined => {\n const input = cssColor.trim().toLowerCase();\n const color = STYLE_COLOR_SYNTAX.test(input) ? parse(input) : undefined;\n return color && clampRgb(color);\n};\n\n/**\n * Red, green, blue and alpha of a colour, each 0–1.\n * @ignore\n */\nexport type RGBAColor = [number, number, number, number];\n\n/**\n * {@link parseColor} as an {@link RGBAColor} tuple.\n * @ignore\n */\nexport const parseCssColor = (cssColor: string): RGBAColor | undefined => {\n const color = parseColor(cssColor);\n if (!color) return undefined;\n\n const { r, g, b, alpha = 1 } = toRgb(color);\n return [r, g, b, alpha];\n};\n\n/**\n * A colour in HSL: hue in degrees (none for a grey), saturation, lightness and alpha 0–1 — the shape\n * of culori's `Hsl`, so culori's functions take it.\n * @ignore\n */\nexport type HslColor = { mode: 'hsl'; h?: number; s: number; l: number; alpha?: number };\n\n/**\n * The HSL of a CSS colour, or `undefined` when it cannot be parsed. Read through sRGB, so an\n * `hsl()` literal comes back canonical — white has no saturation, a grey no hue.\n * @ignore\n */\nexport const toHsl = (cssColor: string): HslColor | undefined => {\n const color = parseColor(cssColor);\n return color && toHslSpace(toRgb(color));\n};\n\n/**\n * Whether a style value is a CSS colour outright, rather than an expression that produces one.\n * @ignore\n */\nexport const isColorLiteral = (value: unknown): value is string =>\n typeof value === 'string' && parseColor(value) !== undefined;\n\n/**\n * Whether a style value is a colour literal that paints something. A fully transparent one is how a\n * style hides a feature, so it is no shade of any colour and nothing to re-derive.\n * @ignore\n */\nexport const isOpaqueColorLiteral = (value: unknown): value is string => {\n const color = typeof value === 'string' ? parseColor(value) : undefined;\n return color !== undefined && (color.alpha ?? 1) > 0;\n};\n\n/**\n * Formats an HSL colour as a CSS `hsl()`/`hsla()` string, rounded to what the map styles themselves\n * use: whole degrees and percents.\n * @ignore\n */\nexport const formatStyleHsl = (hsl: Omit<HslColor, 'mode'>): string => formatHsl(roundStyleHsl(hsl));\n\n/**\n * An HSL colour rounded to what the map styles use: whole degrees, and whole percents of saturation\n * and lightness.\n * @ignore\n */\nexport const roundStyleHsl = ({ h = 0, s, l, alpha }: Omit<HslColor, 'mode'>): HslColor => ({\n mode: 'hsl',\n h: ((Math.round(h) % 360) + 360) % 360,\n s: toHundredths(s),\n l: toHundredths(l),\n alpha,\n});\n\n/**\n * A colour without the opacity a layer drew it at: a halo at 80% white is white.\n * @ignore\n */\nexport const opaqueColor = (cssColor: string): string => {\n const hsl = toHsl(cssColor);\n return hsl && (hsl.alpha ?? 1) < 1 ? formatStyleHsl({ ...hsl, alpha: undefined }) : cssColor;\n};\n\n/**\n * A channel clamped to 0–1: saturation, lightness or alpha.\n * @ignore\n */\nexport const clampUnit = (value: number): number => clamp(value, 0, 1);\n\n/**\n * The HSL offset that turns `fromColor` into `toColor`: how the map style derived one shade (an\n * outline, a tunnel variant) from another. Applying it to a new base colour with {@link shiftHsl}\n * re-derives the shade, so a recolour keeps the style's own shade relationships.\n * @ignore\n */\nexport type HslShift = Required<Omit<HslColor, 'mode'>>;\n\n/**\n * @ignore\n */\nexport const hslShiftBetween = (fromColor: string, toColor: string): HslShift | undefined => {\n const from = toHsl(fromColor);\n const to = toHsl(toColor);\n if (!from || !to) {\n return undefined;\n }\n // A hue shift is meaningless when either colour is grey; keep the base hue then.\n const h = from.s < 0.01 || to.s < 0.01 ? 0 : (to.h ?? 0) - (from.h ?? 0);\n return { h, s: to.s - from.s, l: to.l - from.l, alpha: (to.alpha ?? 1) - (from.alpha ?? 1) };\n};\n\n/**\n * Applies an {@link HslShift} to a CSS color, clamping saturation, lightness and alpha to their ranges.\n * A grey keeps its saturation, so its shades are greys rather than tints of the red at hue 0.\n * @ignore\n */\nexport const shiftHsl = (cssColor: string, shift: HslShift): string | undefined => {\n const hsl = toHsl(cssColor);\n if (!hsl) {\n return undefined;\n }\n return formatStyleHsl({\n h: (hsl.h ?? 0) + shift.h,\n s: hsl.h === undefined ? hsl.s : hsl.s + shift.s,\n l: hsl.l + shift.l,\n alpha: clampUnit((hsl.alpha ?? 1) + shift.alpha),\n });\n};\n\n/**\n * The WCAG 2.x relative luminance (0 = black, 1 = white) of a CSS color, or `undefined` when the\n * color cannot be parsed. Alpha is ignored.\n * @ignore\n */\nexport const relativeLuminance = (cssColor: string): number | undefined => {\n const color = parseColor(cssColor);\n return color && wcagLuminance(color);\n};\n\n/**\n * A CSS colour mixed toward another by `factor` (0 = unchanged, 1 = the other one), as a `#rrggbb`\n * string, or `undefined` when either cannot be parsed.\n * @ignore\n */\nexport const mixColor = (cssColor: string, towardColor: string, factor: number): string | undefined => {\n const color = parseColor(cssColor);\n const toward = parseColor(towardColor);\n return color && toward && formatHex(interpolate([color, toward])(factor));\n};\n\n/**\n * The darker shade a line's outline or label is drawn in, so the two read as one colour family:\n * a route and its outline, a place connection and its label.\n * @ignore\n */\nexport const darkerShadeOf = (cssColor: string): string | undefined => mixColor(cssColor, '#000000', 0.4);\n\n/**\n * The lighter shade of {@link darkerShadeOf}, for the same colour family on a dark map.\n * @ignore\n */\nexport const lighterShadeOf = (cssColor: string): string | undefined => mixColor(cssColor, '#ffffff', 0.4);\n\n/**\n * The shortest distance between two hues, in degrees (0–180); 0 when either colour has no hue. Two\n * colours more than a hue family apart are different colours rather than shades of one another.\n * @ignore\n */\nexport const hueDistance = ({ h: hue }: HslColor, { h: otherHue }: HslColor): number =>\n hue === undefined || otherHue === undefined ? 0 : Math.abs(differenceHueNaive({ h: hue }, { h: otherHue }));\n\n/**\n * Below this saturation (0–1) a colour reads as grey, and its hue carries no meaning — so a grey\n * counts as a shade of any colour.\n * @ignore\n */\nexport const GREY_SATURATION = 0.08;\n","import { everyOf } from '../shared/knobUtils';\nimport type { LayerSelector } from '../shared/layers/layerSelector';\nimport type { MapColorBid, RoadClass } from './colorRewrite';\n\n/**\n * The semantic map colours. The first ten are the *Foundations* palette of TomTom's style themes,\n * under their own names and in their order: one name drives every layer and shade the style derives from it.\n *\n * | Name | Theme label | Reaches |\n * |---|---|---|\n * | `land` | Land (base) | Map background and other bare-land features, influences admin borders/labels. Also used to derive all map colours if undefined. |\n * | `water` | Water | Waterbodies, waterways and other water features. Also influences glaciers, water labels and ferry lines. |\n * | `vegetation` | Vegetation | Forests and other land cover such as grass and shrubland. Also influences earth cover vegetation at smaller scales. |\n * | `park` | Park & Recreation | City parks and sport/recreation grounds. Also influences National Parks and other protected areas at smaller scales. |\n * | `artificial` | Artificial | Built-up-areas and man-made features such as landuse grounds and buildings. Also influences railways. |\n * | `roadMajor` | Major Road | Most important road classes, from motorways & trunks to primary roads. Also influences outline and tunnel colours. |\n * | `road` | Road | Less important road classes, from secondary/tertiary to streets and service roads. Also influences tunnel colours. |\n * | `roadOutline` | Road Outline | Road outlines for secondary/tertiary roads and minor classes. Also influences road labels and all other outline colours. |\n * | `label` | Label | Labels for capitals and other populated places/admin areas. Also influences border styling and all other label colours. |\n * | `labelOutline` | Label Outline | Outlines for populated places and admin area labels. Also influences border outlines and all other label halos. |\n *\n * Plus `accent`, which paints no base-map layer: the default colour of what the modules draw on top\n * of the map ({@link TomTomMap.mapColors}).\n *\n * @group Map Styling\n */\nexport type MapColorName =\n | 'land'\n | 'water'\n | 'vegetation'\n | 'park'\n | 'artificial'\n | 'roadMajor'\n | 'road'\n | 'roadOutline'\n | 'label'\n | 'labelOutline'\n | 'accent';\n\n/**\n * The colours that recolour base-map layers — every {@link MapColorName} but `accent`.\n * @ignore\n */\nexport type BaseMapColorName = Exclude<MapColorName, 'accent'>;\n\n/**\n * A partial set of map colours for {@link StylingFoundationsModule.setMapColors}: any subset of them,\n * each a CSS colour string.\n *\n * @example\n * ```typescript\n * styling.setMapColors({ land: '#f3f5f7', water: '#accbe2', roadMajor: '#a1b8ce', label: '#364659' });\n * ```\n *\n * @group Map Styling\n */\nexport type MapColors = Partial<Record<MapColorName, string>>;\n\n/**\n * Every paint property the styling knobs write a colour to. Listed once as values, since\n * `StylingFoundationsModule` needs the set at runtime to pick MapLibre's paint setter over its layout one.\n * @ignore\n */\nexport const paintColorProperties = [\n 'fill-color',\n 'fill-outline-color',\n 'line-color',\n 'text-color',\n 'text-halo-color',\n 'fill-extrusion-color',\n 'background-color',\n 'hillshade-shadow-color',\n 'hillshade-highlight-color',\n 'hillshade-accent-color',\n] as const;\n\n/** @ignore */\nexport type PaintColorProperty = (typeof paintColorProperties)[number];\n\n/**\n * One set of (layers, properties) a semantic colour governs, optionally only on one side of the\n * major/minor road split inside a `match` on the road class.\n * @ignore\n */\nexport type MapColorTarget = Pick<MapColorBid, 'recolorsTints'> & {\n layers: LayerSelector[];\n properties: PaintColorProperty[];\n roadClass?: RoadClass;\n};\n\n/**\n * @ignore\n */\nexport type MapColorDefinition = {\n /**\n * What the colour reaches, in one line: the theme's own summary, which the `colors.*` knob\n * publishes as its `description` in {@link stylingFoundationsKnobCatalogue}.\n */\n description: string;\n /** Where the colour's base literal is read from: the first matching layer that has the property. */\n anchor: {\n layers: LayerSelector[];\n property: PaintColorProperty;\n roadClass?: RoadClass;\n /** The base is the last zoom stop rather than the most saturated one. */\n lastStop?: boolean;\n };\n targets: MapColorTarget[];\n};\n\n// The colour properties each layer type paints through — a fill's rim included, or a recoloured\n// water body keeps the shore line the style gave it.\nconst paintedBy = {\n fill: ['fill-color', 'fill-outline-color'],\n line: ['line-color'],\n 'fill-extrusion': ['fill-extrusion-color'],\n} as const satisfies Record<string, readonly PaintColorProperty[]>;\n\nconst paintLayers =\n <TYPE extends keyof typeof paintedBy>(layerType: TYPE) =>\n (selector: LayerSelector): MapColorTarget => ({\n layers: [{ ...selector, layerTypes: [layerType] }],\n properties: [...paintedBy[layerType]],\n });\n\nconst fillLayers = paintLayers('fill');\nconst lineLayers = paintLayers('line');\nconst extrusionLayers = paintLayers('fill-extrusion');\n\n// Road lines by id fragment within the `road` metadata group; ids are TomTom's to capitalise.\nconst roadLine = (idIncludes: string[], idExcludes?: string[]): LayerSelector => ({\n metadataGroups: ['road'],\n layerTypes: ['line'],\n idIncludes,\n ...(idExcludes && { idExcludes }),\n});\n\n// The road layers that draw several classes in one `match` on the road class (tunnels, bridges,\n// and link roads, whose motorway and trunk links are major-road shades).\nconst MIXED_CLASS_ROAD_LINES = roadLine(['road line', 'road outline', 'under construction', 'link road'], ['tarmac']);\nconst MAJOR_ROAD_LINES = roadLine(['motorway', 'primary road', 'major road']);\nconst MINOR_ROAD_LINES = roadLine(\n ['secondary road', 'tertiary road', 'street', 'minor road', 'restricted', 'service road'],\n ['outline'],\n);\nconst MINOR_ROAD_OUTLINES = roadLine([\n 'secondary road outline',\n 'tertiary road outline',\n 'street outline',\n 'minor road outline',\n 'tarmac outline',\n]);\n// A railway draws as an outline with a dash on top, both in one colour, so the outline goes too.\nconst RAIL_AND_PATH_LINES = roadLine(['railway', 'subway', 'track', 'path', 'aerialway']);\n\n// Where `label` and `labelOutline` read their base: the capitals' text and halo.\nconst CAPITAL_LABELS: LayerSelector = {\n metadataGroups: ['places_label'],\n layerTypes: ['symbol'],\n idIncludes: ['capital'],\n};\n\n// Label layers whose text the `label` colour governs: everything with text except shields and exit\n// numbers (their colours come from the shield artwork) and water labels (governed by `water`).\nconst TEXT_LAYERS: LayerSelector = {\n metadataGroups: ['places_label', 'road_label', 'label', 'address_point_label', 'border_label'],\n layerTypes: ['symbol'],\n idExcludes: ['shield', 'exit', 'water', 'river'],\n};\nconst WATER_TEXT_LAYERS: LayerSelector = {\n metadataGroups: ['label'],\n layerTypes: ['symbol'],\n idIncludes: ['water', 'river'],\n};\n\n/**\n * What each of the ten colours reaches in the standard styles, and where its base is read from,\n * following the themes' own derivation tree so the recolour fans out the way the designers meant.\n * @ignore\n */\nexport const mapColorDefinitions: Record<BaseMapColorName, MapColorDefinition> = {\n land: {\n description:\n 'Map background and other bare-land features, influences admin borders/labels. Also used to derive all map colours if undefined.',\n anchor: { layers: [{ layerTypes: ['background'] }], property: 'background-color' },\n targets: [\n { layers: [{ layerTypes: ['background'] }], properties: ['background-color'] },\n // The ground the background never shows through: bare earth and sand inside the\n // landcover expressions, pedestrian areas, bridges and piers. `vegetation` owns the\n // greens and `artificial` the built-up shades those same layers also carry — a bridge\n // reads as land at low zoom and as built-up at high — so the hue test, not the layer\n // list, is what splits each expression between them.\n fillLayers({ metadataGroups: ['area'], idIncludes: ['earth cover', 'landcover'] }),\n fillLayers({ metadataGroups: ['road_area'] }),\n fillLayers({ metadataGroups: ['transit_area'] }),\n lineLayers({ metadataGroups: ['transit_area'] }),\n ],\n },\n water: {\n description:\n 'Waterbodies, waterways and other water features. Also influences glaciers, water labels and ferry lines.',\n anchor: {\n layers: [{ metadataGroups: ['water'], layerTypes: ['fill'], idIncludes: ['fill'] }],\n property: 'fill-color',\n },\n targets: [\n fillLayers({ metadataGroups: ['water'] }),\n lineLayers({ metadataGroups: ['water'] }),\n { layers: [WATER_TEXT_LAYERS], properties: ['text-color', 'text-halo-color'] },\n lineLayers({ metadataGroups: ['road'], idIncludes: ['ferry'] }),\n ],\n },\n vegetation: {\n description:\n 'Forests and other land cover such as grass and shrubland. Also influences earth cover vegetation at smaller scales.',\n anchor: {\n layers: [{ metadataGroups: ['area'], layerTypes: ['fill'], idIncludes: ['landcover'] }],\n property: 'fill-color',\n },\n targets: [fillLayers({ metadataGroups: ['area'], idIncludes: ['landcover', 'earth cover'] })],\n },\n park: {\n description:\n 'City parks and sport/recreation grounds. Also influences National Parks and other protected areas at smaller scales.',\n anchor: {\n layers: [{ metadataGroups: ['area'], layerTypes: ['fill'], idIncludes: ['protected'] }],\n property: 'fill-color',\n },\n targets: [\n // `landuse` draws every land-use group in one expression, parks among them; the hue test\n // takes its greens and leaves a hospital's pink or a shopping area's yellow alone.\n fillLayers({ metadataGroups: ['area'], idIncludes: ['protected', 'outdoor', 'sport', 'landuse'] }),\n lineLayers({ metadataGroups: ['border'], idIncludes: ['overlays'] }),\n ],\n },\n artificial: {\n description:\n 'Built-up-areas and man-made features such as landuse grounds and buildings. Also influences railways.',\n anchor: {\n layers: [{ metadataGroups: ['area'], layerTypes: ['fill'], idIncludes: ['built-up'] }],\n property: 'fill-color',\n // The small-scale stop is a variant; the city-scale one is the colour itself.\n lastStop: true,\n },\n targets: [\n // Land-use and building tints (education, healthcare, leisure, shopping) sit on built-up\n // ground, so a dark built-up colour darkens them too.\n ...[\n fillLayers({ metadataGroups: ['area'], idIncludes: ['built-up', 'landuse', 'parking'] }),\n fillLayers({ metadataGroups: ['building'] }),\n lineLayers({ metadataGroups: ['building'] }),\n extrusionLayers({ metadataGroups: ['area_3d', 'building'] }),\n ].map((target) => ({ ...target, recolorsTints: true })),\n fillLayers({ metadataGroups: ['transit_area', 'road_area'] }),\n lineLayers({ metadataGroups: ['transit_area'] }),\n lineLayers(RAIL_AND_PATH_LINES),\n ],\n },\n roadMajor: {\n description:\n 'Most important road classes, from motorways & trunks to primary roads. Also influences outline and tunnel colours.',\n anchor: { layers: [roadLine(['motorway & trunk'], ['outline'])], property: 'line-color', roadClass: 'major' },\n targets: [\n lineLayers(MAJOR_ROAD_LINES),\n { layers: [MIXED_CLASS_ROAD_LINES], properties: ['line-color'], roadClass: 'major' },\n ],\n },\n road: {\n description:\n 'Less important road classes, from secondary/tertiary to streets and service roads. Also influences tunnel colours.',\n anchor: { layers: [roadLine(['secondary road'], ['outline'])], property: 'line-color' },\n targets: [\n lineLayers(MINOR_ROAD_LINES),\n {\n layers: [roadLine(['road line', 'under construction', 'link road'], ['outline'])],\n properties: ['line-color'],\n roadClass: 'minor',\n },\n ],\n },\n roadOutline: {\n description:\n 'Road outlines for secondary/tertiary roads and minor classes. Also influences road labels and all other outline colours.',\n anchor: { layers: [roadLine(['secondary road outline'])], property: 'line-color' },\n targets: [\n lineLayers(MINOR_ROAD_OUTLINES),\n {\n layers: [roadLine(['road outline', 'link road outline'], ['tarmac'])],\n properties: ['line-color'],\n roadClass: 'minor',\n },\n ],\n },\n label: {\n description:\n 'Labels for capitals and other populated places/admin areas. Also influences border styling and all other label colours.',\n anchor: { layers: [CAPITAL_LABELS], property: 'text-color' },\n targets: [\n { layers: [TEXT_LAYERS], properties: ['text-color'] },\n lineLayers({ metadataGroups: ['border'], idIncludes: ['country', 'state', 'treaty', 'disputed'] }),\n ],\n },\n labelOutline: {\n description:\n 'Outlines for populated places and admin area labels. Also influences border outlines and all other label halos.',\n anchor: { layers: [CAPITAL_LABELS], property: 'text-halo-color' },\n targets: [\n { layers: [TEXT_LAYERS], properties: ['text-halo-color'] },\n lineLayers({ metadataGroups: ['border'], idIncludes: ['background'] }),\n ],\n },\n};\n\n// Spelled out rather than taken off the definitions' keys, so a bundle that never reads the\n// definitions drops them: a list computed at module load keeps what it is computed from.\n/**\n * The base-map colour names, in the themes' Foundations order.\n * @ignore\n */\nexport const baseMapColorNames: readonly BaseMapColorName[] = everyOf<BaseMapColorName>()([\n 'land',\n 'water',\n 'vegetation',\n 'park',\n 'artificial',\n 'roadMajor',\n 'road',\n 'roadOutline',\n 'label',\n 'labelOutline',\n]);\n\n/**\n * The map colour names, in the themes' Foundations order, with `accent` last.\n *\n * @group Map Styling\n */\nexport const mapColorNames: readonly MapColorName[] = /* @__PURE__ */ ((): MapColorName[] => [\n ...baseMapColorNames,\n 'accent',\n])();\n\n/**\n * Whether a caller-supplied key names one of the map colours.\n * @ignore\n */\nexport const isMapColorName = (name: string): name is MapColorName => mapColorNames.some((known) => known === name);\n\n/**\n * The id of a map colour's knob.\n * @ignore\n */\nexport const mapColorKnobId = <NAME extends MapColorName>(name: NAME): `colors.${NAME}` => `colors.${name}`;\n\n/**\n * One knob per base-map colour, by its id, in the themes' Foundations order.\n * @ignore\n */\nexport const baseMapColorKnobs = <DEFINITION>(definitionOf: (name: BaseMapColorName) => DEFINITION) =>\n Object.fromEntries(baseMapColorNames.map((name) => [mapColorKnobId(name), definitionOf(name)])) as Record<\n `colors.${BaseMapColorName}`,\n DEFINITION\n >;\n","/**\n * How a style's theme derives the map colours it leaves to its `land`.\n *\n * The formulas are its theme's own (`definition.variables` at `STYLE_VERSION`), computed\n * with its helpers' arithmetic: saturation and lightness clamped after every step, and rounded to\n * whole percents where its helper rounds. `StyleThemes.test.ts` pins them against the live themes.\n *\n * @module\n * @ignore\n */\n\nimport type { StandardStyleID } from '../init';\nimport {\n clampUnit,\n formatStyleHsl,\n type HslColor,\n relativeLuminance,\n roundStyleHsl,\n toHsl,\n toHundredths,\n} from '../utils/colorUtils';\nimport { type BaseMapColorName, baseMapColorNames, type MapColorName, type MapColors } from './mapColorCatalogue';\n\n/**\n * A base map colour a theme can derive: any but `land`, which they all derive from.\n * @ignore\n */\nexport type DerivedMapColorName = Exclude<BaseMapColorName, 'land'>;\n\nconst allButLand = /* @__PURE__ */ baseMapColorNames.filter((name): name is DerivedMapColorName => name !== 'land');\n\n/**\n * The map colours each standard style derives from `land`, per its theme: `Monochrome` all nine.\n * `Satellite` leaves its land cover to `land` too, but draws imagery in its place; the rest set all ten.\n * @ignore\n */\nexport const landDerivedMapColors: Partial<Record<StandardStyleID, readonly DerivedMapColorName[]>> = {\n monoLight: allButLand,\n monoDark: allButLand,\n};\n\n/**\n * The colours a derived colour is computed from: its road for a road's outline and major class,\n * `land` for everything else.\n * @ignore\n */\nexport const mapColorDerivationInputs = (name: DerivedMapColorName): readonly MapColorName[] =>\n name === 'roadMajor' || name === 'roadOutline' ? ['land', 'road'] : ['land'];\n\n/**\n * A colour's relative luminance as the themes read it: rounded to two decimals.\n * @ignore\n */\nexport const themeLuminance = (cssColor: string): number | undefined => {\n const luminance = relativeLuminance(cssColor);\n return luminance === undefined ? undefined : toHundredths(luminance);\n};\n\nconst withLightness = (color: HslColor, l: number): HslColor => ({ ...color, l: clampUnit(l) });\n\n// The themes' colour helpers, amounts in percent as they write them: a factor scales the channel by\n// (1 ± factor) and rounds, an amount offsets it and does not.\nconst lighten = (factor: number, color: HslColor) => roundStyleHsl(withLightness(color, color.l * (1 + factor)));\nconst darken = (factor: number, color: HslColor) => lighten(-factor, color);\nconst desaturate = (factor: number, color: HslColor) =>\n roundStyleHsl({ ...color, s: clampUnit(color.s * (1 - factor)) });\nconst lightenBy = (amount: number, color: HslColor) => withLightness(color, color.l + amount / 100);\nconst darkenBy = (amount: number, color: HslColor) => lightenBy(-amount, color);\n\nconst sign = (positive: boolean) => (positive ? 1 : -1);\n\n/**\n * The nine colours a theme derives from `land`, a road given outright standing in for the derived\n * one wherever a road's outline or major class derives from it. `undefined` for a land it cannot read.\n * @ignore\n */\nexport const deriveMapColorsFromLand = (\n land: string,\n road?: string,\n): Record<DerivedMapColorName, string> | undefined => {\n const base = toHsl(land);\n const luminance = themeLuminance(land);\n if (!base || luminance === undefined) return undefined;\n\n const roadSign = sign(luminance <= 0.95);\n const labelSign = sign(luminance > 0.3);\n const derivedRoad = lightenBy(roadSign * 15, lighten(Math.max(luminance / 5, 0.1), base));\n const effectiveRoad = (road && toHsl(road)) || derivedRoad;\n return {\n water: formatStyleHsl(darkenBy(sign(luminance > 0.01) * 10, darken(Math.max(luminance / 20, 0.05), base))),\n vegetation: formatStyleHsl(darken(0.05, darkenBy(2, base))),\n park: formatStyleHsl(darken(0.03, darkenBy(1, base))),\n artificial: formatStyleHsl(darkenBy(sign(luminance > 0.05) * 4, lighten(Math.max(luminance / 15, 0.02), base))),\n road: formatStyleHsl(derivedRoad),\n roadMajor: formatStyleHsl(effectiveRoad),\n roadOutline: formatStyleHsl(darkenBy(roadSign * 20, effectiveRoad)),\n label: formatStyleHsl(desaturate(0.3, darkenBy(labelSign * 60, lighten(Math.max(luminance / 5, 0.1), base)))),\n labelOutline: formatStyleHsl(lightenBy(labelSign * 75, base)),\n };\n};\n\n/**\n * The palette a style paints with: every colour given, and each colour the style derives — when a\n * colour it derives from was given — the way the theme derives it from the effective `land`.\n * @ignore\n */\nexport const withDerivedMapColors = (\n given: MapColors,\n derivedNames: readonly DerivedMapColorName[],\n styleLand: string | undefined,\n): MapColors => {\n const land = given.land ?? styleLand;\n const toDerive = derivedNames.filter(\n (name) =>\n given[name] === undefined && mapColorDerivationInputs(name).some((input) => given[input] !== undefined),\n );\n const derived = land && toDerive.length ? deriveMapColorsFromLand(land, given.road) : undefined;\n if (!derived) return given;\n\n return { ...Object.fromEntries(toDerive.map((name) => [name, derived[name]])), ...given };\n};\n","/**\n * Recolouring style values while keeping their shape and their shade relationships.\n *\n * A semantic colour reaches many literals the style derived from one base: the motorway line, the\n * trunk a shade lighter, the outline darker, the tunnel washed out. Recolouring picks the literal\n * standing for that base (the *anchor*), measures every other literal's HSL offset from it, and\n * re-derives them all from the new colour at those offsets — so the style is its own manifest.\n *\n * @module\n * @ignore\n */\n\nimport { isLegacyFunction } from '../shared/layers/expressionTransforms';\nimport {\n formatStyleHsl,\n GREY_SATURATION,\n type HslShift,\n hslShiftBetween,\n hueDistance,\n isOpaqueColorLiteral,\n shiftHsl,\n toHsl,\n} from '../utils/colorUtils';\nimport { themeLuminance } from './mapColorDerivation';\n\n/** Road classes the `roadMajor` colour governs; every other class is `road`. */\nconst MAJOR_ROAD_CLASSES = new Set(['motorway', 'trunk', 'primary']);\n\n/**\n * Which side of the major/minor road split a literal sits on.\n * @ignore\n */\nexport type RoadClass = 'major' | 'minor';\n\n/**\n * A road class as read off the expression a literal sits in; `undefined` outside a `match` on the\n * feature's road class.\n * @ignore\n */\nexport type RoadClassContext = RoadClass | undefined;\n\n/**\n * Whether a semantic colour owns a literal, given the road class context the literal sits in.\n * @ignore\n */\nexport type LiteralOwnership = (roadClass: RoadClassContext) => boolean;\n\nconst isMatchOnRoadClass = (node: unknown[]): boolean =>\n node[0] === 'match' &&\n Array.isArray(node[1]) &&\n node[1][0] === 'get' &&\n (node[1][1] === 'category' || node[1][1] === 'road_category');\n\n// A link road — `motorway_link` — takes the class of the road it links.\nconst roadClassOfKeys = (keys: unknown): RoadClassContext => {\n const list = Array.isArray(keys) ? keys : [keys];\n const isMajor = (key: unknown) => typeof key === 'string' && MAJOR_ROAD_CLASSES.has(key.replace(/_link$/, ''));\n return list.some(isMajor) ? 'major' : 'minor';\n};\n\n/**\n * Where a literal sits in the expression: its road class, and whether a `match` key picked it —\n * a glacier or a prohibited-access road — rather than it being the fallback everything else takes.\n */\ntype LiteralContext = { roadClass: RoadClassContext; keyed: boolean };\n\nconst TOP_LEVEL_CONTEXT: LiteralContext = { roadClass: undefined, keyed: false };\n\ntype Visitor = (literal: string, context: LiteralContext) => string;\n\n// Replaces every opaque colour literal in a style value — expression or legacy function — through\n// the visitor, which is told where in the expression the literal sits.\nconst mapColorLiterals = (value: unknown, visit: Visitor, context: LiteralContext = TOP_LEVEL_CONTEXT): unknown => {\n if (isOpaqueColorLiteral(value)) return visit(value, context);\n\n if (Array.isArray(value)) {\n if (value[0] === 'match') {\n // ['match', input, key(s), output, key(s), output, …, fallback]\n const onRoadClass = isMatchOnRoadClass(value);\n const result = [value[0], value[1]];\n for (let index = 2; index < value.length - 1; index += 2) {\n const roadClass = onRoadClass ? roadClassOfKeys(value[index]) : context.roadClass;\n result.push(value[index], mapColorLiterals(value[index + 1], visit, { roadClass, keyed: true }));\n }\n const fallbackClass = onRoadClass ? 'minor' : context.roadClass;\n result.push(mapColorLiterals(value.at(-1), visit, { ...context, roadClass: fallbackClass }));\n return result;\n }\n return value.map((item) => mapColorLiterals(item, visit, context));\n }\n if (isLegacyFunction(value)) {\n return {\n ...value,\n stops: value.stops.map(([input, output]) => [input, mapColorLiterals(output, visit, context)]),\n };\n }\n return value;\n};\n\ntype LocatedColorLiteral = LiteralContext & { literal: string };\n\n/**\n * Every opaque colour literal in a style value, with where it sits.\n * @ignore\n */\nexport const collectColorLiterals = (value: unknown): LocatedColorLiteral[] => {\n const found: LocatedColorLiteral[] = [];\n mapColorLiterals(value, (literal, context) => {\n found.push({ literal, ...context });\n return literal;\n });\n return found;\n};\n\n/**\n * The literal standing for a semantic colour: the most saturated one it owns, so a glacier's pale\n * wash never outvotes the water beside it. Between equally saturated ones — every literal of a mono\n * style is a grey — a `match` fallback wins over a keyed exception, then the first. With `lastWins`,\n * saturation does not count: the last one wins, a fallback still over a keyed exception.\n * @ignore\n */\nexport const pickAnchorLiteral = (\n value: unknown,\n owns: LiteralOwnership = () => true,\n lastWins = false,\n): string | undefined => {\n let best: { literal: string; saturation: number; keyed: boolean } | undefined;\n for (const { literal, roadClass, keyed } of collectColorLiterals(value)) {\n if (!owns(roadClass)) continue;\n\n // In whole percents, as the style writes it: read back through sRGB, two greys the style\n // gives one saturation differ in the noise.\n const saturation = Math.round((toHsl(literal)?.s ?? 0) * 100);\n const tie = best?.saturation === saturation;\n const wins =\n !best || (lastWins ? best.keyed || !keyed : saturation > best.saturation || (tie && best.keyed && !keyed));\n if (wins) best = { literal, saturation, keyed };\n }\n return best?.literal;\n};\n\n// Further than this from the anchor's hue and a literal is a tint of its own — a hospital's pink\n// under the built-up beige — rather than a shade of the semantic colour.\nconst HUE_FAMILY_DEGREES = 40;\n\n// The themes scale a tint's saturation by its built-up colour's relative luminance, floored at this\n// (`lu-saturation-factor`), so a darker built-up area mutes its tints as well as darkening them.\nconst TINT_SATURATION_LUMINANCE_FLOOR = 0.33;\n\nconst tintSaturationScale = (cssColor: string): number | undefined => {\n const luminance = themeLuminance(cssColor);\n return luminance === undefined ? undefined : Math.max(luminance, TINT_SATURATION_LUMINANCE_FLOOR);\n};\n\n// A tint keeps its own hue, moves by the anchor's lightness offset, and takes the saturation the\n// theme would give it over the new colour — whatever that colour's own hue and saturation — so a\n// hospital stays pink but turns as dark, and as muted, as a dark theme's built-up area.\nconst recolorTint = (anchorColor: string, literal: string, newColor: string): string | undefined => {\n const [anchor, tint, target] = [anchorColor, literal, newColor].map(toHsl);\n const [anchorScale, newScale] = [anchorColor, newColor].map(tintSaturationScale);\n if (!anchor || !tint || !target || !anchorScale || !newScale) return undefined;\n\n const s = (tint.s * newScale) / anchorScale;\n const alpha = (tint.alpha ?? 1) * (target.alpha ?? 1);\n return formatStyleHsl({ h: tint.h, s, l: target.l + tint.l - anchor.l, alpha });\n};\n\n// The offset that re-derives one shade, or `undefined` when the literal is no shade of the anchor:\n// greys are shades of anything, coloured literals only of an anchor in their own hue family. A\n// near-grey's hue is noise, so it takes the new colour's hue rather than the offset between two\n// arbitrary ones. A shade keeps its own opacity over the new colour's: a halo drawn at 80% stays so.\nconst shadeShift = (anchorColor: string, literal: string, newColor: string): HslShift | undefined => {\n const [anchor, shade, target] = [anchorColor, literal, newColor].map(toHsl);\n if (!anchor || !shade || !target) return undefined;\n\n const grey = anchor.s < GREY_SATURATION || shade.s < GREY_SATURATION;\n if (!grey && hueDistance(anchor, shade) > HUE_FAMILY_DEGREES) return undefined;\n\n const shift = hslShiftBetween(anchorColor, literal);\n const targetAlpha = target.alpha ?? 1;\n return shift && { ...shift, h: grey ? 0 : shift.h, alpha: (shade.alpha ?? 1) * targetAlpha - targetAlpha };\n};\n\n/**\n * One semantic colour's claim on the literals of a style value: where its shades are measured from,\n * what to re-derive them into, and which of them it owns.\n * @ignore\n */\nexport type MapColorBid = {\n anchorColor: string;\n newColor: string;\n owns?: LiteralOwnership;\n /**\n * Whether the tints of other hue families it owns — a hospital's pink, a campus's cream — follow\n * its lightness and luminance, keeping their hue.\n */\n recolorsTints?: boolean;\n};\n\n/**\n * Re-derives the style's own value through every colour bidding for it, keeping each literal's HSL\n * offset from that colour's anchor. A literal in no bid's hue family is a tint in its own right: it\n * keeps its hue and follows the last owning bid that `recolorsTints`, or is left as it is.\n *\n * Every literal is judged against `originalValue`, never against what an earlier bid wrote, so two\n * colours reaching one layer — the tunnel drawing both road classes, the bridge shading from land\n * into built-up — compose instead of the second re-deriving the first's output. Where both own one\n * literal the last bid wins outright.\n * @ignore\n */\nexport const recolorValue = (originalValue: unknown, bids: MapColorBid[]): unknown => {\n const lastFirst = [...bids].reverse();\n return mapColorLiterals(originalValue, (literal, { roadClass }) => {\n const owning = lastFirst.filter(({ owns }) => !owns || owns(roadClass));\n for (const { anchorColor, newColor } of owning) {\n const shift = shadeShift(anchorColor, literal, newColor);\n if (shift) return shiftHsl(newColor, shift) ?? literal;\n }\n const tintBid = owning.find((bid) => bid.recolorsTints);\n if (!tintBid) return literal;\n\n return recolorTint(tintBid.anchorColor, literal, tintBid.newColor) ?? literal;\n });\n};\n","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 { generateId, iconToTrafficIncidentCategory, indexedMagnitudes } 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 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","/**\n * ID of the sprite holding the large POI pin images. It is added to the style *next to* the\n * sprite the style already ships (base map icons, road shields, traffic), which MapLibre keeps\n * under the reserved `default` ID.\n * @ignore\n */\nexport const PIN_CATEGORIES_SPRITE_ID = 'pinCategories';\n\n/**\n * Namespaces an image ID of the pin categories sprite. MapLibre prefixes the images of every\n * sprite other than `default` with their sprite ID, so an image of that sprite is only found\n * under `pinCategories:<image ID>`.\n * @ignore\n */\nexport const toPinSpriteImageID = (imageID: string): string => `${PIN_CATEGORIES_SPRITE_ID}:${imageID}`;\n\n/**\n * The pixel ratio the SDK's own icons are drawn at, and the one a `CustomImage` given none is added at.\n * @ignore\n */\nexport const ICON_PIXEL_RATIO = 2;\n\n/**\n * @ignore\n */\nexport const isDOMImageSupported = (): boolean =>\n typeof document != 'undefined' && typeof DOMParser != 'undefined' && typeof btoa != 'undefined';\n\n/**\n * Loads an image URL into an `img` element, which, unlike MapLibre's `loadImage`, can decode SVG.\n * @ignore\n */\nexport const urlToImg = (url: string): HTMLImageElement => {\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n const img = document.createElement('img');\n img.src = url;\n return img;\n};\n\n/**\n * @ignore\n */\nexport const svgToImg = (svgDomElement: SVGElement): HTMLImageElement => {\n if (!isDOMImageSupported()) {\n return undefined as never as HTMLImageElement;\n }\n return urlToImg(`data:image/svg+xml;base64,${btoa(new XMLSerializer().serializeToString(svgDomElement))}`);\n};\n","import type { LayerSpecification, Map as MapLibreMap, SourceSpecification, StyleSpecification } from 'maplibre-gl';\nimport { changeLayerProps, existingBeforeLayerID, moveLayerBefore } from './mapUtils';\nimport { ofMap } from './ofMap';\nimport type { ToBeAddedLayerSpec } from './types';\n\nconst byMap = new WeakMap<MapLibreMap, ModuleSourcesAndLayers>();\n\n/**\n * The sources and layers the SDK modules add to one map, and those a style switch carried over.\n *\n * A switch that keeps state carries them into the next style, so what the modules draw stays on screen\n * instead of vanishing until each module restores itself. A module then adds its sources and layers\n * as it would to a fresh style, and adding one that was carried adopts it. The switch drops whatever\n * no module adopted once they all have restored.\n *\n * Adopting keeps a source's spec and a layer's type and source: they follow the module config, which a\n * style switch leaves alone.\n * @ignore\n */\nexport class ModuleSourcesAndLayers {\n private readonly sourceIds = new Set<string>();\n private readonly layerIds = new Set<string>();\n private readonly carriedSourceIds = new Set<string>();\n private readonly carriedLayerIds = new Set<string>();\n\n private constructor(private readonly map: MapLibreMap) {}\n\n /** The ones of this map, tracked from first use. */\n static for(map: MapLibreMap): ModuleSourcesAndLayers {\n return ofMap(byMap, map, () => new ModuleSourcesAndLayers(map));\n }\n\n /** Adds the source unless the map has it; a carried one is adopted as it is, for its module to set its data. */\n addSource(sourceId: string, spec: SourceSpecification): void {\n this.sourceIds.add(sourceId);\n this.carriedSourceIds.delete(sourceId);\n if (!this.map.getSource(sourceId)) this.map.addSource(sourceId, spec);\n }\n\n /**\n * Adds the layer below its `beforeID` unless the map has it, at the top when the style lacks that\n * layer. A carried layer is adopted instead: it takes the spec's paint, layout, filter and zoom\n * range and moves below the same anchor, keeping the visibility it draws with until the module\n * sets it, as the module does for a layer it just added.\n * @returns Whether the layer was placed below its anchor, added or adopted.\n */\n addLayer(spec: ToBeAddedLayerSpec): boolean {\n this.layerIds.add(spec.id);\n const current = this.map.getLayer(spec.id);\n if (!current) {\n this.map.addLayer(spec, existingBeforeLayerID(this.map, spec.beforeID));\n return true;\n }\n if (!this.carriedLayerIds.delete(spec.id)) return false;\n\n const carried = current.serialize();\n changeLayerProps(\n { ...spec, layout: { ...spec.layout, visibility: carried.layout?.visibility } },\n carried,\n this.map,\n );\n moveLayerBefore(this.map, spec.id, spec.beforeID);\n return true;\n }\n\n /** Whether the last style switch carried this layer over and no module has adopted it yet. */\n isCarried(layerId: string): boolean {\n return this.carriedLayerIds.has(layerId);\n }\n\n /**\n * The `next` style with the module layers of `previous` carried in, each with its source and in its\n * place among the layers both styles share. For the `transformStyle` of MapLibre's `setStyle`; with\n * no `previous` it carries nothing.\n */\n carryInto(previous: StyleSpecification | undefined, next: StyleSpecification): StyleSpecification {\n this.carriedSourceIds.clear();\n this.carriedLayerIds.clear();\n if (!previous) return next;\n\n const sources = { ...next.sources };\n const nextLayerIds = new Set(next.layers.map((layer) => layer.id));\n // Each run of carried layers goes right below the first layer above it that `next` has too.\n const carriedBelow = new Map<string, LayerSpecification[]>();\n let run: LayerSpecification[] = [];\n for (const layer of previous.layers) {\n if (nextLayerIds.has(layer.id)) {\n if (run.length) carriedBelow.set(layer.id, run);\n\n run = [];\n continue;\n }\n if (!this.layerIds.has(layer.id)) continue;\n\n const sourceId = 'source' in layer ? layer.source : undefined;\n if (sourceId && !sources[sourceId]) {\n if (!this.sourceIds.has(sourceId)) continue;\n\n sources[sourceId] = previous.sources[sourceId];\n this.carriedSourceIds.add(sourceId);\n }\n this.carriedLayerIds.add(layer.id);\n run.push(layer);\n }\n const layers = next.layers.flatMap((layer) => [...(carriedBelow.get(layer.id) ?? []), layer]);\n return { ...next, sources, layers: [...layers, ...run] };\n }\n\n /** Removes the layers and sources the last style switch carried over and no module adopted. */\n dropUnadopted(): void {\n for (const layerId of this.carriedLayerIds) {\n if (this.map.getLayer(layerId)) this.map.removeLayer(layerId);\n }\n for (const sourceId of this.carriedSourceIds) {\n if (this.map.getSource(sourceId)) this.map.removeSource(sourceId);\n }\n this.carriedLayerIds.clear();\n this.carriedSourceIds.clear();\n }\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 type { GeopoliticalView, GlobalConfig } from '@tomtom-org/maps-sdk/core';\nimport { DEFAULT_COMMON_BASE_URL, generateTomTomHeaders, isProxyCredentialsMode } from '@tomtom-org/maps-sdk/core';\nimport { keyBy } from 'lodash-es';\nimport type {\n AllLayoutProperties,\n AllPaintProperties,\n BackgroundLayerSpecification,\n FilterSpecification,\n Map,\n RequestParameters,\n ResourceType,\n StyleImageMetadata,\n StyleSpecification,\n} from 'maplibre-gl';\nimport {\n type InternalTomTomMapParams,\n type StandardStyleID,\n type StyleInput,\n type StylePart,\n standardStyleIDs,\n} from '../init';\nimport { DEFAULT_STANDARD_STYLE_ID, setApiKeyParameter } from '../init/styleInputBuilder';\nimport { collectColorLiterals } from '../styling/colorRewrite';\nimport type { TomTomMap } from '../TomTomMap';\nimport { FLOW_TAGS } from '../traffic/util/trafficFlowMapping';\nimport { INCIDENT_TAGS } from '../traffic/util/trafficIncidentMapping';\nimport { relativeLuminance } from '../utils/colorUtils';\nimport { cannotAddStylePartToCustomStyle } from './errorMessages';\nimport { PIN_CATEGORIES_SPRITE_ID, svgToImg, urlToImg } from './imageUtils';\nimport { ModuleSourcesAndLayers } from './ModuleSourcesAndLayers';\nimport { parseSvg } from './resources';\nimport { AbstractSourceWithLayers } from './SourceWithLayers';\nimport type { CustomImage, LightDark, ToBeAddedLayerSpec, ToBeAddedLayerSpecWithoutSource } from './types';\n\n/**\n * Wait until the map is ready.\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\nconst 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. With credentials-proxy\n * mode drop any inbound `key` param too: TomTom's edge bakes the request's key\n * into tile URLs returned in the style JSON, and the proxy injects its own\n * server-side — leaving the client-visible key would just leak it.\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, isCredentialsProxy: boolean): string => {\n if (!isProxyMode) return url;\n\n try {\n const parsed = new URL(url);\n if (!isTomTomHostname(parsed.hostname)) return url;\n\n if (isCredentialsProxy) {\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\ntype VectorTileViewParameter = 'view' | 'geopoliticalView';\n\n// The Orbis vector tile endpoints, and the query parameter each takes the geopolitical view in:\n// each ignores the other's name.\nconst VECTOR_TILE_VIEW_PARAMETERS: [path: RegExp, viewParameter: VectorTileViewParameter][] = [\n [/\\/maps\\/orbis\\/map-display\\/tile\\/[^/]+\\/[^/]+\\/[^/]+\\.pbf$/, 'view'],\n [/\\/maps\\/orbis\\/display\\/vector\\/tile\\/[^/]+\\/[^/]+\\/[^/]+$/, 'geopoliticalView'],\n];\n\nconst vectorTileViewParameter = (url: URL): VectorTileViewParameter | undefined =>\n VECTOR_TILE_VIEW_PARAMETERS.find(([path]) => path.test(url.pathname))?.[1];\n\ntype TileView = Exclude<GeopoliticalView, 'AE' | 'CN' | 'RU'> | 'Arabic';\n\n// The tiles name the Arabic view `Arabic`, and serve neither `CN` nor `RU`.\nconst toTileView = (view: GeopoliticalView): TileView | undefined => {\n if (view === 'AE') return 'Arabic';\n\n return view === 'CN' || view === 'RU' ? undefined : view;\n};\n\nconst isVectorTileUrl = (tileUrl: string): boolean => {\n try {\n return vectorTileViewParameter(new URL(tileUrl)) !== undefined;\n } catch {\n return false;\n }\n};\n\nconst applyGeopoliticalView = (url: URL, view: GeopoliticalView | undefined): void => {\n const viewParameter = vectorTileViewParameter(url);\n const tileView = view && toTileView(view);\n if (viewParameter && tileView) {\n url.searchParams.set(viewParameter, tileView);\n }\n};\n\n/**\n * The IDs of the style's sources fed by the Orbis vector tiles, the ones a geopolitical view changes.\n * @ignore\n */\nexport const vectorTileSourceIds = (map: Map): string[] =>\n Object.entries(map.getStyle()?.sources ?? {})\n .filter(([, source]) => source.type === 'vector' && source.tiles?.some(isVectorTileUrl))\n .map(([sourceId]) => sourceId);\n\n/**\n * Reloads the visible Orbis vector tiles in place, so they come back in the current geopolitical view.\n * @ignore\n */\nexport const reloadVectorTiles = (map: Map): void => {\n for (const sourceId of vectorTileSourceIds(map)) {\n const tileManager = map.style.tileManagers[sourceId];\n // Most tiles keep their ETag across views, and MapLibre answers a matching one without\n // registering the tile with the worker it reloaded it on, which fails that tile's next re-layout.\n for (const tileId of tileManager?.getIds() ?? []) {\n const tile = tileManager.getTileByID(tileId);\n if (tile) tile.etag = undefined;\n }\n map.refreshTiles(sourceId);\n }\n};\n\ndeclare global {\n /**\n * Installed by the demos-proxy session bootstrap both example builds inject\n * (`examples/src/demos-proxy/demosProxyBootstrap.ts`). Resolves once the\n * session cookie is valid and not about to expire, re-minting it first when\n * needed. A global var rather than a Window member so it can be read off\n * `globalThis`.\n */\n var __DEMOS_PROXY_ENSURE_SESSION__: (() => Promise<void>) | undefined;\n}\n\n/**\n * Gate a request on a fresh demos-proxy session when the bootstrap installed its\n * hook. MapLibre awaits transformRequest results on the MAIN thread before\n * handing a request to its tile worker, which makes this the one place that can\n * hold back worker-fetched tiles until the session cookie is renewed (the\n * workers' own `fetch` is unreachable from here). Without the hook this stays\n * fully synchronous, and a failed renewal lets the request proceed (it will\n * 401, same as without the gate) rather than wedging the map.\n * @ignore\n */\nconst gateOnDemosProxySession = (result: RequestParameters): RequestParameters | Promise<RequestParameters> => {\n const ensureSession = globalThis.__DEMOS_PROXY_ENSURE_SESSION__;\n if (typeof ensureSession !== 'function') return result;\n\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 — with a credentials proxy — set\n * `credentials: 'include'` so the session cookie travels with each tile.\n *\n * @ignore\n */\nexport const transformRequest = (globalConfig: Partial<GlobalConfig>) => {\n const baseURL = globalConfig.commonBaseURL ?? DEFAULT_COMMON_BASE_URL;\n // commonBaseURL points at something other than TomTom — the demos proxy or a\n // customer's own proxy. We always rewrite tile hostnames to flow through it.\n const isProxyMode = baseURL !== DEFAULT_COMMON_BASE_URL;\n // Credentials proxy: it injects the key server-side (no apiKey), so we strip\n // key= and attach `credentials: 'include'`. A customer proxy that keeps its\n // apiKey is excluded (its backend may serve CORS as `*`).\n const isCredentialsProxy = isProxyCredentialsMode(globalConfig);\n\n return (url: string, resourceType?: ResourceType): RequestParameters | Promise<RequestParameters> => {\n const rewrittenUrl = rewriteForProxy(url, baseURL, isProxyMode, isCredentialsProxy);\n const isProxyUrl = isProxyMode && rewrittenUrl.startsWith(baseURL);\n const useCredentials = isCredentialsProxy && 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 ? gateOnDemosProxySession({ url: rewrittenUrl, credentials: 'include' })\n : { url: rewrittenUrl };\n }\n\n const parsedUrl = new URL(rewrittenUrl);\n injectTrafficTags(parsedUrl);\n applyGeopoliticalView(parsedUrl, globalConfig.geopoliticalView);\n\n const result: RequestParameters = {\n url: parsedUrl.toString(),\n headers: { ...generateTomTomHeaders(globalConfig) },\n };\n if (useCredentials) {\n result.credentials = 'include';\n return gateOnDemosProxySession(result);\n }\n return result;\n };\n};\n\ntype LayerProps = {\n id: string;\n layout?: Partial<AllLayoutProperties>;\n paint?: Partial<AllPaintProperties>;\n minzoom?: number;\n maxzoom?: number;\n filter?: FilterSpecification;\n};\n\n// Style-spec property names and name/value pairs, as `changeLayerProps` needs them to call\n// maplibre's key-generic setters.\ntype LayoutKey = keyof AllLayoutProperties;\ntype PaintKey = keyof AllPaintProperties;\ntype LayoutEntry = [LayoutKey, AllLayoutProperties[LayoutKey]];\ntype PaintEntry = [PaintKey, AllPaintProperties[PaintKey]];\n\n/**\n * Applies the layout and paint properties from newLayerProps\n * while unsetting (setting as undefined) the ones from previousLayerProps which no longer exist in newLayerProps.\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/paint props.\n * @param previousLayerProps The previous layer to ensure layout/paint props are removed.\n */\nexport const changeLayerProps = (newLayerProps: LayerProps, previousLayerProps: LayerProps, map: Map) => {\n const layerId = newLayerProps.id;\n if (newLayerProps.maxzoom !== previousLayerProps.maxzoom || newLayerProps.minzoom !== previousLayerProps.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 // `Object.keys`/`Object.entries` can only ever report `string`, while both maplibre setters key\n // by style-spec property name. That correspondence is ours to assert; the loop headers do it\n // once each, which keeps the setter calls below free of casts.\n for (const property of Object.keys(previousLayerProps.layout ?? {}) as LayoutKey[]) {\n if (!newLayerProps.layout?.[property]) {\n map.setLayoutProperty(layerId, property, undefined, { validate: false });\n }\n }\n for (const property of Object.keys(previousLayerProps.paint ?? {}) as PaintKey[]) {\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 ?? {}) as PaintEntry[]) {\n map.setPaintProperty(layerId, property, value, { validate: false });\n }\n\n for (const [property, value] of Object.entries(newLayerProps.layout ?? {}) as LayoutEntry[]) {\n map.setLayoutProperty(layerId, property, value, { validate: false });\n }\n};\n\n/**\n * {@link changeLayerProps} over two parallel arrays of layers.\n */\nconst changeLayersProps = (newLayerProps: LayerProps[], previousLayerProps: LayerProps[], map: Map) => {\n newLayerProps.forEach((layerProps, index) => changeLayerProps(layerProps, previousLayerProps[index], map));\n};\n\n/**\n * Moves every layer whose spec now names a different anchor, and every layer pinned to one that\n * moved, so a group of layers drawn as one keeps its internal order wherever it lands.\n *\n * @remarks\n * Where a layer draws is neither paint nor layout, so {@link changeLayersProps} cannot say it —\n * only `moveLayer` can. Moving the anchored layer alone is not enough either: MapLibre holds no\n * relation between two layers, so a sibling pinned to the one that moved stays behind and the\n * group tears apart. A section drawn with a coloured line under a dashed one is exactly that case.\n * @ignore\n */\nconst restackMovedLayers = (\n newLayerSpecs: ToBeAddedLayerSpecWithoutSource[],\n oldLayerSpecs: ToBeAddedLayerSpecWithoutSource[],\n recordedSpecs: ToBeAddedLayerSpec[],\n map: Map,\n): void => {\n const movedLayerIDs = new Set<string>();\n const restack = (layerSpec: ToBeAddedLayerSpecWithoutSource): void => {\n if (!map.getLayer(layerSpec.id)) return;\n\n moveLayerBefore(map, layerSpec.id, layerSpec.beforeID);\n movedLayerIDs.add(layerSpec.id);\n // The recorded spec is what a style change replays, so it has to name the new anchor too.\n const recordedSpec = recordedSpecs.find((spec) => spec.id === layerSpec.id);\n if (recordedSpec) recordedSpec.beforeID = layerSpec.beforeID;\n };\n\n newLayerSpecs.forEach((newLayerSpec, index) => {\n if (newLayerSpec.beforeID !== oldLayerSpecs[index].beforeID) restack(newLayerSpec);\n });\n\n // Followers resolve in as many passes as the chain is long, and a layer already moved is never\n // picked again, so a circular pinning cannot spin here.\n const followers = () =>\n newLayerSpecs.filter(\n (spec) => !movedLayerIDs.has(spec.id) && spec.beforeID !== undefined && movedLayerIDs.has(spec.beforeID),\n );\n for (let pinned = followers(); pinned.length; pinned = followers()) {\n pinned.forEach(restack);\n }\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 = keyBy(newLayersSpecs, 'id');\n const oldLayersMap = keyBy(oldLayersSpecs, 'id');\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 const addedLayerSpecs = layersToAdd.map(\n (layerId) =>\n ({\n ...newLayersMap[layerId],\n source: sourceWithLayers.source.id,\n }) as ToBeAddedLayerSpec,\n );\n if (addedLayerSpecs.length) {\n layerSpecs.push(...addedLayerSpecs);\n // A layer the new config introduced is not on the map yet — recording the spec does not put\n // it there. `addLayers` honours each spec's `beforeID` and adds it hidden; the caller\n // reveals it alongside the layers that were already there.\n addLayers(addedLayerSpecs, map);\n }\n sourceWithLayers._updateSourceAndLayerIDs();\n restackMovedLayers(newLayersToUpdate, oldLayersToUpdate, layerSpecs, map);\n // update existing layers\n changeLayersProps(newLayersToUpdate, oldLayersToUpdate, map);\n};\n\n/**\n * Returns `beforeLayerID` when the current style has that layer, and undefined — the top of the\n * layer stack — when it does not.\n *\n * MapLibre refuses both `addLayer` and `moveLayer` against a layer the style does not have: it\n * fires an `ErrorEvent` and gives up, so the layer is either never added or left where it was. Not\n * every anchor in the map style layer IDs exists in every style — the satellite style has neither\n * `lowestRoadLine` nor `lowestBuilding` — so a module positioning itself against one has to check\n * first. The top of the stack is the documented fallback for a missing reference layer.\n *\n * @ignore\n * @param map MapLibre map\n * @param beforeLayerID The wanted anchor layer, if any.\n */\nexport const existingBeforeLayerID = (map: Map, beforeLayerID: string | undefined): string | undefined =>\n beforeLayerID && map.getLayer(beforeLayerID) ? beforeLayerID : undefined;\n\n/**\n * Moves a layer below `beforeLayerID`, or to the top of the stack when the style does not have\n * that layer. See {@link existingBeforeLayerID}.\n *\n * @ignore\n * @param map MapLibre map\n * @param layerID The layer to move.\n * @param beforeLayerID The layer to move it below, or undefined for the top of the stack.\n */\nexport const moveLayerBefore = (map: Map, layerID: string, beforeLayerID: string | undefined): void => {\n map.moveLayer(layerID, existingBeforeLayerID(map, beforeLayerID));\n};\n\n/**\n * Shows or hides the given layers, leaving hidden any whose spec carries `visibility: 'none'`, and\n * skipping any a style swap has removed: the module's restore re-adds them with the visibility it holds.\n *\n * Pass the module's current specs: a source's `_layerSpecs` keep the ones its layers were built with.\n * @ignore\n */\nexport const applyLayersVisibility = (\n layerSpecs: ToBeAddedLayerSpecWithoutSource[],\n visible: boolean,\n map: Map,\n): void => {\n if (map._removed) return;\n\n const visibility = visible ? 'visible' : 'none';\n for (const spec of layerSpecs) {\n if (spec.layout?.visibility === 'none' || !map.getLayer(spec.id)) continue;\n\n map.setLayoutProperty(spec.id, 'visibility', visibility, { validate: false });\n }\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 * @param restackExisting Also moves the given layers already on the map below their anchor, in the\n * same order, so they end up stacked as if freshly added — for anchors that moved across sources.\n * @ignore\n */\nexport const addLayers = (layersToAdd: ToBeAddedLayerSpec[], map: Map, restackExisting = false): void => {\n const moduleSourcesAndLayers = ModuleSourcesAndLayers.for(map);\n const layerIdsAlreadyOnMap = new Set<string>();\n const addLayer = (layer: ToBeAddedLayerSpec): void => {\n const placed = moduleSourcesAndLayers.addLayer({ ...layer, layout: { ...layer.layout, visibility: 'none' } });\n if (!placed && restackExisting) {\n moveLayerBefore(map, layer.id, layer.beforeID);\n }\n layerIdsAlreadyOnMap.add(layer.id);\n };\n\n const mapIdDependency: Record<string, ToBeAddedLayerSpec[]> = {};\n const givenLayerIDs = new Set(layersToAdd.map((layer) => layer.id));\n // A given layer that may move yet anchors others only once it has been placed itself: any of\n // them when restacking, and one a style switch carried over. Any other anchor is placed, or\n // missing from the style, which puts the layer on top.\n const layerIDsToPlace = new Set(\n layersToAdd\n .filter((layer) => restackExisting || moduleSourcesAndLayers.isCarried(layer.id))\n .map((layer) => layer.id),\n );\n const isAnchorPlaced = (layerID: string) =>\n layerIdsAlreadyOnMap.has(layerID) ||\n (!layerIDsToPlace.has(layerID) && (!givenLayerIDs.has(layerID) || !!map.getLayer(layerID)));\n\n layersToAdd.forEach((layer) => {\n if (layer.beforeID) {\n if (isAnchorPlaced(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 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 * Adds a style part to the given style input (e.g. the traffic layers), if possible.\n * @ignore\n */\nexport const updateStyleWithPart = (style: StyleInput | undefined, stylePart: StylePart): StyleInput => {\n switch (typeof style) {\n case 'undefined':\n return { type: 'standard', include: [stylePart] };\n case 'string':\n // this is a standard style\n return { type: 'standard', id: style, include: [stylePart] };\n default:\n if (style.type === 'standard') {\n if (style.include) {\n return style.include.includes(stylePart)\n ? style\n : { ...style, include: [...style.include, stylePart] };\n }\n return { ...style, include: [stylePart] };\n }\n throw cannotAddStylePartToCustomStyle(stylePart);\n }\n};\n\n/**\n * Reloads the style with this part when its source is missing.\n * @ignore\n */\nexport const ensureAddedToStyle = async (map: TomTomMap, sourceId: string, stylePart: StylePart): 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 // Resolves once the new style has loaded and the map has hidden the freshly added part's\n // layers (see TomTomMap.handleStyleData) — the module then shows them if asked to.\n await map.setStyle(updateStyleWithPart(map.getStyleInput(), stylePart));\n await waitUntilSourceIsLoaded(map, sourceId);\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 const addOrUpdateToMap = (imgElement: HTMLImageElement | ImageData | ImageBitmap) => {\n if (!map.hasImage(imageId)) {\n map.addImage(imageId, imgElement, options);\n return;\n }\n if (mode === 'if-not-in-sprite') return;\n\n // MapLibre updates an image in place only at the same size and pixel ratio.\n const existing = map.getImage(imageId);\n if (\n existing.data.width === imgElement.width &&\n existing.data.height === imgElement.height &&\n existing.pixelRatio === (options?.pixelRatio ?? 1)\n ) {\n map.updateImage(imageId, imgElement);\n } else {\n map.removeImage(imageId);\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 (/^data:image\\/svg\\+xml[;,]/i.test(imageToLoad)) {\n // MapLibre's loadImage decodes through createImageBitmap, which cannot rasterise SVG\n ensureImageLoaded(urlToImg(imageToLoad));\n } else 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 try {\n addOrUpdateToMap((await map.loadImage(imageToLoad)).data);\n } catch (error) {\n console.warn(`Failed to load image for ID ${imageId}`, error);\n }\n }\n } else {\n // Expecting HTMLImageElement, wait for it to be loaded\n ensureImageLoaded(imageToLoad);\n }\n};\n\n/**\n * An image a module put on the map, as it was given: what tells {@link syncImages} a changed image from\n * the same one.\n * @ignore\n */\nexport type AddedImage = Required<Pick<CustomImage, 'image' | 'pixelRatio'>>;\n\n/**\n * Puts the `wanted` images on the map under their IDs: adds the missing ones, replaces those whose image\n * or pixel ratio changed since `added`, and removes those `added` holds but `wanted` no longer does.\n *\n * @returns What is now added, to pass back as `added` next time.\n * @ignore\n */\nexport const syncImages = (\n map: Map,\n added: Record<string, AddedImage>,\n wanted: Record<string, AddedImage>,\n): Record<string, AddedImage> => {\n for (const imageID of Object.keys(added)) {\n if (!(imageID in wanted) && map.hasImage(imageID)) map.removeImage(imageID);\n }\n for (const [imageID, { image, pixelRatio }] of Object.entries(wanted)) {\n const previous = added[imageID];\n const unchanged = previous?.image === image && previous.pixelRatio === pixelRatio;\n void addOrUpdateImage(unchanged ? 'if-not-in-sprite' : 'add-or-update', imageID, image, map, { pixelRatio });\n }\n return { ...wanted };\n};\n\nconst darkStandardStyleIDs: ReadonlySet<StandardStyleID> = new Set(\n standardStyleIDs.filter((styleID) => styleID.endsWith('Dark') || styleID === 'satellite'),\n);\n\nconst getStandardStyleTheme = (standardStyleID: StandardStyleID): LightDark =>\n darkStandardStyleIDs.has(standardStyleID) ? 'dark' : 'light';\n\n/**\n * Returns the light/dark theme for a given style input.\n * * A custom style that declares its `lightDarkTheme` is taken at its word.\n * * Unknown standard styles, and custom styles that declare nothing, are considered as 'light'\n * theme. For the latter, {@link detectStyleLightDarkTheme} gives a better answer once the style\n * has loaded.\n * @param styleInput The style input to check. If not provided, 'light' is returned.\n * @ignore\n */\nexport const getStyleLightDarkTheme = (styleInput?: StyleInput): LightDark => {\n const declaredTheme = getDeclaredLightDarkTheme(styleInput);\n if (declaredTheme) {\n return declaredTheme;\n }\n const standardStyleID = standardStyleIdOf(styleInput);\n return standardStyleID ? getStandardStyleTheme(standardStyleID) : 'light';\n};\n\n/**\n * The standard style a style input loads, or `undefined` for a custom style or no input.\n * @ignore\n */\nexport const standardStyleIdOf = (styleInput?: StyleInput): StandardStyleID | undefined => {\n if (typeof styleInput === 'string') return styleInput;\n if (styleInput?.type !== 'standard') return undefined;\n\n return styleInput.id ?? DEFAULT_STANDARD_STYLE_ID;\n};\n\n/**\n * The light/dark theme a custom style declares for itself, or `undefined` when it declares none —\n * which is also every standard style, whose theme follows from its ID.\n * @ignore\n */\nexport const getDeclaredLightDarkTheme = (styleInput?: StyleInput): LightDark | undefined =>\n isCustomStyle(styleInput) ? styleInput.lightDarkTheme : undefined;\n\n/**\n * Whether the style input names a custom style (URL or inline JSON) rather than a standard one.\n * @ignore\n */\nexport const isCustomStyle = (styleInput?: StyleInput): styleInput is Extract<StyleInput, { type: 'custom' }> =>\n typeof styleInput === 'object' && styleInput?.type === 'custom';\n\n// Below this relative luminance (0 = black, 1 = white) a canvas colour reads as a dark map.\nconst DARK_LUMINANCE_THRESHOLD = 0.35;\n\n/**\n * Reads the light/dark theme off a loaded style, from the colour its `background` layer paints the\n * canvas with. Returns `undefined` when the style has no background layer or its colour cannot be\n * read, so the caller can keep whatever it assumed before.\n * @ignore\n */\nexport const detectStyleLightDarkTheme = (\n style: Pick<StyleSpecification, 'layers'> | undefined,\n): LightDark | undefined => {\n const background = style?.layers?.find(\n (layer): layer is BackgroundLayerSpecification => layer?.type === 'background',\n );\n if (!background) return undefined;\n\n // The first colour the canvas can wear is enough to tell a dark one from a light one: a\n // background colour is very rarely data-driven.\n const [first] = collectColorLiterals(background.paint?.['background-color']);\n const luminance = first && relativeLuminance(first.literal);\n if (luminance === undefined) return undefined;\n\n return luminance < DARK_LUMINANCE_THRESHOLD ? 'dark' : 'light';\n};\n\n/**\n * Adds the large POI sprite to the map style, as a sprite of its own.\n * * It has to be *added*, not set: the style already ships a `default` sprite with the base map\n * icons (POIs, road shields, traffic), and setting the style's sprite replaces that one, which\n * makes MapLibre drop every image it brought in.\n * * Called on every style load, so it skips the sprite once the current style has it.\n * @ignore\n */\nexport const addPinCategoriesSpriteToStyle = async (\n mapParams: InternalTomTomMapParams,\n theme: LightDark,\n mapLibreMap: Map,\n) => {\n if (mapLibreMap.getSprite().some((sprite) => sprite.id === PIN_CATEGORIES_SPRITE_ID)) {\n return;\n }\n const searchParameters = new URLSearchParams();\n setApiKeyParameter(searchParameters, mapParams.apiKey);\n searchParameters.set('poi', `poi_${theme}`);\n searchParameters.set('apiVersion', '1');\n searchParameters.set('apiChannel', 'preview');\n mapLibreMap.addSprite(\n PIN_CATEGORIES_SPRITE_ID,\n `${mapParams.commonBaseURL}/maps/orbis/assets/sprites/2.*/sprite?${searchParameters}`,\n { validate: false },\n );\n};\n","import type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { AnyMapModule } from './AbstractEventProxy';\nimport type { EventsProxy } from './EventsProxy';\nimport { combineEventScopes, type EventScope, type ResolvedEventScope } from './eventScope';\nimport type { EventHandlerConfig, EventType, SourceWithLayers, UserEventHandler } from './types';\n\n/**\n * Everything {@link UserEvents} needs to register handlers for one scope of one module.\n * @ignore\n */\nexport type UserEventsOptions<T, WHERE_SCOPE> = {\n eventProxy: EventsProxy;\n /**\n * The source(s) whose features this scope handles events for. Several sources back one scope\n * when they carry the same data in different display modes — hexgrid, square and heatmap in\n * {@link TrafficAreaAnalyticsModule}, clustered and unclustered places in {@link PlacesModule}.\n */\n sourcesWithLayers: SourceWithLayers[];\n /**\n * The module that owns these sources. Kept with every handler so a style-change restore can\n * tell handlers apart when two modules share one MapLibre source ID.\n */\n owner: AnyMapModule;\n /**\n * Event configuration for every handler registered through this instance — the hover cursor,\n * chiefly. A scope built by {@link UserEvents.where} inherits the module's unless `where` is\n * given one of its own, which is how two parts of one module get different cursors.\n */\n config?: EventHandlerConfig;\n /**\n * Transforms a raw {@link MapGeoJSONFeature} into the feature type `T` this scope exposes, so\n * callers receive the module's own feature shape. Applied before handlers and before any\n * {@link UserEvents.where} predicate, so a predicate also sees `T`.\n */\n mapping?: (feature: MapGeoJSONFeature) => T;\n /** The scope already applied to this instance, if it came from {@link UserEvents.where}. */\n scope?: ResolvedEventScope;\n /**\n * Turns a module-specific scope object into a {@link ResolvedEventScope}. Only modules with a\n * typed layer vocabulary supply one — today that is {@link BaseMapModule} and its layer groups.\n */\n scopeResolver?: (scope: WHERE_SCOPE) => ResolvedEventScope;\n};\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, WHERE_SCOPE = never> {\n private readonly sources: SourceWithLayers[];\n private readonly options: UserEventsOptions<T, WHERE_SCOPE>;\n\n constructor(options: UserEventsOptions<T, WHERE_SCOPE>) {\n this.options = options;\n this.sources = options.sourcesWithLayers;\n }\n\n /**\n * Narrows these events to part of what they cover, returning a {@link UserEvents} over just\n * that part.\n *\n * Two things can be narrowed. A **feature predicate** keeps only the features you care about —\n * available on every module, since only the module's own data can tell a major incident from a\n * minor one. A **layer scope** keeps only part of the map's layers; the SDK offers this where\n * a stable, typed vocabulary for those layers exists, which today means\n * {@link BaseMapModule}'s layer groups.\n *\n * A new instance, rather than this one narrowed in place: callers hold `events` and narrow it\n * more than once, and mutating would make the second `where()` mean \"first AND second\" while\n * leaving the surface it came from silently scoped — and its `config` overridden.\n *\n * Narrowing on both axes is one call, not two — {@link BaseMapModule}'s scope object takes a\n * `features` predicate alongside its `layerGroups`. A further `where()` does intersect with\n * this one, but two feature predicates read better as a single `a(f) && b(f)`.\n *\n * @param scope See {@link EventScope} — a predicate over this scope's feature type, or, where\n * the module supports one, a scope object such as `{ layerGroups }`.\n * @param config Event configuration for handlers registered through the returned instance.\n * Each scope can carry its own, which is how two parts of one module get different hover\n * cursors. Defaults to the configuration this instance already uses.\n *\n * @example\n * ```typescript\n * // Feature scope: only major incidents are clickable.\n * trafficIncidents.events.where((incident) => incident.properties.magnitude === 'major')\n * .on('click', showIncidentDetails);\n *\n * // Layer scope with its own cursor, on the base map.\n * baseMap.events\n * .where({ layerGroups: { show: 'only', values: ['roadLabels'] } }, { cursorOnHover: 'pointer' })\n * .on('click', showRoadName);\n *\n * // A named scope, narrowed by a predicate.\n * routing.events.tunnels.where((section) => section.properties.lengthInMeters > 500)\n * .on('click', showLongTunnel);\n * ```\n */\n where(scope: EventScope<T, WHERE_SCOPE>, config?: EventHandlerConfig): UserEvents<T, WHERE_SCOPE> {\n return new UserEvents<T, WHERE_SCOPE>({\n ...this.options,\n config: config ?? this.options.config,\n scope: combineEventScopes(this.options.scope, this.resolveScope(scope)),\n });\n }\n\n // Turns whatever `where()` was handed into the two predicates the event proxy understands.\n // A bare function is a feature predicate over `T`, so the module's mapping is composed in\n // front of it — the caller's predicate must see the module's feature type, not MapLibre's.\n private resolveScope(scope: EventScope<T, WHERE_SCOPE>): ResolvedEventScope {\n if (typeof scope === 'function') {\n const predicate = scope as (feature: T) => boolean;\n const mapping = this.options.mapping;\n return {\n featureMatches: mapping\n ? (feature) => predicate(mapping(feature))\n : (feature) => predicate(feature as T),\n };\n }\n\n const resolver = this.options.scopeResolver;\n if (!resolver) {\n throw new Error('This module supports only feature predicates in events.where().');\n }\n\n return resolver(scope);\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 { eventProxy, config, owner, scope } = this.options;\n const registeredHandler = this.toRegisteredHandler(handler);\n const entries: SourceWithLayers[] = [];\n\n for (const source of this.sources) {\n eventProxy.addEventHandler(source, registeredHandler, type, { config, owner, scope });\n entries.push(source);\n }\n\n return () => {\n for (const source of entries) {\n eventProxy.removeHandler(source, type, registeredHandler);\n }\n };\n }\n\n // Wraps the caller's handler with everything that has to happen between the event proxy and\n // the caller: the module's feature mapping, and any feature predicate this scope carries.\n private toRegisteredHandler(handler: UserEventHandler<T>): UserEventHandler<any> {\n const { mapping, scope } = this.options;\n const featureMatches = scope?.featureMatches;\n\n if (!mapping && !featureMatches) return handler as UserEventHandler<any>;\n\n const toExposed = (feature: MapGeoJSONFeature) => (mapping ? mapping(feature) : (feature as T));\n\n if (!featureMatches) {\n return (feature, lngLat, features, sourceWithLayers) =>\n handler(toExposed(feature), lngLat, features.map(toExposed), sourceWithLayers);\n }\n\n // A predicate filters the whole stack of features under the pointer and promotes the first\n // survivor to the primary argument, so clicking a stack whose top hit is a minor incident\n // and whose second is major still reaches a `magnitude === 'major'` handler with the major\n // one. When nothing in the stack matches, the handler is not called at all.\n return (feature, lngLat, features, sourceWithLayers) => {\n const stack = features.length ? features : [feature];\n const inScope = stack.filter((candidate) => featureMatches(candidate));\n if (!inScope.length) return;\n\n const exposed = inScope.map(toExposed);\n handler(exposed[0], lngLat, exposed, sourceWithLayers);\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.options.eventProxy.remove(source, type, this.options.owner);\n }\n }\n}\n","import { get, isEmpty } from 'lodash-es';\nimport type { Map, MapGeoJSONFeature } from 'maplibre-gl';\nimport type { TomTomMap } from '../TomTomMap';\nimport { CombinedEvents } from './CombinedEvents';\nimport type { EventsProxy } from './EventsProxy';\nimport { assertNoReservedScopeNames, type ResolvedEventScope } from './eventScope';\nimport { configWith, configWithout } from './knobUtils';\nimport { LayerFilterComposer } from './layers/layerFilterComposer';\nimport { ModuleEvents } from './ModuleEvents';\nimport { waitUntilMapIsReady } from './mapUtils';\nimport type { MapModuleCommonConfig, SourcesWithLayers, SourceWithLayerIDs } from './types';\nimport { UserEvents, type UserEventsOptions } from './UserEvents';\n\n/**\n * How a module builds user events over some of its sources.\n * @ignore\n */\ntype EventScopeOptions<T, WHERE_SCOPE> = {\n /** Turns a raw MapLibre feature into the feature type this scope exposes. */\n mapping?: (feature: MapGeoJSONFeature) => T;\n /** Resolves a module-specific scope object, such as BaseMapModule's `{ layerGroups }`. */\n scopeResolver?: (scope: WHERE_SCOPE) => ResolvedEventScope;\n};\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 * No module extends this class directly. Each one extends the subclass that says who owns the\n * sources and layers it controls, and therefore how its instances behave:\n *\n * - {@link AbstractDataOwnedMapModule} — the module adds and owns its own sources, layers and\n * images, suffixed per instance. Multi-instance, built with `create(map, config?)`.\n * - {@link AbstractStyleOwnedMapModule} — the module controls sources and layers the map style\n * already provides, under fixed global IDs. A shared controller, obtained with\n * `get(map, config?)`.\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 AbstractDataOwnedMapModule<MySourcesWithLayers, MyConfig> {\n * constructor(tomtomMap: TomTomMap, config?: MyConfig) {\n * super(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 /**\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 *\n * Only the data-owned modules need it, because the style-owned ones work under fixed global\n * IDs. It is assigned here all the same: this constructor already runs\n * `_initSourcesWithLayers`, which reads it, so a subclass field would be assigned too late.\n * @ignore\n */\n protected readonly instanceIndex: number;\n\n /**\n * The map's single owner of a **style** layer's `filter`. Several modules narrow the same style\n * layer, so none of them calls `setFilter` on one directly: the composer is what keeps one from\n * dropping another's work, and what holds the filter the style itself shipped. A module may\n * still set the filter of a layer it added and owns alone.\n *\n * Assigned in this constructor, which already runs `_initSourcesWithLayers` and `applyConfig`,\n * so a subclass field would be assigned too late for either to use it.\n * @ignore\n */\n protected readonly filterComposer: LayerFilterComposer;\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 config Optional configuration to initialize directly as soon as the map is ready.\n */\n protected constructor(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.tomtomMap = tomtomMap;\n this.eventsProxy = tomtomMap._eventsProxy;\n this.mapLibreMap = tomtomMap.mapLibreMap;\n this.filterComposer = LayerFilterComposer.for(tomtomMap);\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: ({ resetState }) => (resetState ? this.resetOnNewStyle() : this.restoreDataAndConfig()),\n priority: this.styleChangePriority(),\n });\n this._initializing = false;\n }\n\n /**\n * Where this module restores itself in the style-change sequence (see\n * {@link StyleChangeHandler.priority}). Data and visibility modules keep the default; a module\n * whose work must land on top of theirs returns a higher value.\n * @protected\n * @ignore\n */\n protected styleChangePriority(): number {\n return 0;\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 // Passing `this` lets the proxy re-resolve only this module's scopes: POIs and the\n // base map share the `vectorTiles` source ID, so source ID alone cannot tell them apart.\n this.eventsProxy.updateIfRegistered(this.sourcesWithLayers, this);\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 * The given configuration **replaces** the current one\n * rather than being merged into it. To change one part of it, use {@link updateConfig}.\n *\n * @example\n * ```typescript\n * // Apply a new configuration\n * myModule.applyConfig({ visible: true, color: '#E63312' });\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 * Changes part of the configuration and keeps the rest.\n *\n * The given properties are laid over the current configuration — one level deep, so a nested\n * object you pass replaces the nested object that was there — and the result is applied with\n * {@link applyConfig}. This is the same on every module, whatever its `applyConfig` does with a\n * whole configuration.\n *\n * @param partial - The properties to change.\n *\n * @example\n * ```typescript\n * routing.applyConfig({ displayUnits: { distance: { type: 'imperial_us' } }, color: '#0a3653' });\n * routing.updateConfig({ color: '#c2185b' }); // displayUnits stays imperial\n * ```\n *\n * @see {@link applyConfig} to apply a whole configuration\n */\n updateConfig(partial: Partial<NonNullable<CFG>>): void {\n this.applyConfig({ ...this.config, ...partial } as CFG);\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, color: '#E63312' });\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 // Runs while `TomTomMap` notifies its style-change handlers, so `setStyle` only resolves once\n // every module is back. The new style is fully applied by then: the SDK waits for MapLibre's\n // `style.load`, which fires once all the layers of the new style are in place.\n private restoreDataAndConfig(): void {\n // defensively declaring the module as not ready to prevent race conditions:\n this.moduleReady = false;\n this.restoreDataAndConfigImpl();\n }\n\n // The clean-switch counterpart of restoreDataAndConfig (`setStyle(style, { resetState: true })`):\n // the module re-binds to the new style with the configuration `resetConfig()` returns to and\n // nothing shown, so it stays usable — a stale module over a style that no longer has its layers\n // would not be.\n private resetOnNewStyle(): void {\n this.moduleReady = false;\n this.config = undefined;\n this.discardShownData();\n this.initSourcesWithLayers(undefined, true);\n this.config = this._applyConfig(undefined);\n this.emitConfigChange();\n }\n\n /**\n * Forgets whatever this module remembers beyond its configuration, so that a clean style\n * switch does not bring it back. The sources are rebuilt empty right after, so there is\n * nothing to clear on the map.\n *\n * A data-owned module always remembers something, so {@link AbstractDataOwnedMapModule} makes\n * this abstract. A style-owned module overrides it only when it keeps state of its own, such\n * as raw layer edits.\n * @protected\n * @ignore\n */\n protected discardShownData(): void {\n // Deliberately empty: a style-owned module remembers nothing beyond its configuration\n // unless it says otherwise, and the ones that do override this.\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, color: '#E63312' });\n *\n * // Later, retrieve the current configuration\n * const currentConfig = myModule.getConfig();\n * console.log(currentConfig); // { visible: true, color: '#E63312' }\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 // The knob hooks `setKnob`, `getKnob` and `resetKnob` go through. A knob's id is its dotted path\n // into the configuration; a module whose ids are not paths overrides all three.\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return get(this.config, id);\n }\n\n /**\n * @ignore\n */\n _setKnobValue(id: string, value: unknown): void {\n // Every module configuration is all optional, so an unset one is empty.\n this.applyConfig(configWith(this.config ?? ({} as NonNullable<CFG>), id, value));\n }\n\n /**\n * @ignore\n */\n _resetKnobValue(id: string): void {\n if (!this.config) return;\n\n const next = configWithout(this.config, id);\n this.applyConfig(isEmpty(next) ? undefined : next);\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 /**\n * User events over the named sources of this module — what every named scope is.\n *\n * `sourceNames` may list several sources when they carry the same data in different display\n * modes — clustered and unclustered places, or the hexgrid/square/heatmap trio — and missing\n * ones are skipped, so an optional source costs the caller nothing.\n *\n * A scope carries no lifecycle events: a module has one configuration and one `show` stream,\n * neither of which a scope narrows. Both live on the module's own `events`.\n * @ignore\n */\n protected userEvents<T = MapGeoJSONFeature, WHERE_SCOPE = never>(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n options: EventScopeOptions<T, WHERE_SCOPE> = {},\n ): UserEvents<T, WHERE_SCOPE> {\n return new UserEvents<T, WHERE_SCOPE>(this.userEventsOptions(sourceNames, options));\n }\n\n /**\n * What {@link userEvents} builds a scope from, for a subclass that builds a richer one.\n * @ignore\n */\n protected userEventsOptions<T, WHERE_SCOPE>(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n options: EventScopeOptions<T, WHERE_SCOPE>,\n ): UserEventsOptions<T, WHERE_SCOPE> {\n return {\n eventProxy: this.eventsProxy,\n sourcesWithLayers: this.sourcesNamed(sourceNames),\n owner: this,\n config: this.config?.events,\n mapping: options.mapping,\n scopeResolver: options.scopeResolver,\n };\n }\n\n /**\n * The current sources of the given names, skipping the ones this module does not have now.\n * @ignore\n */\n protected sourcesNamed(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n ): SOURCES_WITH_LAYERS[keyof SOURCES_WITH_LAYERS][] {\n return sourceNames.map((name) => this.sourcesWithLayers[name]).filter((sourceWithLayers) => !!sourceWithLayers);\n }\n\n /**\n * `config-change` alone: the event surface of a module with no `show` and nothing on the map a\n * user can interact with, and the lifecycle half of {@link moduleEvents}.\n * @ignore\n */\n protected lifecycleEvents(): ModuleEvents<CFG, never> {\n return new ModuleEvents<CFG, never>(this.configChangeHandlers, []);\n }\n\n /**\n * The event surface of a module with no `show`: user events over the named sources, plus\n * `config-change`. `shown-features` is not subscribable here — `TShown` is `never`, which\n * makes the overload uncallable.\n * @ignore\n */\n protected moduleEvents<T = MapGeoJSONFeature, WHERE_SCOPE = never>(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n options: EventScopeOptions<T, WHERE_SCOPE> = {},\n ): CombinedEvents<T, CFG, never, WHERE_SCOPE> {\n return new CombinedEvents<T, CFG, never, WHERE_SCOPE>(\n this.userEvents<T, WHERE_SCOPE>(sourceNames, options),\n this.lifecycleEvents(),\n );\n }\n\n /**\n * The event surface of a module that has a `show`. `shownFeaturesHandlers` is passed\n * positionally, and must be the very array `show` iterates: handing over a throwaway would\n * accept subscriptions that can never fire.\n * @ignore\n */\n protected moduleEventsWithShown<T, TShown, WHERE_SCOPE = never>(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n shownFeaturesHandlers: ((features: TShown) => void)[],\n options: EventScopeOptions<T, WHERE_SCOPE> = {},\n ): CombinedEvents<T, CFG, TShown, WHERE_SCOPE> {\n return new CombinedEvents<T, CFG, TShown, WHERE_SCOPE>(\n this.userEvents<T, WHERE_SCOPE>(sourceNames, options),\n new ModuleEvents<CFG, TShown>(this.configChangeHandlers, shownFeaturesHandlers),\n );\n }\n\n /**\n * Attaches named scopes to the module's event surface, so `events.on(...)` and\n * `events.<scope>.on(...)` live on one object.\n *\n * A module only declares scopes where it manages more than one surface; with a single source,\n * `events` already is that scope and a named alias would just duplicate it.\n *\n * The two scope type parameters are unrelated, and a module typically has one or the other:\n * `NAMED_SCOPES` is the object of scopes hung off `events` as properties (RoutingModule's\n * `mainLines`, `waypoints`, …), while `WHERE_SCOPE` is the scope object `events.where(…)`\n * accepts as an argument (BaseMapModule's `{ layerGroups }`).\n * @ignore\n */\n protected buildEvents<T, TShown, WHERE_SCOPE, NAMED_SCOPES extends object>(\n moduleWide: CombinedEvents<T, CFG, TShown, WHERE_SCOPE>,\n scopes: NAMED_SCOPES,\n ): CombinedEvents<T, CFG, TShown, WHERE_SCOPE> & NAMED_SCOPES {\n assertNoReservedScopeNames(Object.keys(scopes), this.constructor.name);\n return Object.assign(moduleWide, scopes);\n }\n}\n","import type { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { CleanEventStateOptions, CleanEventStatesOptions, EventStateIds, PutEventStateOptions } from './types';\n\n/**\n * The event states of one set of shown features, over every source that draws them — the one\n * implementation behind a data-owned module's event-state methods and those of its named scopes.\n *\n * The sources are resolved on every call: a style change replaces a module's sources, and a\n * caller may hold on to a scope across one.\n * @ignore\n */\nexport class EventStates {\n constructor(private readonly sources: () => GeoJSONSourceWithLayers[]) {}\n\n put(options: PutEventStateOptions): void {\n for (const source of this.sources()) source.putEventState(options);\n }\n\n clean(options: CleanEventStateOptions): void {\n for (const source of this.sources()) source.cleanEventState(options);\n }\n\n cleanAll(options?: CleanEventStatesOptions): void {\n for (const source of this.sources()) source.cleanEventStates(options);\n }\n\n get(): EventStateIds {\n const ids: EventStateIds = {};\n for (const source of this.sources()) source.getEventStates(ids);\n return ids;\n }\n}\n","import type { MapGeoJSONFeature } from 'maplibre-gl';\nimport { EventStates } from './EventStates';\nimport type { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { CleanEventStateOptions, CleanEventStatesOptions, EventStateIds, PutEventStateOptions } from './types';\nimport { UserEvents, type UserEventsOptions } from './UserEvents';\n\n/**\n * A named events scope of a module that draws its own data: the {@link UserEvents} of one part of\n * what the module shows, plus the event states of those features.\n *\n * @remarks\n * The module's own `putEventState`, `cleanEventState`, `cleanEventStates` and `getEventStates` act\n * on the features its `show` takes; a scope's act on the scope's features alone, by the ids its\n * handlers receive — `routing.events.waypoints` on the waypoints, `custom.events.<source>` on one\n * source. A {@link UserEvents.where} narrowing returns plain {@link UserEvents}: a state belongs to\n * a feature, not to a predicate over features.\n *\n * @example\n * ```typescript\n * routing.events.waypoints.putEventState({ id: waypoint.id, state: 'click' });\n * const { hover: hoveredStops = [] } = routing.events.waypoints.getEventStates();\n * ```\n *\n * @group User Interaction Events\n */\nexport class UserEventsWithStates<T = MapGeoJSONFeature> extends UserEvents<T> {\n private readonly eventStates: EventStates;\n\n /**\n * @param sources Resolves the sources this scope's features are drawn in, on every call.\n */\n constructor(options: UserEventsOptions<T, never>, sources: () => GeoJSONSourceWithLayers[]) {\n super(options);\n this.eventStates = new EventStates(sources);\n }\n\n /** Puts an event state on one of this scope's features; see {@link PutEventStateOptions}. */\n putEventState(options: PutEventStateOptions): void {\n this.eventStates.put(options);\n }\n\n /** Takes the event state off one of this scope's features. */\n cleanEventState(options: CleanEventStateOptions): void {\n this.eventStates.clean(options);\n }\n\n /** Takes the given event states, or all of them, off this scope's features. */\n cleanEventStates(options?: CleanEventStatesOptions): void {\n this.eventStates.cleanAll(options);\n }\n\n /** The ids of this scope's features that have an event state, by state. */\n getEventStates(): EventStateIds {\n return this.eventStates.get();\n }\n}\n","import type { MapGeoJSONFeature } from 'maplibre-gl';\nimport { AbstractMapModule } from './AbstractMapModule';\nimport { EventStates } from './EventStates';\nimport type {\n CleanEventStateOptions,\n CleanEventStatesOptions,\n EventStateIds,\n GeoJSONSourcesWithLayers,\n MapModuleCommonConfig,\n PutEventStateOptions,\n} from './types';\nimport type { UserEventsOptions } from './UserEvents';\nimport { UserEventsWithStates } from './UserEventsWithStates';\n\n/**\n * Base class for the map modules that add and own their own GeoJSON sources, layers and images:\n * places, routes, geometries, the traffic overlays and bring-your-own-data.\n *\n * @remarks\n * The module owns the data it draws. Its generated IDs therefore carry a per-instance suffix\n * (see `instanceIndex`), and the module is safely multi-instance: every `create(map, config?)`\n * call returns another module with its own sources and layers.\n *\n * Owning the data brings the duty this class states: a clean style switch must forget the data\n * the module remembers having shown, through {@link discardShownData}.\n *\n * Every such module hides and shows through {@link setVisible}, which keeps the data it shows.\n *\n * Owning the features also lets it read and write their {@link EventState | event states}:\n * {@link putEventState}, {@link cleanEventState}, {@link cleanEventStates} and\n * {@link getEventStates} act on the features its `show` takes, and each named scope of its\n * `events` carries the same four over its own part ({@link UserEventsWithStates}). A style change\n * shows again what its sources draw, not the data `show` was given, so the states survive it.\n *\n * @typeParam SOURCES_WITH_LAYERS - The GeoJSON sources, with their layers, this module owns\n * @typeParam CFG - The configuration type for this module, or undefined if no configuration is needed\n *\n * @group Shared\n */\nexport abstract class AbstractDataOwnedMapModule<\n SOURCES_WITH_LAYERS extends GeoJSONSourcesWithLayers,\n CFG extends (MapModuleCommonConfig & { visible?: boolean }) | undefined = undefined,\n> extends AbstractMapModule<SOURCES_WITH_LAYERS, CFG> {\n private readonly eventStates = new EventStates(() => this.sourcesNamed(this.eventStateSources()));\n\n /**\n * Owning the data it draws, such a module always remembers something a clean style switch must\n * forget, so the inherited no-op becomes a duty here. A module that remembers nothing\n * implements this as a no-op, and says why.\n * @protected\n * @ignore\n */\n protected abstract discardShownData(): void;\n\n /**\n * Shows or hides the layers of everything shown, per `config.visible` and whether each part has\n * data. `config` is passed rather than read, because `_applyConfig` runs before `this.config`\n * holds the new configuration.\n * @protected\n * @ignore\n */\n protected abstract applyVisibility(config: CFG | undefined): void;\n\n /**\n * Shows or hides everything the module draws.\n *\n * @remarks\n * Stored as `visible` in the configuration. Showing and clearing data never change it: a hidden\n * module keeps its data, through style changes too, and draws it again on `setVisible(true)`.\n *\n * Showing leaves hidden what the configuration turns off: a route section type that is off, the\n * layers a place marker type does not use.\n *\n * @example\n * ```typescript\n * module.setVisible(false);\n * await module.show(data); // stays hidden\n * module.setVisible(true);\n * ```\n */\n setVisible(visible: boolean): void {\n this.config = { ...this.config, visible } as CFG;\n this.applyVisibility(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Whether the module is set to be shown: its `visible` setting, `true` unless set. A shown\n * module with nothing shown draws nothing; `getShown()` returns what is shown.\n */\n isVisible(): boolean {\n return this.config?.visible !== false;\n }\n\n /**\n * The sources drawing the features `show` takes, which the event-state methods act on. Several\n * when the module draws those features more than once; missing ones are skipped.\n * @protected\n * @ignore\n */\n protected abstract eventStateSources(): (keyof SOURCES_WITH_LAYERS)[];\n\n /**\n * A named events scope over the given sources, carrying their event states as well.\n * @ignore\n */\n protected userEventsWithStates<T = MapGeoJSONFeature>(\n sourceNames: (keyof SOURCES_WITH_LAYERS)[],\n options: Pick<UserEventsOptions<T, never>, 'mapping'> = {},\n ): UserEventsWithStates<T> {\n return new UserEventsWithStates<T>(this.userEventsOptions(sourceNames, options), () =>\n this.sourcesNamed(sourceNames),\n );\n }\n\n /**\n * Puts an event state on one of the shown features, as a user interaction would.\n *\n * @remarks\n * The state is what the module's own layers draw (place pins and entry points, route outlines,\n * geometries, details-module incidents, area-analytics cells), what layers of yours read with\n * `['get', 'eventState']`, and what {@link getEventStates} returns. It lasts as long as the shown\n * feature does: through {@link setVisible}, style and configuration changes, not through a new\n * `show`, a `clear` or a `setStyle` with `resetState`.\n *\n * @example\n * ```typescript\n * // Select a feature from a list, as a click would\n * module.putEventState({ id: feature.id, state: 'click' });\n *\n * // Hover a second one without taking the hover off the first\n * module.putEventState({ index: 2, state: 'hover', mode: 'add' });\n * ```\n */\n putEventState(options: PutEventStateOptions): void {\n this.eventStates.put(options);\n }\n\n /**\n * Takes the event state off one of the shown features.\n *\n * @example\n * ```typescript\n * module.cleanEventState({ id: feature.id });\n * ```\n */\n cleanEventState(options: CleanEventStateOptions): void {\n this.eventStates.clean(options);\n }\n\n /**\n * Takes the given event states, or all of them, off the shown features.\n *\n * @example\n * ```typescript\n * module.cleanEventStates(); // every state\n * module.cleanEventStates({ states: ['hover'] }); // hovers only, selections stay\n * ```\n */\n cleanEventStates(options?: CleanEventStatesOptions): void {\n this.eventStates.cleanAll(options);\n }\n\n /**\n * Returns the ids of the shown features that have an event state, by state.\n *\n * @remarks\n * The read side of {@link putEventState} and {@link cleanEventStates}: it reflects those and\n * the states user interactions put. The ids are those `getShown()` and the event handlers hand\n * back, so each can go straight back into {@link putEventState}.\n *\n * @example\n * ```typescript\n * // Keep a list in sync with the selected feature\n * const { click: [selectedID] = [] } = module.getEventStates();\n *\n * // Is this feature hovered?\n * const hovered = module.getEventStates().hover?.includes(feature.id) ?? false;\n * ```\n */\n getEventStates(): EventStateIds {\n return this.eventStates.get();\n }\n}\n","import type { LayerSpecification } from 'maplibre-gl';\nimport type { TomTomMap } from '../../TomTomMap';\nimport { ofMap } from '../ofMap';\nimport { AbstractLayerComposer, setOrDelete } from './abstractLayerComposer';\n\ntype Visibility = NonNullable<NonNullable<LayerSpecification['layout']>['visibility']>;\n\n// Everything that has a say in one layer's visibility, next to the visibility the style gave it.\ntype LayerVisibilityContributions = {\n styleVisibility: Visibility;\n contributions: Map<string, boolean>;\n};\n\nconst composersByMap = new WeakMap<TomTomMap, LayerVisibilityComposer>();\n\nconst composeVisibility = ({ styleVisibility, contributions }: LayerVisibilityContributions): Visibility => {\n const says = new Set(contributions.values());\n if (says.has(false)) return 'none';\n\n return says.has(true) ? 'visible' : styleVisibility;\n};\n\n/**\n * The single owner of a style layer's `visibility`, for the layers more than one module shows or\n * hides.\n *\n * A style-owned module shows or hides the style layers it owns, and a styling toggle shows or hides\n * a few layers nested among them: `pois.microMarkers` among the POIs, `roads.arrows` among the\n * base-map roads. If each wrote the layout property itself, the later write would undo the other.\n *\n * So a layer is hidden when any contributor hides it, shown when one shows it and none hides it,\n * and left as the style shipped it when no contributor has a say.\n *\n * @ignore\n */\nexport class LayerVisibilityComposer extends AbstractLayerComposer<LayerVisibilityContributions> {\n private constructor(tomtomMap: TomTomMap) {\n super(tomtomMap);\n }\n\n /** The composer of this map, created on first use. */\n static for(tomtomMap: TomTomMap): LayerVisibilityComposer {\n return ofMap(composersByMap, tomtomMap, () => new LayerVisibilityComposer(tomtomMap));\n }\n\n /**\n * Registers whether this contributor shows or hides the layer, replacing what it registered\n * before. Pass `undefined` to withdraw its say.\n *\n * @param layerId - The style layer to show or hide.\n * @param contributor - A key unique to the contributor, e.g. `'pois.visible'`.\n * @param visible - `true` to show the layer unless another contributor hides it, `false` to hide it.\n */\n setVisible(layerId: string, contributor: string, visible: boolean | undefined): void {\n // Teardown may still run after `map.remove()`, which leaves no style to write to; and a layer\n // the loaded style does not have is left alone.\n if (this.mapLibreMap._removed || !this.mapLibreMap.getLayer(layerId)) return;\n\n let layer = this.byLayer.get(layerId);\n if (!layer) {\n // Nobody had a say in it yet, so there is nothing to withdraw.\n if (visible === undefined) return;\n\n layer = {\n styleVisibility: this.mapLibreMap.getLayoutProperty(layerId, 'visibility') ?? 'visible',\n contributions: new Map(),\n };\n this.byLayer.set(layerId, layer);\n }\n setOrDelete(layer.contributions, contributor, visible);\n this.mapLibreMap.setLayoutProperty(layerId, 'visibility', composeVisibility(layer), { validate: false });\n }\n}\n","import { AbstractMapModule } from './AbstractMapModule';\nimport { LayerVisibilityComposer } from './layers/layerVisibilityComposer';\nimport type { MapModuleCommonConfig, StyleSourcesWithLayers } from './types';\n\n/**\n * Base class for the map modules that control sources and layers the loaded map style already\n * provides, under fixed global IDs: the base map, POIs, the traffic tiles and the terrain.\n *\n * @remarks\n * The style owns the data, so such a module brings no data of its own and shows nothing by\n * itself. It changes how the parts the style provides are drawn, filtered and made visible.\n * The SDK memoizes one instance per map, obtained with `get(map, config?)`, so every handle on it\n * sees the same state.\n *\n * Because those sources and layers arrive with the style, they are in place as soon as the new\n * style is loaded, and the module re-binds to them in the same tick. That is what the base class\n * already does, so this class only names the kind, narrows the sources a module of this kind can\n * hold, and hands it the map's {@link LayerVisibilityComposer}.\n *\n * @typeParam SOURCES_WITH_LAYERS - The style sources, with their layers, this module controls\n * @typeParam CFG - The configuration type for this module, or undefined if no configuration is needed\n *\n * @group Shared\n */\nexport abstract class AbstractStyleOwnedMapModule<\n SOURCES_WITH_LAYERS extends StyleSourcesWithLayers,\n CFG extends MapModuleCommonConfig | undefined = undefined,\n> extends AbstractMapModule<SOURCES_WITH_LAYERS, CFG> {\n /**\n * The map's single owner of the visibility of a style layer that a styling toggle shows or hides\n * as well as its owning module. A getter rather than a field: the base constructor already runs\n * `applyConfig`, which writes through it.\n * @ignore\n */\n protected get visibilityComposer(): LayerVisibilityComposer {\n return LayerVisibilityComposer.for(this.tomtomMap);\n }\n}\n","import { remove } from 'lodash-es';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { AbstractMapModule } from './AbstractMapModule';\nimport { type ResolvedEventScope, scopedLayerSpecs } from './eventScope';\nimport type { EventHandlerConfig, EventType, SourcesWithLayers, SourceWithLayers, UserEventHandler } from './types';\n\n/**\n * Any map module, whatever its sources and config. The event proxy only ever compares owners by\n * identity, so it needs no more than \"some module\" — but naming the class says who may own a\n * handler, which a bare `object` did not. The two type parameters are irrelevant to identity, and\n * pinning them would drag every module's concrete types through this file.\n *\n * @ignore\n */\nexport type AnyMapModule = AbstractMapModule<any, any>;\n\n/**\n * One entry in the handler index: the caller's function plus everything dispatch needs to decide\n * whether it should fire. A caller supplies a {@link HandlerRegistration}; the proxy files this.\n *\n * Repeated `on()` calls append rather than replace, so several entries can listen to one layer and\n * all of them fire.\n *\n * @ignore\n */\nexport type HandlerEntry = {\n /**\n * The source the handler was registered against — **all** of its layers, not just the ones\n * this entry listens to; `layerIDs` is that narrowing.\n *\n * Kept because the handler receives it as its last argument, because a GeoJSON source can\n * substitute the caller's typed original back in for the rendered feature, and because\n * dropping by source needs its ID. Replaced wholesale on a style change.\n */\n sourceWithLayers: SourceWithLayers;\n /**\n * The layers this entry actually listens to: `sourceWithLayers`' layers narrowed by `scope`,\n * so a scoped handler holds a subset and an unscoped one holds the lot.\n *\n * It is also *where the entry is filed* — it appears in `handlers[type][layerID]` under each\n * of these — which is why a style change re-files the entry rather than just overwriting the\n * set. A Set rather than a list because a layer-scoped handler tests membership per dispatch\n * to narrow the features its caller sees, and the base map contributes ~90 layers to test\n * against.\n */\n layerIDs: Set<string>;\n /**\n * The handler to call, already wrapped by {@link UserEvents} with the module's feature mapping\n * and any `where()` predicate — so by the time dispatch reaches it, it takes the module's own\n * feature type and has already declined features outside its scope.\n */\n handler: UserEventHandler<any>;\n /** Event configuration this entry registered with — its hover cursor, chiefly. */\n config?: EventHandlerConfig;\n /**\n * The module that registered this handler. Two modules can share one MapLibre source ID —\n * POIs and the base map both use `vectorTiles` — so a style-change refresh has to tell their\n * handlers apart by owner rather than by source ID alone.\n */\n owner: AnyMapModule;\n /**\n * How the registration was narrowed, if at all. Kept alongside `layerIDs` rather than being\n * spent when it is resolved: `layerFilter` has to run again over the new layer set after a\n * style change, and `featureMatches` decides per dispatch whether this entry claims the\n * hover cursor for the feature under the pointer.\n */\n scope?: ResolvedEventScope;\n};\n\n/**\n * How a handler was registered: the parts of a {@link HandlerEntry} a caller supplies, as opposed\n * to the layers and source the proxy resolves for it. Grouped rather than passed as three trailing\n * arguments, so a call site names what it means instead of positioning an `undefined` config\n * before an owner.\n *\n * @ignore\n */\nexport type HandlerRegistration = Pick<HandlerEntry, 'config' | 'owner' | 'scope'>;\n\n/**\n * One event type's handlers, bucketed by the layer they listen to. Each bucket is in registration\n * order — `findHandlers()[0]` is the first one registered, which is what claims the hover cursor.\n *\n * @ignore\n */\ntype HandlersByLayer = Record<string, HandlerEntry[]>;\n\n/**\n * Every registered handler, indexed by event type and then by the layer it listens to. A handler\n * scoped to several layers appears under each of them, so dispatch is a lookup rather than a scan:\n * a hit on `layerId` fires exactly `handlers[type][layerId]`.\n *\n * Keyed this way, the interactive-layer list is local information — a layer is interactive while\n * some type still holds a non-empty bucket for it, so releasing one costs five lookups instead of\n * a walk over every registered handler.\n *\n * @example\n * ```typescript\n * const handlers = {\n * click: {\n * 'Buildings': [{ sourceWithLayers, layerIDs: Set { 'Buildings', 'Roads' }, handler, … }],\n * 'Roads': [ …the same handler again, plus any other listening to Roads ],\n * },\n * hover: { 'Water': [ … ] },\n * contextmenu: {},\n * 'hover-move': {},\n * 'long-hover': {},\n * };\n * ```\n *\n * @ignore\n */\ntype EventHandlers = Record<EventType, HandlersByLayer>;\n\n// A fresh index. Every event type owns a bucket map from the start, so registering and dropping\n// never have to create or delete one.\nconst emptyHandlers = (): EventHandlers => ({\n click: {},\n contextmenu: {},\n hover: {},\n 'hover-move': {},\n 'long-hover': {},\n});\n\n/**\n * @ignore\n */\nexport abstract class AbstractEventProxy {\n protected interactiveLayerIDs: string[] = [];\n protected handlers: EventHandlers = emptyHandlers();\n\n /**\n * Adds the given layers as interactive, so we'll listen to them for hover and click.\n *\n * Only the layers a handler's scope actually selects are added. Marking the whole source\n * interactive would let an out-of-scope feature rendered on top shadow an in-scope one, since\n * dispatch matches on the topmost hit.\n * @param layerIDs The scoped layers to listen to.\n */\n private ensureInteractiveLayerIDsAdded(layerIDs: Iterable<string>) {\n for (const layerID of layerIDs) {\n if (!this.interactiveLayerIDs.includes(layerID)) {\n this.interactiveLayerIDs.push(layerID);\n }\n }\n }\n\n // Stops listening to any of these layers that no handler covers any more. Cheap because the\n // index is keyed by layer: \"still needed\" is five bucket lookups, not a walk over every handler.\n private releaseUnusedInteractiveLayerIDs(layerIDs: Set<string>): void {\n remove(this.interactiveLayerIDs, (layerID) => layerIDs.has(layerID) && !this.hasHandlerForLayer(layerID));\n }\n\n // Files a handler under each layer it listens to, creating buckets as needed.\n private indexHandler(handler: HandlerEntry, type: EventType): void {\n const byLayer = this.handlers[type];\n for (const layerID of handler.layerIDs) {\n byLayer[layerID] ??= [];\n byLayer[layerID].push(handler);\n }\n }\n\n // Removes a handler from every bucket it occupies, dropping buckets that empty out.\n private unindexHandlers(handlers: Set<HandlerEntry>, type: EventType): Set<string> {\n const byLayer = this.handlers[type];\n const touchedLayerIDs = new Set<string>();\n for (const handler of handlers) {\n for (const layerID of handler.layerIDs) {\n touchedLayerIDs.add(layerID);\n const bucket = byLayer[layerID];\n if (!bucket) continue;\n\n remove(bucket, (candidate) => handlers.has(candidate));\n if (!bucket.length) delete byLayer[layerID];\n }\n }\n return touchedLayerIDs;\n }\n\n /**\n * Detaches this source's handlers that the predicate matches, then stops listening to any\n * layer none of the survivors still covers.\n *\n * The source has to be part of the match: `UserEvents.on` registers one handler function\n * across several sources and unsubscribes them one at a time, so the function alone does not\n * identify which registration to drop.\n */\n private dropHandlers(\n sourceWithLayers: SourceWithLayers,\n type: EventType,\n matches: (handler: HandlerEntry) => boolean,\n ): void {\n const sourceId = sourceWithLayers.source.id;\n const dropped = new Set<HandlerEntry>();\n for (const bucket of Object.values(this.handlers[type])) {\n for (const handler of bucket) {\n if (handler.sourceWithLayers.source.id === sourceId && matches(handler)) dropped.add(handler);\n }\n }\n if (!dropped.size) return;\n\n this.releaseUnusedInteractiveLayerIDs(this.unindexHandlers(dropped, type));\n }\n\n /**\n * Register an event listener to the list.\n * @param sourceWithLayers The sources and layers to added.\n * @param handler Function that will handle the event.\n * @param type Type of event to listen to.\n * @param registration How the handler was registered — see {@link HandlerRegistration}.\n */\n addEventHandler<T = MapGeoJSONFeature>(\n sourceWithLayers: SourceWithLayers,\n handler: UserEventHandler<T>,\n type: EventType,\n registration: HandlerRegistration,\n ) {\n const layerIDs = new Set(scopedLayerSpecs(sourceWithLayers, registration.scope).map((layer) => layer.id));\n this.ensureInteractiveLayerIDsAdded(layerIDs);\n this.indexHandler({ ...registration, sourceWithLayers, layerIDs, handler }, type);\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 * @param sourceWithLayers The source whose handler should be removed.\n * @param type The event type.\n * @param handler The exact function reference to remove.\n */\n removeHandler(sourceWithLayers: SourceWithLayers, type: EventType, handler: UserEventHandler<any>) {\n this.dropHandlers(sourceWithLayers, type, (entry) => entry.handler === handler);\n }\n\n /**\n * Removes every handler this owner registered for the given source and event type — what\n * `events.off(type)` means. Scoped handlers go with the rest: a scope is a way of registering,\n * not a separate surface to tear down.\n *\n * Matching is by owner rather than by layer set, because two modules can share one MapLibre\n * source ID (POIs and the base map both use `vectorTiles`) and neither may drop the other's\n * handlers.\n * @param sourceWithLayers The source whose handlers should go.\n * @param type The event type to be removed.\n * @param owner The module that registered them.\n */\n remove(sourceWithLayers: SourceWithLayers, type: EventType, owner: AnyMapModule) {\n this.dropHandlers(sourceWithLayers, type, (handler) => handler.owner === owner);\n }\n\n /**\n * Removes all interactive sources and layers.\n */\n removeAll() {\n this.interactiveLayerIDs = [];\n this.handlers = emptyHandlers();\n }\n\n /**\n * Whether any handler, of any event type, listens to this layer.\n * @param layerId The layer id a rendered feature was drawn from.\n */\n hasHandlerForLayer(layerId: string): boolean {\n return Object.values(this.handlers).some((byLayer) => !!byLayer[layerId]?.length);\n }\n\n /**\n * Look up the {@link SourceWithLayers} a layer's handlers were registered against, if any.\n *\n * The SDK assumes at most one `SourceWithLayers` per MapLibre source ID — each module creates\n * its own source — and a layer belongs to exactly one source, so any handler listening to the\n * layer carries the right reference.\n */\n protected sourceWithLayersFor(layerId: string | undefined): SourceWithLayers | undefined {\n if (!layerId) return undefined;\n\n for (const byLayer of Object.values(this.handlers)) {\n const bucket = byLayer[layerId];\n if (bucket?.length) return bucket[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 * @param owner The module whose sources these are. Only its own handlers are refreshed: two\n * modules can share one MapLibre source ID, so the ID alone cannot tell them apart.\n */\n updateIfRegistered(sourcesWithLayers: SourcesWithLayers, owner: AnyMapModule): void {\n const restoredBySourceId = new Map(\n Object.values(sourcesWithLayers).map((sourceWithLayers) => [sourceWithLayers.source.id, sourceWithLayers]),\n );\n\n for (const type of Object.keys(this.handlers) as EventType[]) {\n const affected = new Map<HandlerEntry, SourceWithLayers>();\n for (const bucket of Object.values(this.handlers[type])) {\n for (const handler of bucket) {\n const restored =\n handler.owner === owner\n ? restoredBySourceId.get(handler.sourceWithLayers.source.id)\n : undefined;\n if (restored) affected.set(handler, restored);\n }\n }\n if (!affected.size) continue;\n\n // Re-file rather than edit in place: a style change can rename or drop layers, so a\n // handler's buckets are keyed by ids that may no longer be the ones its scope selects.\n const vacatedLayerIDs = this.unindexHandlers(new Set(affected.keys()), type);\n for (const [handler, sourceWithLayers] of affected) {\n handler.sourceWithLayers = sourceWithLayers;\n handler.layerIDs = new Set(scopedLayerSpecs(sourceWithLayers, handler.scope).map((layer) => layer.id));\n this.ensureInteractiveLayerIDsAdded(handler.layerIDs);\n this.indexHandler(handler, type);\n }\n this.releaseUnusedInteractiveLayerIDs(vacatedLayerIDs);\n }\n }\n\n /**\n * The handlers that should fire for a hit on `layerId`.\n *\n * The bucket *is* the layer, so no layer matching is needed here: a handler is filed under\n * exactly the layers its scope selects, which is what keeps a click on a POI out of the base\n * map's handler even though both register against the `vectorTiles` source.\n *\n * When a feature is supplied, handlers whose scope rejects it are dropped too — that is what\n * lets a feature-scoped handler drive its own hover cursor.\n */\n protected findHandlers = (\n types: EventType[],\n layerId: string | undefined,\n feature?: MapGeoJSONFeature,\n ): HandlerEntry[] =>\n (layerId &&\n types.flatMap((type) => {\n // `?? []` is load-bearing at runtime even though the index type says otherwise:\n // a bucket is deleted once its last handler goes.\n const bucket = this.handlers[type][layerId] ?? [];\n return feature\n ? bucket.filter(\n (handler) => !handler.scope?.featureMatches || handler.scope.featureMatches(feature),\n )\n : bucket;\n })) ||\n [];\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\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 { isNil } from 'lodash-es';\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 if (isNil(value)) {\n throw new Error(`${value} must not be null/undefined. Something went wrong with its initialization.`);\n }\n\n return value;\n};\n","import type { Map, Source, SourceSpecification } from 'maplibre-gl';\nimport { ModuleSourcesAndLayers } from './ModuleSourcesAndLayers';\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 (this.spec) ModuleSourcesAndLayers.for(map).addSource(this.id, this.spec);\n\n this.runtimeSource = map.getSource(this.id) as RUNTIME_SOURCE;\n }\n }\n}\n","import type { Feature, FeatureCollection } from 'geojson';\nimport { omit } from 'lodash-es';\nimport type {\n GeoJSONSource,\n GeoJSONSourceSpecification,\n LayerSpecification,\n Map,\n MapGeoJSONFeature,\n Source,\n SourceSpecification,\n} from 'maplibre-gl';\nimport { asDefined } from './assertionUtils';\nimport { findFeatureByRefId, type IndexedFeature, renderedRefId } from './featureId';\nimport { ModuleSourcesAndLayers } from './ModuleSourcesAndLayers';\nimport { TomTomMapSource } from './TomTomMapSource';\nimport type {\n ByIdOrIndex,\n CleanEventStateOptions,\n CleanEventStatesOptions,\n EventState,\n EventStateIds,\n LayerSpecFilter,\n LayerSpecWithSource,\n PutEventStateOptions,\n SourceWithLayerIDs,\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 /**\n * True once `map.remove()` has run — it deletes `map.style`, so any `getLayer`/`setLayoutProperty`/\n * `addLayer` then throws. Teardown (module `clear()`/`setVisible()`, overlay `remove()`) can run\n * after the host removes the map, so callers treat a removed map as \"no layers present\".\n */\n protected get mapRemoved(): boolean {\n return this.map._removed === true;\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 /**\n * The features the visible layers draw in the viewport.\n *\n * @remarks\n * Queries only the layers drawn now: MapLibre still returns a just-hidden layer's symbols until\n * its next placement, a few frames later.\n */\n queryRenderedFeatures(): MapGeoJSONFeature[] {\n const layers = this._layerSpecs.filter((layer) => this.isLayerVisible(layer)).map((layer) => layer.id);\n return layers.length ? this.map.queryRenderedFeatures({ layers, validate: false }) : [];\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 (this.mapRemoved) return false;\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\n return this.map.getLayoutProperty(layer.id, 'visibility') !== 'none';\n }\n\n setLayersVisible(visible: boolean, filter?: LayerSpecFilter): void {\n if (this.mapRemoved) return;\n\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\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\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 if (this.mapRemoved) return;\n\n const moduleSourcesAndLayers = ModuleSourcesAndLayers.for(this.map);\n for (const layerSpec of this._layerSpecs) {\n moduleSourcesAndLayers.addLayer(layerSpec);\n }\n }\n\n ensureAddedToMapWithVisibility(visible: boolean, addLayersToMap: boolean): void {\n if (this.mapRemoved) return;\n\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 * A copy of the feature with the given event state, or without one.\n * @ignore\n */\nexport const withEventState = <F extends Feature>(feature: F, eventState: EventState | undefined): F => ({\n ...feature,\n properties: eventState ? { ...feature.properties, eventState } : omit(feature.properties, 'eventState'),\n});\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 private readonly showListeners: (() => void)[] = [];\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 if (this.mapRemoved) return; // keep the bookkeeping above; skip the MapLibre calls\n asDefined(this.source.runtimeSource).setData(featureCollection);\n if (automaticVisibility) {\n this.setLayersVisible(!!featureCollection.features.length);\n }\n for (const listener of this.showListeners) {\n listener();\n }\n }\n\n /**\n * Calls `listener` after every `show`, including the ones an event-state change makes.\n */\n onShow(listener: () => void): void {\n this.showListeners.push(listener);\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\n return undefined;\n }\n\n // Event-state changes copy what they change rather than write into it: the shown features can\n // be the caller's own objects, as `show` was given them.\n private showWithFeatures(features: T['features'], show = true): void {\n const shown = { ...this.shownFeatures, features };\n if (show) {\n this.show(shown, { automaticVisibility: false });\n } else {\n this.shownFeatures = shown;\n }\n }\n\n putEventState(options: PutEventStateOptions) {\n const mode = options.mode ?? 'put';\n const target = this.findFeature(options);\n this.showWithFeatures(\n this.shownFeatures.features.map((feature) => {\n if (feature === target) return withEventState(feature, options.state);\n if (mode === 'put' && feature.properties?.eventState === options.state) {\n return withEventState(feature, undefined);\n }\n return feature;\n }),\n options.show,\n );\n }\n\n cleanEventState(options: CleanEventStateOptions) {\n const target = this.findFeature(options);\n if (!target?.properties?.eventState) return;\n\n this.showWithFeatures(\n this.shownFeatures.features.map((feature) =>\n feature === target ? withEventState(feature, undefined) : feature,\n ),\n options.show,\n );\n }\n\n cleanEventStates(options?: CleanEventStatesOptions) {\n const isCleaned = (feature: Feature) =>\n feature.properties?.eventState !== undefined &&\n (!options?.states || options.states.includes(feature.properties.eventState));\n if (!this.shownFeatures.features.some(isCleaned)) return;\n\n this.showWithFeatures(\n this.shownFeatures.features.map((feature) =>\n isCleaned(feature) ? withEventState(feature, undefined) : feature,\n ),\n options?.show,\n );\n }\n\n /**\n * Collects the ids of the shown features by event state into `into`, skipping an id already\n * listed under its state, so several sources showing the same features can share one result.\n * A feature without an id is skipped: {@link ByIdOrIndex} could not address it by one.\n */\n getEventStates(into: EventStateIds = {}): EventStateIds {\n for (const feature of this.shownFeatures.features) {\n const state: EventState | undefined = feature.properties?.eventState;\n const id = renderedRefId(feature);\n if (!state || id === undefined) continue;\n\n const ids = (into[state] ??= []);\n if (!ids.includes(id)) ids.push(id);\n }\n return into;\n }\n}\n","import type { Feature } from 'geojson';\nimport { isNil } from 'lodash-es';\nimport type { MapGeoJSONFeature, Point2D } from 'maplibre-gl';\nimport { findFeatureByRefId, renderedRefId } from './featureId';\nimport { GeoJSONSourceWithLayers, withEventState } from './SourceWithLayers';\nimport type { ClickEventType, EventState, 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((feature) => feature.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 deduped: MapGeoJSONFeature[] = [];\n for (const feature of features) {\n const refId = renderedRefId(feature);\n if (refId === undefined) {\n deduped.push(feature);\n continue;\n }\n const key = `${feature.source} ${refId}`;\n if (!seen.has(key)) {\n seen.add(key);\n deduped.push(feature);\n }\n }\n return deduped;\n};\n\n/**\n * Whether an event state comes from a click. Click states outrank hover ones: only another click\n * replaces them.\n * @ignore\n */\nexport const isClickEventType = (eventState: EventState | undefined): eventState is ClickEventType =>\n eventState === 'click' || eventState === 'contextmenu';\n\nconst isHoverEventState = (eventState: EventState | undefined): boolean =>\n eventState === 'hover' || eventState === 'hover-move' || eventState === 'long-hover';\n\n// `recently-hovered` belongs to neither: several fade out at once while the pointer sweeps.\nconst isSameEventFamily = (eventState: EventState | undefined, other: EventState): boolean =>\n (isClickEventType(eventState) && isClickEventType(other)) ||\n (isHoverEventState(eventState) && isHoverEventState(other));\n\n// `recently-hovered` only follows a hover; otherwise only a click replaces a click.\nconst canReplaceEventState = (current: EventState | undefined, next: EventState): boolean =>\n next === 'recently-hovered' ? isHoverEventState(current) : !isClickEventType(current) || isClickEventType(next);\n\n/**\n * Puts or removes the given event state on the feature with the given ID in featuresToUpdate,\n * unless the state that feature already has outranks it.\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: EventState,\n featureId: string | number | undefined,\n featuresToUpdate: Feature[], // \"featuresToUpdate\" will be mutated\n mode: 'updateInProps' | 'removeFromProps' = 'updateInProps',\n): number | undefined => {\n const found = findFeatureByRefId(featuresToUpdate, featureId);\n if (found && canReplaceEventState(found.feature.properties?.eventState, eventState)) {\n featuresToUpdate.splice(\n found.index,\n 1,\n withEventState(found.feature, mode === 'updateInProps' ? eventState : undefined),\n );\n return found.index;\n }\n return undefined;\n};\n\n// Takes `eventState` off a feature the event left, or turns a hover into `recently-hovered`.\nconst leaveEventState = (\n eventState: EventType,\n featureId: string | number | undefined,\n featuresToUpdate: Feature[],\n fadeHover: boolean,\n): number | undefined =>\n fadeHover\n ? putEventState('recently-hovered', featureId, featuresToUpdate)\n : putEventState(eventState, featureId, featuresToUpdate, 'removeFromProps');\n\n// A user event moves its state onto one feature of the source, so it also takes it off any that\n// `putEventState` gave it, which the proxy never saw.\nconst leaveOtherFeatures = (\n eventState: EventType,\n featureId: string | number | undefined,\n featuresToUpdate: Feature[],\n) => {\n featuresToUpdate.forEach((feature, index) => {\n if (renderedRefId(feature) !== featureId && isSameEventFamily(feature.properties?.eventState, eventState)) {\n featuresToUpdate[index] = withEventState(feature, undefined);\n }\n });\n};\n\nconst removeEventStateAndShow = (\n newEventType: EventType,\n rawFeature: MapGeoJSONFeature,\n sourceWithLayers: GeoJSONSourceWithLayers,\n fadeHover: boolean,\n): void => {\n const previousFeaturesToUpdate = [...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 = leaveEventState(newEventType, renderedRefId(rawFeature), previousFeaturesToUpdate, fadeHover);\n if (!isNil(updatedIndex)) {\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: previousFeaturesToUpdate },\n { automaticVisibility: false },\n );\n }\n};\n\nconst areBothDefinedAndEqual = (\n featureA: MapGeoJSONFeature | undefined,\n featureB: MapGeoJSONFeature | undefined,\n): boolean => !!featureA && !!featureB && featureA.id === featureB.id;\n\n/**\n * Mutates the `eventState` marker on the cached `shownFeatures` so MapLibre paint\n * expressions can react to hover/click. Mutates `previousSourceWithLayers` 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 *\n * With `fadeHover`, a hover state on the feature the event left turns into `recently-hovered`\n * rather than going; the caller clears that with {@link clearRecentHover} when its time is up.\n * @ignore\n */\nexport const updateEventState = (\n eventState: EventType,\n eventFeature: MapGeoJSONFeature | undefined,\n previousEventFeature: MapGeoJSONFeature | undefined,\n sourceWithLayers: SourceWithLayers | undefined,\n previousSourceWithLayers: SourceWithLayers | undefined,\n fadeHover = false,\n): void => {\n if (eventFeature && sourceWithLayers instanceof GeoJSONSourceWithLayers) {\n const featuresToUpdate = [...sourceWithLayers.shownFeatures.features];\n putEventState(eventState, renderedRefId(eventFeature), featuresToUpdate);\n\n if (previousEventFeature && !areBothDefinedAndEqual(previousEventFeature, eventFeature)) {\n // (we have both current and previous features for this event type)\n if (previousSourceWithLayers === sourceWithLayers) {\n // we undo the event state from previous feature next to the new one (they will be shown below):\n leaveEventState(eventState, renderedRefId(previousEventFeature), featuresToUpdate, fadeHover);\n } else if (previousSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n // the previous feature is in other source/layers, so we update and show them:\n removeEventStateAndShow(eventState, previousEventFeature, previousSourceWithLayers, fadeHover);\n }\n }\n leaveOtherFeatures(eventState, renderedRefId(eventFeature), featuresToUpdate);\n\n sourceWithLayers.show(\n { ...sourceWithLayers.shownFeatures, features: featuresToUpdate },\n { automaticVisibility: false },\n );\n return;\n }\n if (previousEventFeature && previousSourceWithLayers instanceof GeoJSONSourceWithLayers) {\n removeEventStateAndShow(eventState, previousEventFeature, previousSourceWithLayers, fadeHover);\n }\n};\n\n/**\n * Clears `recently-hovered` from a feature, leaving any other state it has gained since alone.\n * @ignore\n */\nexport const clearRecentHover = (sourceWithLayers: GeoJSONSourceWithLayers, featureId: string | number): void => {\n const found = sourceWithLayers.findById(featureId);\n if (found?.feature.properties?.eventState === 'recently-hovered') {\n sourceWithLayers.cleanEventState({ index: found.index });\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 previousHoveredPoint The pixel coordinates from the previous hovering event, if any.\n * @param previousHoveredFeature 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 previousHoveredPoint: Point2D | undefined,\n previousHoveredFeature: 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 (!previousHoveredFeature) {\n return { hoverChanged: true };\n }\n if (\n (hoveringFeature.id && hoveringFeature.id !== previousHoveredFeature.id) ||\n (hoveringFeature.properties.id && hoveringFeature.properties.id !== previousHoveredFeature.properties.id) ||\n hoveringFeature.source !== previousHoveredFeature.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 !== previousHoveredFeature.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 previousHoveredPoint &&\n (hoveringPoint.x - previousHoveredPoint.x != 0 || hoveringPoint.y - previousHoveredPoint.y != 0)\n ) {\n return { mouseInMotionOverHoveredFeature: true };\n }\n } else if (previousHoveredFeature) {\n return { hoverChanged: true };\n }\n return {};\n};\n","import type { Feature } from 'geojson';\nimport { isUndefined, omitBy } from 'lodash-es';\nimport type { LngLat, Map, MapGeoJSONFeature, MapMouseEvent, Point2D, PointLike } from 'maplibre-gl';\nimport type { MapEventsConfig } from '../init';\nimport { AbstractEventProxy, type HandlerEntry } from './AbstractEventProxy';\nimport {\n clearRecentHover,\n dedupeRenderedFeatures,\n detectHoverState,\n scopeToSource,\n updateEventState,\n} from './eventUtils';\nimport { renderedRefId } from './featureId';\nimport { GeoJSONSourceWithLayers } from './SourceWithLayers';\nimport type { ClickEventType, EventHandlerConfig, EventType, SourceWithLayers } from './types';\n\n// Shared empty result, so the fallbacks on the pointer path never allocate.\nconst noRenderedFeatures: MapGeoJSONFeature[] = [];\n\n// A handler for any of these makes a feature interactive, and so gives it hover states.\nconst interactiveEventTypes: EventType[] = ['hover', 'hover-move', 'long-hover', 'click', 'contextmenu'];\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 hoverGracePeriodMS: 400,\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?: ReturnType<typeof setTimeout>;\n // Keyed by source and feature id, so leaving a feature again restarts its own countdown.\n // `globalThis`: `Map` here is MapLibre's.\n private readonly recentHoverTimeouts = new globalThis.Map<string, ReturnType<typeof setTimeout>>();\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 // Newest un-handled mouse move, and whether a frame is already booked to handle it.\n private pendingMouseMove?: MapMouseEvent;\n private mouseMoveFrameRequested = false;\n // Configuration\n private 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, ...omitBy(config, isUndefined) };\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n this.lastCursorStyle = this.config.cursorOnMap;\n this.listenToEvents();\n }\n\n /**\n * Merges `config` into the current configuration; what it leaves out, or sets `undefined`,\n * keeps its value. Timers already running keep the delay they started with.\n */\n updateConfig(config: MapEventsConfig): void {\n this.config = { ...this.config, ...omitBy(config, isUndefined) };\n if (!this.hoveringFeature) {\n this.mapCanvas.style.cursor = this.config.cursorOnMap;\n }\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 // Drop any move already queued for the next frame, so disabling takes effect now\n // rather than one frame late.\n this.pendingMouseMove = undefined;\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(\n rendered: MapGeoJSONFeature[],\n firingFeature: MapGeoJSONFeature | undefined,\n scopedLayerIDs?: Set<string>,\n ): Feature[] {\n const inScope = scopedLayerIDs ? rendered.filter((feature) => scopedLayerIDs.has(feature.layer.id)) : rendered;\n const scoped = dedupeRenderedFeatures(scopeToSource(inScope, firingFeature?.source));\n const sourceWithLayers = this.sourceWithLayersFor(firingFeature?.layer.id);\n return sourceWithLayers instanceof GeoJSONSourceWithLayers\n ? scoped.map((feature) => sourceWithLayers.findById(renderedRefId(feature))?.feature ?? feature)\n : scoped;\n }\n\n // The features a given handler should see. A layer-scoped handler gets its own narrowed array;\n // everyone else shares the one computed for the source, so the common path costs nothing.\n //\n // A scope is resolved to concrete layer IDs once, against the style's own specs, and those IDs\n // are what narrowing uses. The scope's predicate is not an option here: it matches on\n // `metadata.group`, which the style carries but a feature from `queryRenderedFeatures` does\n // not.\n //\n // Feature predicates are not applied here either — UserEvents applies those, because it also\n // has to promote the first surviving feature to the primary argument.\n private callerFeaturesFor(\n handler: HandlerEntry,\n rendered: MapGeoJSONFeature[],\n firingFeature: MapGeoJSONFeature | undefined,\n shared: Feature[],\n ): Feature[] {\n return handler.scope?.layerFilter ? this.toCallerFeatures(rendered, firingFeature, handler.layerIDs) : shared;\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 clearTimeout(this.longHoverTimeoutHandlerID);\n }\n\n private restartLongHoverTimeout() {\n this.clearLongHoverTimeout();\n this.longHoverTimeoutHandlerID = 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(['long-hover'], this.hoveringFeature?.layer.id);\n\n // Only build `allEventFeatures` when a handler will read it (see onMouseMove).\n if (longHoverHandlers.length) {\n const rendered = this.hoveringFeatures ?? noRenderedFeatures;\n const firing = this.hoveringFeature;\n const shared = this.toCallerFeatures(rendered, firing);\n for (const entry of longHoverHandlers) {\n const callerFeatures = this.callerFeaturesFor(entry, rendered, firing, shared);\n entry.handler(\n callerFeatures[0],\n this.hoveringLngLat as LngLat,\n callerFeatures,\n entry.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 /**\n * Mouse movement arrives far more often than the browser paints, and each pass runs a\n * `queryRenderedFeatures` — by a wide margin the most expensive thing this proxy does. Only\n * the newest position matters by the time anything is drawn, so moves are coalesced into one\n * pass per animation frame and the intermediate ones are dropped.\n *\n * Hover semantics are unaffected: `detectHoverState` compares against the previously handled\n * position, so a coalesced move is indistinguishable from the pointer having travelled\n * straight there.\n */\n private onMouseMove(ev: MapMouseEvent) {\n this.pendingMouseMove = ev;\n if (this.mouseMoveFrameRequested) return;\n\n this.mouseMoveFrameRequested = true;\n requestAnimationFrame(() => {\n this.mouseMoveFrameRequested = false;\n const latestMove = this.pendingMouseMove;\n this.pendingMouseMove = undefined;\n if (latestMove) {\n this.handleMouseMove(latestMove);\n }\n });\n }\n\n private handleMouseMove(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.hasHandlerForLayer(hoveredTopFeature.layer.id)) {\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 if (!hoverChanged && !mouseInMotionOverHoveredFeature) return;\n\n const previousHoveredFeature = this.hoveringFeature;\n const previousHoveredSourceWithLayers = this.hoveringSourceWithLayers;\n this.hoveringLngLat = ev.lngLat;\n this.hoveringPoint = ev.point;\n this.hoveringFeature = hoveredTopFeature;\n this.hoveringSourceWithLayers = this.sourceWithLayersFor(hoveredTopFeature?.layer.id);\n\n // Resolved lazily by the `hover` branch so it picks up the marker-added spread that\n // `updateEventState` writes back to `shownFeatures` (matches the click path's dispatch\n // order), then reused by `hover-move` rather than recomputed.\n let callerFeatures: Feature[] | undefined;\n\n if (hoverChanged) {\n callerFeatures = this.dispatchHoverEnter(ev, previousHoveredFeature, previousHoveredSourceWithLayers);\n }\n\n if (mouseInMotionOverHoveredFeature) {\n this.dispatchHoverMove(ev, callerFeatures);\n }\n\n this.restartLongHoverTimeout();\n }\n\n /**\n * Handles the pointer arriving on a different feature: cursor, `eventState` marker, and the\n * `hover` handlers themselves.\n * @returns the caller-facing features, if they had to be built, so `hover-move` can reuse them.\n */\n private dispatchHoverEnter(\n ev: MapMouseEvent,\n previousHoveredFeature: MapGeoJSONFeature | undefined,\n previousHoveredSourceWithLayers: SourceWithLayers | undefined,\n ): Feature[] | undefined {\n const layerId = this.hoveringFeature?.layer.id;\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 // The top feature is passed here so a feature-scoped handler only claims the cursor for\n // features it actually accepts. The dispatch lookup below deliberately omits it: UserEvents\n // filters the whole stack itself, and may promote a lower feature the top one hid.\n const firstHandler = this.findHandlers(interactiveEventTypes, layerId, this.hoveringFeature)?.[0];\n this.updateHoverCursor(firstHandler?.config);\n\n const hoverGracePeriodMS = previousHoveredFeature ? this.hoverGracePeriodFor(previousHoveredFeature) : 0;\n updateEventState(\n 'hover',\n this.hoveringFeature,\n previousHoveredFeature,\n this.hoveringSourceWithLayers,\n previousHoveredSourceWithLayers,\n hoverGracePeriodMS > 0,\n );\n if (previousHoveredFeature && hoverGracePeriodMS > 0) {\n this.clearRecentHoverLater(previousHoveredFeature, hoverGracePeriodMS);\n }\n\n const hoverHandlers = this.findHandlers(['hover'], layerId);\n // Only build `allEventFeatures` (scope + dedupe + substitute) when a handler will read it —\n // hover state above is tracked for the cursor/eventState even without hover handlers, so a\n // click-only module pays nothing here.\n if (!hoverHandlers.length) return undefined;\n\n const callerFeatures = this.toCallerFeatures(this.hoveringFeatures ?? noRenderedFeatures, this.hoveringFeature);\n this.dispatchToHandlers(hoverHandlers, ev, this.hoveringFeature, callerFeatures);\n return callerFeatures;\n }\n\n // Handles the pointer travelling along the feature it is already on.\n private dispatchHoverMove(ev: MapMouseEvent, alreadyBuiltFeatures: Feature[] | undefined): void {\n const hoverMoveHandlers = this.findHandlers(['hover-move'], this.hoveringFeature?.layer.id);\n if (!hoverMoveHandlers.length) return;\n\n const firing = this.hoveringFeature;\n const callerFeatures =\n alreadyBuiltFeatures ?? this.toCallerFeatures(this.hoveringFeatures ?? noRenderedFeatures, firing);\n this.dispatchToHandlers(hoverMoveHandlers, ev, firing, callerFeatures);\n }\n\n // Calls each handler with the features its own scope allows. Kept parameter-by-parameter\n // rather than bundled into an options object: this runs on the pointer path, and an object\n // literal per call would be garbage collected every frame for no benefit.\n private dispatchToHandlers(\n handlers: HandlerEntry[],\n ev: MapMouseEvent,\n firingFeature: MapGeoJSONFeature | undefined,\n sharedFeatures: Feature[],\n ): void {\n for (const entry of handlers) {\n const handlerFeatures = this.callerFeaturesFor(\n entry,\n this.hoveringFeatures ?? noRenderedFeatures,\n firingFeature,\n sharedFeatures,\n );\n entry.handler(handlerFeatures[0], ev.lngLat, handlerFeatures, entry.sourceWithLayers);\n }\n }\n\n // The first handler's config decides, as it does for the hover cursor.\n private hoverGracePeriodFor(feature: MapGeoJSONFeature): number {\n const handler = this.findHandlers(interactiveEventTypes, feature.layer.id, feature)[0];\n return handler?.config?.hoverGracePeriodMS ?? this.config.hoverGracePeriodMS;\n }\n\n // Resolves the source again when the time is up: a style change may have replaced it meanwhile.\n private clearRecentHoverLater(feature: MapGeoJSONFeature, delayMS: number): void {\n const featureId = renderedRefId(feature);\n if (featureId === undefined) return;\n\n const key = `${feature.source} ${featureId}`;\n const layerId = feature.layer.id;\n clearTimeout(this.recentHoverTimeouts.get(key));\n this.recentHoverTimeouts.set(\n key,\n setTimeout(() => {\n this.recentHoverTimeouts.delete(key);\n const sourceWithLayers = this.sourceWithLayersFor(layerId);\n if (sourceWithLayers instanceof GeoJSONSourceWithLayers) clearRecentHover(sourceWithLayers, featureId);\n }, delayMS),\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 previousClickedFeature = this.lastClickedFeature;\n this.lastClickedFeature = clickedFeatures[0];\n const previousClickedSourceWithLayers = this.lastClickedSourceWithLayers;\n const clickHandlers = this.findHandlers([clickType], this.lastClickedFeature?.layer.id);\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 previousClickedFeature,\n this.lastClickedSourceWithLayers,\n previousClickedSourceWithLayers,\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 firing = this.lastClickedFeature;\n const shared = this.toCallerFeatures(clickedFeatures, firing);\n for (const entry of clickHandlers) {\n const callerFeatures = this.callerFeaturesFor(entry, clickedFeatures, firing, shared);\n entry.handler(callerFeatures[0], ev.lngLat, callerFeatures, entry.sourceWithLayers);\n }\n }\n }\n}\n","import { isColorLiteral } from '../utils/colorUtils';\nimport type { KnobEntry, KnobRange } from './knobs';\nimport type { KnobPlainValue, ValueAt } from './knobUtils';\n\nconst describe = (value: unknown): string => {\n if (typeof value === 'string') return `'${value}'`;\n\n return Array.isArray(value) ? `[${value.map(describe).join(', ')}]` : String(value);\n};\n\nconst expected = (what: string, value: unknown): string => `expects ${what}; got ${describe(value)}`;\n\nconst isFiniteNumber = (value: unknown): value is number => typeof value === 'number' && Number.isFinite(value);\n\nconst isWithin = (value: number, { min, max, bounds }: KnobRange): boolean =>\n bounds === 'soft' || (value >= min && (bounds === 'hard-min' || value <= max));\n\nconst boundsText = ({ min, max, bounds }: KnobRange): string => {\n if (bounds === 'hard') return ` between ${min} and ${max}`;\n\n return bounds === 'hard-min' ? ` of at least ${min}` : '';\n};\n\n// A list's problem: the whole value where it is no list of strings, else the items it refuses.\nconst listProblem = (what: string, value: unknown, accepts: (item: string) => boolean): string | undefined => {\n if (!Array.isArray(value) || !value.every((item) => typeof item === 'string')) return expected(what, value);\n\n const refused = value.filter((item) => !accepts(item));\n if (!refused.length) return undefined;\n\n return `expects ${what}; ${refused.map(describe).join(', ')} ${refused.length === 1 ? 'is' : 'are'} not`;\n};\n\nconst problemOf = (knob: KnobEntry<string>, value: unknown): string | undefined => {\n switch (knob.kind) {\n case 'toggle':\n return typeof value === 'boolean' ? undefined : expected('true or false', value);\n case 'factor':\n case 'number':\n return isFiniteNumber(value) && isWithin(value, knob.range)\n ? undefined\n : expected(`a number${boundsText(knob.range)}`, value);\n case 'offset':\n return Array.isArray(value) &&\n value.length === 2 &&\n value.every((component) => isFiniteNumber(component) && isWithin(component, knob.range))\n ? undefined\n : expected(`an [x, y] pair of numbers${boundsText(knob.range)}`, value);\n case 'color':\n return isColorLiteral(value)\n ? undefined\n : expected('a CSS colour (#rrggbb, hsl(), rgb() or a named colour)', value);\n case 'enum':\n return typeof value === 'string' && knob.options.includes(value)\n ? undefined\n : expected(`one of ${knob.options.join(', ')}`, value);\n case 'enums':\n return listProblem('a list of its options', value, (item) => knob.options.includes(item));\n case 'colors':\n return listProblem(\n knob.options ? 'a list of CSS colours or of its options' : 'a list of CSS colours',\n value,\n (item) => isColorLiteral(item) || Boolean(knob.options?.includes(item)),\n );\n default:\n return typeof value === 'string' ? undefined : expected('a string', value);\n }\n};\n\n/**\n * Checks a value against a catalogue entry: its kind, its options, and its range as far as the\n * range's `bounds` hold.\n *\n * @remarks\n * A `color` must parse as a CSS colour. An `image` or `text` value need only be a string; whether\n * the sprite has the image is known on a loaded map only.\n *\n * @param knob - The knob's entry in its catalogue.\n * @throws `RangeError` naming the knob and what it expects, for a value it does not take.\n *\n * @example\n * ```typescript\n * const knob = trafficIncidentsKnobCatalogue.find(({ id }) => id === 'filters.magnitudes.values');\n * validateKnobValue(knob, ['major', 'severe']); // RangeError: … 'severe' is not\n * ```\n *\n * @group Shared\n */\nexport const validateKnobValue = (knob: KnobEntry<string>, value: unknown): void => {\n const problem = problemOf(knob, value);\n if (problem) throw new RangeError(`Knob '${knob.id}' ${problem}.`);\n};\n\n/**\n * What {@link setKnob}, {@link getKnob} and {@link resetKnob} act on: every map module, and a\n * plugin with knobs of its own, such as the map-effects plugin's `MapEffects`.\n *\n * @group Shared\n */\nexport type KnobTarget<CONFIG> = {\n getConfig(): CONFIG | undefined;\n /** @ignore */\n _knobValue(id: string): unknown;\n /** @ignore */\n _setKnobValue(id: string, value: unknown): void;\n /** @ignore */\n _resetKnobValue(id: string): void;\n};\n\n// Typed where the id is a literal; where it is any string, as in a panel over a whole catalogue,\n// the value is checked at run time only.\ntype KnobInput<CONFIG, KNOB extends string> = string extends KNOB ? unknown : KnobPlainValue<ValueAt<CONFIG, KNOB>>;\ntype KnobOutput<CONFIG, KNOB extends string> = string extends KNOB ? unknown : ValueAt<CONFIG, KNOB> | undefined;\n\n/**\n * A knob's entry in its catalogue: its kind, range or options, default and description.\n *\n * @throws `Error` for an id the catalogue lacks.\n *\n * @example\n * ```typescript\n * const { range } = knobEntryOf(routingKnobCatalogue, 'widthFactor');\n * ```\n *\n * @group Shared\n */\nexport const knobEntryOf = <ENTRY extends KnobEntry<string>>(catalogue: readonly ENTRY[], id: ENTRY['id']): ENTRY => {\n const knob = catalogue.find((entry) => entry.id === id);\n if (!knob) throw new Error(`Unknown knob '${id}'; its catalogue lists every id.`);\n\n return knob;\n};\n\n/**\n * Sets one knob by its catalogue id: checks the value with {@link validateKnobValue}, then applies\n * it with every other setting kept.\n *\n * @remarks\n * Nothing changes when the value is invalid. A knob's id is its dotted path into the module's\n * configuration (or, for `StylingFoundationsModule`, its key there), so the change also shows in `getConfig`\n * and fires a `config-change` event.\n *\n * @param module - A map module, or a plugin with knobs, such as `MapEffects`.\n * @param catalogue - Its knob catalogue, e.g. `trafficFlowKnobCatalogue` for a `TrafficFlowModule`.\n * @param id - The knob's id.\n * @throws `RangeError` for a value the knob does not take; `Error` for an id the catalogue lacks.\n *\n * @example\n * ```typescript\n * setKnob(trafficFlow, trafficFlowKnobCatalogue, 'filters.roadCategories.show', 'only');\n * setKnob(styling, stylingFoundationsKnobCatalogue, 'labels.sizeFactor', 1.2);\n * ```\n *\n * @group Shared\n */\nexport const setKnob = <CONFIG, ID extends string, KNOB extends ID>(\n module: KnobTarget<CONFIG>,\n catalogue: readonly KnobEntry<ID>[],\n id: KNOB,\n value: KnobInput<CONFIG, KNOB>,\n): void => {\n validateKnobValue(knobEntryOf(catalogue, id), value);\n module._setKnobValue(id, value);\n};\n\n/**\n * The value a knob is at: the one set, otherwise what the module itself is at where it knows it\n * (`StylingFoundationsModule` reads the loaded style), otherwise the catalogue's default.\n *\n * @param module - A map module, or a plugin with knobs, such as `MapEffects`.\n * @param catalogue - Its knob catalogue.\n * @param id - The knob's id.\n * @returns The value, or `undefined` where nothing gives one: a knob with no default, or a style\n * value that is a data-driven expression.\n * @throws `Error` for an id the catalogue lacks.\n *\n * @example\n * ```typescript\n * for (const knob of stylingFoundationsKnobCatalogue) console.log(knob.id, getKnob(styling, stylingFoundationsKnobCatalogue, knob.id));\n * ```\n *\n * @group Shared\n */\nexport const getKnob = <CONFIG, ID extends string, KNOB extends ID>(\n module: KnobTarget<CONFIG>,\n catalogue: readonly KnobEntry<ID>[],\n id: KNOB,\n): KnobOutput<CONFIG, KNOB> => {\n const fallback = knobEntryOf(catalogue, id).default;\n return (module._knobValue(id) ?? fallback) as KnobOutput<CONFIG, KNOB>;\n};\n\n/**\n * Unsets one knob, so the module is back at its own value for it, as {@link getKnob} reads it.\n * To unset every knob, use the module's `resetConfig`.\n *\n * @param module - A map module, or a plugin with knobs, such as `MapEffects`.\n * @param catalogue - Its knob catalogue.\n * @param id - The knob's id.\n * @throws `Error` for an id the catalogue lacks.\n *\n * @example\n * ```typescript\n * resetKnob(styling, stylingFoundationsKnobCatalogue, 'labels.sizeFactor');\n * ```\n *\n * @group Shared\n */\nexport const resetKnob = <CONFIG, ID extends string, KNOB extends ID>(\n module: KnobTarget<CONFIG>,\n catalogue: readonly KnobEntry<ID>[],\n id: KNOB,\n): void => {\n knobEntryOf(catalogue, id);\n module._resetKnobValue(id);\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-org/maps-sdk/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-org/maps-sdk/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-org/maps-sdk/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-org/maps-sdk/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-org/maps-sdk/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-org/maps-sdk/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-org/maps-sdk/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","/**\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.create(map);\n * // Uses mapStyleLayerIDs.lowestLabel by default; `beforeLayerConfig` moves the route lines\n * const underRoads = await RoutingModule.create(map, { beforeLayerConfig: { mainLines: 'lowestRoadLine' } });\n * ```\n *\n * **GeometriesModule** - Configurable layer positioning for polygon areas:\n * ```typescript\n * // Default: on top of the map, below the geometry titles\n * const geometriesModule = await GeometriesModule.create(map);\n *\n * // Custom: below buildings to show at ground level\n * const geometriesModule = await GeometriesModule.create(map, {\n * beforeLayerConfig: 'lowestBuilding'\n * });\n *\n * // On top of everything\n * const geometriesModule = await GeometriesModule.create(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 * - `lowestRoadLine` and `lowestBuilding` are not available in the Satellite style\n * - If a reference layer doesn't exist, a module repositioning its layers puts them on top of the\n * style instead. A layer added with a `beforeID` that never appears is reported through\n * `console.error` and skipped, since a missing anchor there is usually a typo.\n * - RoutingModule draws its lines below `lowestLabel` by default; GeometriesModule adds its layers on top\n *\n * @see {@link GeometryBeforeLayerConfig} for GeometriesModule layer positioning options\n * @see {@link RoutingBeforeLayerConfig} for RoutingModule layer positioning options\n * @see {@link PlacesBeforeLayerConfig} for PlacesModule layer positioning options\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 * **Note:** Not available in the Satellite style.\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 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 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. The base map\n * tiles carry them, so this is {@link BASE_MAP_SOURCE_ID} under the name the\n * POI layers are selected by.\n *\n * @group POIs\n */\nexport const POI_SOURCE_ID = BASE_MAP_SOURCE_ID;\n\n/**\n * Source identifier of the style's elevation data, added by the `hillshade` style part.\n *\n * @remarks\n * A raster-dem source, drawn as the hillshade layer. {@link TerrainModule} raises the 3D surface\n * from a copy of it, {@link TERRAIN_SOURCE_ID}.\n *\n * @group Terrain\n */\nexport const HILLSHADE_SOURCE_ID = 'hillshade';\n\n/**\n * Source identifier of the 3D terrain surface: a copy of {@link HILLSHADE_SOURCE_ID} that\n * {@link TerrainModule} adds when elevation is first enabled.\n *\n * @remarks\n * MapLibre renders hillshade and 3D terrain at a lower quality when both share one source.\n *\n * @group Terrain\n */\nexport const TERRAIN_SOURCE_ID = 'hillshade-terrain';\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","import type { TomTomMap } from '../TomTomMap';\nimport { ofMap } from './ofMap';\n\n/**\n * Per-map, per-class module instances.\n *\n * A `WeakMap` keyed on the map means a module lives exactly as long as the map it controls, and\n * needs no explicit teardown: drop the map and its modules go with it.\n *\n * The **promise** is stored rather than the instance, because `get()` is asynchronous. Two callers\n * that ask before the map is ready would otherwise both miss the cache and build a second\n * controller over the same style layers — precisely what sharing exists to prevent.\n */\nconst instancesByMap = new WeakMap<TomTomMap, Map<Function, Promise<unknown>>>();\n\n/**\n * Returns the shared instance of a style-owned module for this map, building it on first use.\n *\n * Style-owned modules control sources and layers the map style already provides, under fixed\n * global IDs, so a second instance would be a second controller over one piece of shared state.\n * Callers get the same instance instead — two components each asking for the terrain is reasonable\n * code, and making them hoist a shared reference would be work pushed onto them for no benefit.\n *\n * @param map The map the module belongs to.\n * @param moduleClass The concrete module class, which together with the map identifies the instance.\n * @param construct Builds the instance. Called at most once per map and class.\n * @param applyToExisting Applies the caller's configuration to an instance that already exists.\n * Omit it when the caller passed no configuration: `get(map)` is a plain accessor and must not\n * re-apply defaults over settings someone else already made.\n *\n * @ignore\n */\nexport const sharedInstance = async <MODULE>(\n map: TomTomMap,\n moduleClass: Function,\n construct: () => MODULE,\n applyToExisting?: (existing: MODULE) => void,\n): Promise<MODULE> => {\n const instances = ofMap(instancesByMap, map, () => new Map());\n const existing = instances.get(moduleClass) as Promise<MODULE> | undefined;\n if (existing) {\n const instance = await existing;\n applyToExisting?.(instance);\n return instance;\n }\n\n // Stored before awaiting anything, so a concurrent call joins this promise instead of\n // starting a second construction.\n const created = Promise.resolve().then(construct);\n instances.set(moduleClass, created);\n try {\n return await created;\n } catch (error) {\n // A failed construction must not be cached: the next call should be able to try again,\n // for instance once the style carrying the module's source has loaded.\n instances.delete(moduleClass);\n throw error;\n }\n};\n","import type { LayerSpecification } from 'maplibre-gl';\n\n/**\n * Picks style layers by the taxonomy TomTom ships with the style rather than by their (unstable)\n * ids: `metadata.group` first, then optional narrowing by layer type, source layer and\n * case-insensitive id fragments. Every constraint given must hold.\n *\n * This is the one matching rule behind the base-map layer groups and the styling knobs, so a\n * curated map is a small data table rather than code, and a style-version bump is checked by\n * running the same tables against the new style.\n *\n * @ignore\n */\nexport type LayerSelector = {\n /** Values of `metadata.group` the layer may carry. Omit to accept any group. */\n metadataGroups?: string[];\n /** Layer types the layer may have. */\n layerTypes?: LayerSpecification['type'][];\n /** Sources the layer must draw from. */\n sources?: string[];\n /**\n * Source types (`vector`, `raster`, `geojson`…) the layer's source may have. Needs the style's\n * sources to be passed along when matching; without them this constraint cannot hold.\n */\n sourceTypes?: string[];\n /** Source layers (vector tile layers) the layer must draw from. */\n sourceLayers?: string[];\n /** The layer id must contain at least one of these fragments (case-insensitive). */\n idIncludes?: string[];\n /** The layer id must contain none of these fragments (case-insensitive). */\n idExcludes?: string[];\n};\n\n/**\n * @ignore\n */\nexport const metadataGroupOf = (layer: LayerSpecification): string | undefined => {\n const metadata = layer.metadata;\n if (metadata && typeof metadata === 'object' && 'group' in metadata) {\n const group = (metadata as { group?: unknown }).group;\n return typeof group === 'string' ? group : undefined;\n }\n return undefined;\n};\n\nconst sourceOf = (layer: LayerSpecification): string | undefined =>\n 'source' in layer && typeof layer.source === 'string' ? layer.source : undefined;\n\nconst sourceLayerOf = (layer: LayerSpecification): string | undefined =>\n 'source-layer' in layer && typeof layer['source-layer'] === 'string' ? layer['source-layer'] : undefined;\n\n/**\n * The style's sources, for selectors that constrain the source type.\n * @ignore\n */\nexport type StyleSources = Record<string, { type: string }>;\n\nconst sourceTypeOf = (layer: LayerSpecification, styleSources?: StyleSources): string | undefined => {\n const source = sourceOf(layer);\n return source === undefined ? undefined : styleSources?.[source]?.type;\n};\n\n// Every list constraint is optional in the same way: an omitted list accepts any layer, a given one\n// has to hold the value the layer carries — and a layer carrying nothing there never matches.\nconst matchesOneOf = <VALUE>(allowed: readonly VALUE[] | undefined, value: VALUE | undefined): boolean =>\n !allowed || (value !== undefined && allowed.includes(value));\n\nconst matchesIdFragments = (selector: LayerSelector, layer: LayerSpecification): boolean => {\n const id = layer.id.toLowerCase();\n const isInId = (fragment: string) => id.includes(fragment.toLowerCase());\n if (selector.idIncludes && !selector.idIncludes.some(isInId)) return false;\n\n return !selector.idExcludes?.some(isInId);\n};\n\n/**\n * Whether a layer satisfies every constraint of the selector.\n * @ignore\n */\nexport const matchesLayerSelector = (\n selector: LayerSelector,\n layer: LayerSpecification,\n styleSources?: StyleSources,\n): boolean =>\n matchesOneOf(selector.metadataGroups, metadataGroupOf(layer)) &&\n matchesOneOf(selector.layerTypes, layer.type) &&\n matchesOneOf(selector.sources, sourceOf(layer)) &&\n matchesOneOf(selector.sourceTypes, sourceTypeOf(layer, styleSources)) &&\n matchesOneOf(selector.sourceLayers, sourceLayerOf(layer)) &&\n matchesIdFragments(selector, layer);\n\n/**\n * Whether a layer satisfies at least one of the selectors.\n * @ignore\n */\nexport const matchesAnyLayerSelector = (\n selectors: LayerSelector[],\n layer: LayerSpecification,\n styleSources?: StyleSources,\n): boolean => selectors.some((selector) => matchesLayerSelector(selector, layer, styleSources));\n","import { get } from 'lodash-es';\nimport type { LayerSpecification } from 'maplibre-gl';\nimport { type LayerSelector, matchesAnyLayerSelector } from './layerSelector';\nimport type { LayerVisibilityComposer } from './layerVisibilityComposer';\n\n/**\n * The parts of a style-owned module's layers its configuration shows or hides on their own, each\n * by its knob id — its path into the configuration. A part has its say through the visibility\n * composer under its own key, so hiding one never shows what the module hides, and the module\n * showing its layers never brings back a part that is off.\n *\n * Unset, a part is drawn as the style ships it, which {@link valueOf} reports.\n *\n * @ignore\n */\nexport class LayerPartToggles<ID extends string> {\n private layerIds = new Map<string, string[]>();\n private styleVisibility = new Map<string, boolean>();\n\n constructor(\n // Prefixes each part's key with the visibility composer.\n private readonly contributor: string,\n private readonly selectors: Readonly<Record<ID, LayerSelector[]>>,\n ) {}\n\n /** Finds each part among the module's layers in the loaded style. */\n index(layers: readonly LayerSpecification[]): void {\n this.layerIds = new Map();\n this.styleVisibility = new Map();\n for (const [id, selectors] of Object.entries<LayerSelector[]>(this.selectors)) {\n const matched = layers.filter((layer) => matchesAnyLayerSelector(selectors, layer));\n this.layerIds.set(\n id,\n matched.map((layer) => layer.id),\n );\n if (matched.length) this.styleVisibility.set(id, matched[0].layout?.visibility !== 'none');\n }\n }\n\n /** Has every part say what the configuration sets for it, withdrawing the say of those it leaves unset. */\n apply(composer: LayerVisibilityComposer, config: object | undefined): void {\n for (const [id, layerIds] of this.layerIds) {\n const visible: boolean | undefined = get(config, id);\n for (const layerId of layerIds) composer.setVisible(layerId, `${this.contributor}.${id}`, visible);\n }\n }\n\n /** Whether the style ships a part shown; `undefined` for an id not among them, or a part it lacks. */\n valueOf(id: string): boolean | undefined {\n return this.styleVisibility.get(id);\n }\n}\n","import type { Map } from 'maplibre-gl';\nimport type { StylePart } from '../init';\nimport type { TomTomMap } from '../TomTomMap';\nimport { notInTheStyle } from './errorMessages';\nimport { ensureAddedToStyle, waitUntilMapIsReady } from './mapUtils';\nimport { StyleSourceWithLayers } from './SourceWithLayers';\nimport { sharedInstance } from './sharedInstance';\nimport type { LayerSpecFilter } from './types/mapsSDKLayerSpecs';\n\n/**\n * What a style-owned module's `get()` resolves to: its shared instance for the map once the map is\n * ready and, given `stylePart`, once the style carries that part's source. A `config` is applied to\n * an instance that already exists as well.\n * @ignore\n */\nexport const getStyleOwnedModule = async <CONFIG, MODULE extends { applyConfig(config: CONFIG): void }>(\n map: TomTomMap,\n moduleClass: Function,\n construct: () => MODULE,\n config: CONFIG | undefined,\n stylePart?: { sourceId: string; part: StylePart },\n): Promise<MODULE> => {\n await waitUntilMapIsReady(map);\n if (stylePart) await ensureAddedToStyle(map, stylePart.sourceId, stylePart.part);\n\n return sharedInstance(map, moduleClass, construct, config ? (existing) => existing.applyConfig(config) : undefined);\n};\n\n/**\n * The style's source a style-owned module controls, with its layers `layerFilter` keeps.\n * @throws when the loaded style lacks the source.\n * @ignore\n */\nexport const styleSourceWithLayers = (\n mapLibreMap: Map,\n sourceId: string,\n moduleName: string,\n layerFilter?: LayerSpecFilter,\n): StyleSourceWithLayers => {\n const source = mapLibreMap.getSource(sourceId);\n if (!source) throw notInTheStyle(`init ${moduleName} with source ID ${sourceId}`);\n\n return new StyleSourceWithLayers(mapLibreMap, source, layerFilter);\n};\n","import { mapStyleLayerIDs } from '../../shared';\n\n/**\n * Layer IDs for POIs on the map.\n * @ignore\n */\nexport const poiLayerIDs: string[] = [mapStyleLayerIDs.poi, `${mapStyleLayerIDs.poi} - Micro`];\n","import type { MapModuleCommonConfig, ValuesFilter } 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 * Ordered by z-order in the TomTom base-map style, from the layers painted\n * lowest (underneath) to highest (on top). The place-label order mirrors the\n * style's own draw order rather than the settlement hierarchy.\n *\n * @see {@link BaseMapLayerGroupName}\n *\n * @group Base Map\n */\nexport const baseMapLayerGroupNames = [\n 'land',\n 'water',\n 'buildings2D',\n 'roads',\n 'railways',\n 'ferries',\n 'borders',\n 'buildings3D',\n 'natureLabels',\n 'roadLabels',\n 'roadShields',\n 'houseNumbers',\n 'smallerTownLabels',\n 'stateLabels',\n 'cityLabels',\n 'allPlaceLabels',\n 'capitalLabels',\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 * Layers are classified by the style's `metadata.group`, so these groups track\n * the TomTom base-map style rather than any layer id naming.\n *\n * **Surfaces & nature:**\n * - `land` - Land use & land cover areas (and the map background)\n * - `water` - Water bodies (oceans, lakes, rivers)\n * - `natureLabels` - Natural feature labels (peaks, islands, rivers, water areas)\n *\n * **Transport geometry:**\n * - `roads` - Road lines (motorway…street, service, paths, tracks), their arrows,\n * and road/transit surface areas (pedestrian areas, runways, platforms, piers)\n * - `railways` - Railways (rail, light rail, subway, tram) and aerial cableways\n * - `ferries` - Ferry connections\n *\n * **Buildings:**\n * - `buildings2D` - 2D building footprints\n * - `buildings3D` - 3D building models\n *\n * **Boundaries:**\n * - `borders` - Administrative boundaries, overlays (military/protected) and their labels\n *\n * **Text:**\n * - `roadLabels` - Road name labels\n * - `roadShields` - Highway shields (e.g., I-95, A1)\n * - `houseNumbers` - House number labels\n *\n * **Place labels:**\n * - `allPlaceLabels` - All place labels (superset of the settlement/admin groups below)\n * - `smallerTownLabels` - Neighbourhood, village, hamlet & town 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 = 'roads';\n * const labels: BaseMapLayerGroupName[] = ['cityLabels', 'countryLabels'];\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroupName = (typeof baseMapLayerGroupNames)[number];\n\n/**\n * Which base map layer groups a call reaches: the {@link ValuesFilter} POIs and traffic filter by.\n *\n * @remarks\n * - `show: 'only'`: the `values` groups.\n * - `show: 'all-except'`: every layer outside the `values` groups, which\n * {@link BaseMapModule.setVisible} leaves as they were.\n *\n * @example\n * ```typescript\n * // Show only roads and borders\n * const roadsOnly: BaseMapLayerGroups = { show: 'only', values: ['roads', 'roadLabels', 'borders'] };\n *\n * // Show everything except buildings\n * const noBuildings: BaseMapLayerGroups = { show: 'all-except', values: ['buildings2D', 'buildings3D'] };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapLayerGroups = ValuesFilter<BaseMapLayerGroupName>;\n\n/**\n * The parts of the roads {@link BaseMapModule} shows or hides on their own, finer than the layer\n * groups: the granularity TomTom's style designers chose as toggle-worthy. Unset, a part is drawn\n * as the style ships it.\n *\n * @group Base Map\n */\nexport type BaseMapRoadsConfig = {\n /** Motorway exit number badges, in `roadLabels`. */\n exitNumbers?: { visible?: boolean };\n /** One-way arrows drawn on roads, in `roads`. */\n arrows?: { visible?: boolean };\n /** Dashed marking of roads with restricted access, in `roads`. */\n restricted?: { visible?: boolean };\n /** Roads under construction, in `roads`. */\n underConstruction?: { visible?: boolean };\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 * `visible` applies to every base-map layer, then each entry of `groups` to its group's layers, in\n * the order given — so a later entry wins where two groups share layers (`allPlaceLabels` and\n * `cityLabels`). Whatever neither sets is drawn as the style ships it, and so is a group whose entry\n * leaves `visible` out. {@link BaseMapModule.setVisible} writes the same record, so `getConfig()`\n * reproduces every call.\n *\n * Showing a group shows every layer in it, including the ones the style ships hidden (such as\n * detailed road markings); leave a group out, or its `visible` unset, to keep the style's choice.\n *\n * @example\n * ```typescript\n * // Hide the buildings\n * const config: BaseMapModuleConfig = {\n * groups: { buildings2D: { visible: false }, buildings3D: { visible: false } }\n * };\n *\n * // Hide everything but water and land\n * const minimalConfig: BaseMapModuleConfig = {\n * visible: false,\n * groups: { water: { visible: true }, land: { visible: true } }\n * };\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapModuleConfig = MapModuleCommonConfig & {\n /**\n * Shows or hides every base-map layer. Unset, each layer is drawn as the style ships it.\n */\n visible?: boolean;\n\n /**\n * Shows or hides one layer group each, over `visible`, in the order given. An entry without\n * `visible` draws its group as the style ships it.\n *\n * @example\n * ```typescript\n * groups: { buildings3D: { visible: true }, houseNumbers: { visible: false } }\n *\n * // Only the roads, drawn as the style ships them\n * visible: false, groups: { roads: {} }\n * ```\n */\n groups?: Partial<Record<BaseMapLayerGroupName, { visible?: boolean }>>;\n\n /**\n * Shows or hides parts of the roads on their own. A part never shows while its group is hidden.\n */\n roads?: BaseMapRoadsConfig;\n};\n","import type { LayerSpecification } from 'maplibre-gl';\nimport { poiLayerIDs } from '../pois/layers/poisLayers';\nimport type { LayerSpecFilter } from '../shared';\nimport { type LayerSelector, matchesLayerSelector } from '../shared/layers/layerSelector';\nimport {\n type BaseMapLayerGroupName,\n type BaseMapLayerGroups,\n baseMapLayerGroupNames,\n} from './types/baseMapModuleConfig';\n\n// Layers are classified by the style's `metadata.group` — the authoritative\n// taxonomy TomTom ships with the style — rather than by their (unstable) layer\n// ids. `idIncludes`/`idExcludes` only sub-split a single metadata group (roads\n// and place labels), and `layerTypes` disambiguates where a metadata group mixes\n// geometry types. The matching rule itself is shared with the styling knobs.\ntype LayerGroupMapping = LayerSelector & { metadataGroups: string[] };\n\n// Rail/ferry/aerialway lines carved out of the `road` metadata group by layer id.\nconst railAndWaterTransitIDs = ['railway', 'subway', 'aerialway', 'ferry'];\n\nconst layerGroupMappings: Record<BaseMapLayerGroupName, LayerGroupMapping> = {\n // 'area' fills plus the base 'background' canvas.\n land: { metadataGroups: ['area', 'background'] },\n water: { metadataGroups: ['water'] },\n natureLabels: { metadataGroups: ['label'] },\n\n // All road geometry (incl. paths, tracks and road/transit surface areas)\n // except rail/ferry/aerialway lines, which get their own groups.\n roads: { metadataGroups: ['road', 'road_area', 'transit_area'], idExcludes: railAndWaterTransitIDs },\n railways: { metadataGroups: ['road'], layerTypes: ['line'], idIncludes: ['railway', 'subway', 'aerialway'] },\n ferries: { metadataGroups: ['road'], layerTypes: ['line'], idIncludes: ['ferry'] },\n\n buildings2D: { metadataGroups: ['building'], layerTypes: ['fill', 'line'] },\n buildings3D: { metadataGroups: ['area_3d', 'building'], layerTypes: ['fill-extrusion'] },\n\n // Admin boundaries, overlays (military/protected) and boundary labels.\n borders: { metadataGroups: ['border', 'border_label'] },\n\n // `road_label` split into shields vs. everything else.\n roadLabels: { metadataGroups: ['road_label'], idExcludes: ['shield'] },\n roadShields: { metadataGroups: ['road_label'], idIncludes: ['shield'] },\n houseNumbers: { metadataGroups: ['address_point_label'] },\n\n // `allPlaceLabels` is the superset of the whole `places_label` group; the\n // groups below are narrower sub-selections of it, split by layer id.\n allPlaceLabels: { metadataGroups: ['places_label'] },\n smallerTownLabels: { metadataGroups: ['places_label'], idIncludes: ['neighbourhood', 'village', 'hamlet', 'town'] },\n cityLabels: { metadataGroups: ['places_label'], idIncludes: ['city'] },\n capitalLabels: { metadataGroups: ['places_label'], idIncludes: ['capital'] },\n stateLabels: { metadataGroups: ['places_label'], idIncludes: ['state'] },\n countryLabels: { metadataGroups: ['places_label'], idIncludes: ['country'] },\n};\n\nconst isMatching = (group: BaseMapLayerGroupName, layer: LayerSpecification) =>\n matchesLayerSelector(layerGroupMappings[group], layer);\n\n/**\n * The selector behind a base-map layer group, for other curated maps (the styling knobs) that\n * want to build on the same taxonomy instead of restating it.\n * @ignore\n */\nexport const baseMapLayerGroupSelector = (group: BaseMapLayerGroupName): LayerSelector => layerGroupMappings[group];\n\n/**\n * @ignore\n */\nexport const buildLayerGroupFilter = ({ show, values }: BaseMapLayerGroups): LayerSpecFilter => {\n const inGroups: LayerSpecFilter = (layer) => values.some((group) => isMatching(group, layer));\n return show === 'only' ? inGroups : (layer) => !inGroups(layer);\n};\n\n/**\n * The layers the base map module manages: everything in the base-map source except the POI\n * layers, which {@link POIsModule} owns.\n *\n * @ignore\n */\nexport const baseMapLayerFilter: LayerSpecFilter = (layer: LayerSpecification): boolean =>\n !poiLayerIDs.includes(layer.id);\n\n/**\n * Classifies the given layers into every base-map layer group they belong to,\n * using the same `metadata.group` matching as the visibility filters.\n *\n * The result has an entry for every {@link BaseMapLayerGroupName} (empty array\n * when no layer matches), so callers get a stable, fully-keyed record. Layer ids\n * keep their style z-order within each group. Groups intentionally overlap — e.g.\n * a city label is in both `cityLabels` and `allPlaceLabels` — so a layer id can\n * appear under several groups.\n *\n * @ignore\n */\nexport const groupBaseMapLayers = (layers: LayerSpecification[]): Record<BaseMapLayerGroupName, string[]> => {\n const grouped = Object.fromEntries(baseMapLayerGroupNames.map((group) => [group, [] as string[]])) as Record<\n BaseMapLayerGroupName,\n string[]\n >;\n for (const layer of layers) {\n for (const group of baseMapLayerGroupNames) {\n if (isMatching(group, layer)) grouped[group].push(layer.id);\n }\n }\n return grouped;\n};\n","import { mapValues, omit } from 'lodash-es';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport {\n AbstractStyleOwnedMapModule,\n BASE_MAP_SOURCE_ID,\n type CombinedEvents,\n type LayerSpecFilter,\n type ResolvedEventScope,\n type StyleSourceWithLayers,\n} from '../shared';\nimport { LayerPartToggles } from '../shared/layers/layerPartToggles';\nimport type { LayerSelector } from '../shared/layers/layerSelector';\nimport { getStyleOwnedModule, styleSourceWithLayers } from '../shared/styleOwnedModules';\nimport type { TomTomMap } from '../TomTomMap';\nimport { baseMapLayerFilter, buildLayerGroupFilter, groupBaseMapLayers } from './layerGroups';\nimport {\n type BaseMapLayerGroupName,\n type BaseMapLayerGroups,\n type BaseMapModuleConfig,\n type BaseMapRoadsConfig,\n baseMapLayerGroupNames,\n} from './types/baseMapModuleConfig';\n\ntype BaseSourceAndLayers = {\n vectorTiles: StyleSourceWithLayers;\n};\n\ntype GroupsVisibility = NonNullable<BaseMapModuleConfig['groups']>;\ntype LayersByGroup = Record<BaseMapLayerGroupName, string[]>;\n\n// This module's key with the visibility composer, next to the road parts it toggles on their own.\nconst VISIBILITY_CONTRIBUTOR = 'baseMap.visible';\n\n// The road parts the designers made toggle-worthy, by their knob ids.\nconst roadPartSelectors: Record<`roads.${keyof BaseMapRoadsConfig}.visible`, LayerSelector[]> = {\n 'roads.exitNumbers.visible': [{ metadataGroups: ['road_label'], layerTypes: ['symbol'], idIncludes: ['exit'] }],\n 'roads.arrows.visible': [{ metadataGroups: ['road'], layerTypes: ['symbol'], idIncludes: ['arrow'] }],\n 'roads.restricted.visible': [{ metadataGroups: ['road'], layerTypes: ['line'], idIncludes: ['restricted'] }],\n 'roads.underConstruction.visible': [\n { metadataGroups: ['road'], layerTypes: ['line'], idIncludes: ['construction'] },\n ],\n};\n\nconst isGroupName = (name: string): name is BaseMapLayerGroupName =>\n (baseMapLayerGroupNames as readonly string[]).includes(name);\n\nconst groupsVisible = (names: readonly BaseMapLayerGroupName[], visible: boolean): GroupsVisibility =>\n Object.fromEntries(names.map((name) => [name, { visible }]));\n\n// The groups of a record in the order they apply, each with its visibility; `undefined` for a group\n// drawn as the style ships it.\nconst groupEntries = (groups: GroupsVisibility | undefined): [BaseMapLayerGroupName, boolean | undefined][] =>\n Object.keys(groups ?? {})\n .filter(isGroupName)\n .flatMap((name) => {\n const entry = groups?.[name];\n return entry ? [[name, entry.visible]] : [];\n });\n\nconst containsAll = (outer: readonly string[], inner: readonly string[]) =>\n inner.every((layerId) => outer.includes(layerId));\n\n// The record's say over the named groups, rewritten onto them alone so `visible` can change around\n// them: an entry within them kept, one over a group holding a named group narrowed to it. Base-map\n// groups either nest or are disjoint, so no entry reaches part of a named group only.\nconst groupsAsRecorded = (\n config: BaseMapModuleConfig | undefined,\n names: readonly BaseMapLayerGroupName[],\n layersByGroup: LayersByGroup,\n): GroupsVisibility => {\n const namedLayers = names.flatMap((name) => layersByGroup[name]);\n const says = new Map<BaseMapLayerGroupName, boolean | undefined>();\n const say = (group: BaseMapLayerGroupName, visible: boolean | undefined) => {\n says.delete(group);\n says.set(group, visible);\n };\n for (const name of names) say(name, config?.visible);\n for (const [group, visible] of groupEntries(config?.groups)) {\n const narrowed = containsAll(namedLayers, layersByGroup[group])\n ? [group]\n : names.filter((name) => containsAll(layersByGroup[group], layersByGroup[name]));\n for (const target of narrowed) say(target, visible);\n }\n return Object.fromEntries([...says].map(([group, visible]) => [group, visible === undefined ? {} : { visible }]));\n};\n\n// The record after a call over some groups. `only` sets each named group, moving it last so it\n// wins over an earlier overlapping one; `all-except` sets every layer outside the named groups, which\n// replaces whatever the record held for them, and keeps the named groups as they were.\nconst recordAfter = (\n config: BaseMapModuleConfig | undefined,\n { show, values }: BaseMapLayerGroups,\n visible: boolean,\n layersByGroup: LayersByGroup,\n): BaseMapModuleConfig =>\n show === 'all-except'\n ? { ...config, visible, groups: groupsAsRecorded(config, values, layersByGroup) }\n : { ...config, groups: { ...omit(config?.groups, values), ...groupsVisible(values, visible) } };\n\n/**\n * Scope accepted by `baseMap.events.where(...)`, in addition to a plain feature predicate.\n *\n * `layerGroups` narrows to part of the map — road labels, water, buildings — using the same\n * {@link BaseMapLayerGroupName} vocabulary as `setVisible`. `features` narrows within those\n * layers, and the two combine.\n *\n * @example\n * ```typescript\n * baseMap.events\n * .where({ layerGroups: { show: 'only', values: ['roadLabels', 'roadShields'] } },\n * { cursorOnHover: 'pointer' })\n * .on('click', (feature) => showRoadName(feature.properties.name));\n * ```\n *\n * @group Base Map\n */\nexport type BaseMapEventScope = {\n /** Which layer groups this scope covers. */\n layerGroups: BaseMapLayerGroups;\n /** Optional further narrowing to particular features within those layers. */\n features?: (feature: MapGeoJSONFeature) => boolean;\n};\n\n/**\n * Event surface of {@link BaseMapModule}. The base map manages one source, so it has no named\n * scopes — `events` is that scope, narrowed on demand with `where()`.\n *\n * @group Base Map\n */\nexport type BaseMapEvents = CombinedEvents<MapGeoJSONFeature, BaseMapModuleConfig, never, BaseMapEventScope>;\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 * **What this module owns:** whether each base-map layer group is shown, and the road parts within\n * them (`roads.arrows.visible`, `roads.exitNumbers.visible`, …), which never show in a group it hides. How the base\n * map looks is {@link StylingFoundationsModule} knobs. See [Who owns what](https://docs.tomtom.com/maps-sdk-js/guides/map/modules#who-owns-what).\n * {@link baseMapKnobCatalogue} lists every setting of its configuration.\n *\n * **Managed Layers:**\n * - Background and land cover\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 and 3D terrain (use {@link TerrainModule})\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 * const baseMap = await BaseMapModule.get(map);\n *\n * // Hide buildings and labels\n * baseMap.setVisible(false, {\n * layerGroups: {\n * show: 'only',\n * values: ['buildings2D', 'buildings3D', 'allPlaceLabels']\n * }\n * });\n *\n * // Show only water and land\n * baseMap.setVisible(true, {\n * layerGroups: {\n * show: 'only',\n * values: ['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/styles)\n *\n * @group Base Map\n */\nexport class BaseMapModule extends AbstractStyleOwnedMapModule<BaseSourceAndLayers, BaseMapModuleConfig> {\n // Layer ids per group for the managed layers, computed lazily from the current\n // style and reset whenever the module (re)initializes its sources.\n private layersByGroup?: LayersByGroup;\n // No initializer: the base constructor indexes the style, and an initializer would run after it.\n private roadParts!: LayerPartToggles<keyof typeof roadPartSelectors>;\n\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 * **Instances:**\n * `BaseMapModule` controls the base-map source and layers the map style already provides, under\n * fixed global IDs, so every map has exactly one — a second `get()` returns the same instance.\n * To work with part of the map, name the layer groups per call: `setVisible(false, {\n * layerGroups })` for visibility, `events.where({ layerGroups })` for events.\n *\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 * - `groups`: Visibility per layer 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 * groups: { buildings3D: { visible: false }, houseNumbers: { visible: false } },\n * roads: { arrows: { visible: false }, exitNumbers: { visible: false } }\n * });\n * ```\n */\n static get(tomtomMap: TomTomMap, config?: BaseMapModuleConfig): Promise<BaseMapModule> {\n return getStyleOwnedModule(tomtomMap, BaseMapModule, () => new BaseMapModule(tomtomMap, config), config);\n }\n\n private constructor(map: TomTomMap, config?: BaseMapModuleConfig) {\n super(map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const vectorTiles = styleSourceWithLayers(\n this.mapLibreMap,\n BASE_MAP_SOURCE_ID,\n BaseMapModule.name,\n baseMapLayerFilter,\n );\n\n // Runs on init and on every style change, so the group index recomputes for the new style.\n this.layersByGroup = undefined;\n this.roadParts = new LayerPartToggles('baseMap', roadPartSelectors);\n this.roadParts.index(vectorTiles._layerSpecs);\n\n return { vectorTiles };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: BaseMapModuleConfig | undefined) {\n // Until something is set the layers are as the style ships them, so there is nothing to write.\n if (config || !this._initializing) this.writeVisibility(config);\n\n return config;\n }\n\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return super._knobValue(id) ?? this.roadParts.valueOf(id);\n }\n\n /**\n * Whether the base map, or the layer groups asked about, is set to be shown.\n *\n * @param options - Optional settings for fine-grained control.\n * @param options.layerGroups - Ask about specific layer groups instead of all layers.\n *\n * @returns `true` if at least one of the layers asked about is set to show, `false` if all are\n * set to hide.\n *\n * @remarks\n * Reads the settings, not the map: `visible`, then the `groups` over it, the last that reaches a\n * layer winning — the same order {@link setVisible} writes them in.\n *\n * A layer none of them reaches answers with what the style draws, which is how a per-group\n * control reflects the state the style starts in.\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 * @example\n * Ask about one group:\n * ```typescript\n * // buildings3D ships hidden in most styles\n * const shown = baseMap.isVisible({ layerGroups: { show: 'only', values: ['buildings3D'] } });\n * ```\n */\n isVisible(options?: { layerGroups?: BaseMapLayerGroups }): boolean {\n const settings = this.layerSettings(\n this.config,\n options?.layerGroups && buildLayerGroupFilter(options.layerGroups),\n );\n return (\n [...settings.values()].includes(true) ||\n this.sourcesWithLayers.vectorTiles.isAnyLayerVisible(\n (layer) => settings.has(layer.id) && settings.get(layer.id) === undefined,\n )\n );\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` showing `only` its `values`: Affects only those layer groups\n * - Showing `all-except` them: Affects every layer outside those groups, which stay as they were\n * - Changes are applied immediately if map is ready\n *\n * Every call is kept in the configuration's `groups` (or `visible`), so a style change restores\n * all of them. The road parts, such as `roads.arrows.visible`, stay set.\n *\n * **Layer Groups:**\n * Available groups: `land`, `water`, `buildings2D`, `roads`, `railways`,\n * `ferries`, `borders`, `buildings3D`, `natureLabels`, `roadLabels`,\n * `roadShields`, `houseNumbers`, `smallerTownLabels`, `stateLabels`,\n * `cityLabels`, `allPlaceLabels`, `capitalLabels`, `countryLabels`.\n * See {@link BaseMapLayerGroupName}.\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 * show: 'only',\n * values: ['buildings2D', 'buildings3D']\n * }\n * });\n *\n * // Show everything except labels\n * baseMap.setVisible(true, {\n * layerGroups: {\n * show: 'all-except',\n * values: ['allPlaceLabels', '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 const layerGroups = options?.layerGroups;\n this.applyConfig(\n layerGroups\n ? recordAfter(this.config, layerGroups, visible, this.groupedLayers())\n : { ...omit(this.config, 'groups'), visible },\n );\n }\n\n private writeVisibility(record: BaseMapModuleConfig | undefined): void {\n if (!this.tomtomMap.mapReady) return;\n\n for (const [layerId, visible] of this.layerSettings(record)) {\n this.visibilityComposer.setVisible(layerId, VISIBILITY_CONTRIBUTOR, visible);\n }\n this.roadParts.apply(this.visibilityComposer, record);\n }\n\n // This module's say over each layer `within` selects: `visible`, then each group of the record\n // in order — the last of them that reaches a layer wins, and `undefined` leaves the layer to the\n // style.\n private layerSettings(\n record: BaseMapModuleConfig | undefined,\n within?: LayerSpecFilter,\n ): Map<string, boolean | undefined> {\n const layersByGroup = this.groupedLayers();\n const overrides = new Map<string, boolean | undefined>();\n for (const [group, visible] of groupEntries(record?.groups)) {\n for (const layerId of layersByGroup[group]) overrides.set(layerId, visible);\n }\n const settings = new Map<string, boolean | undefined>();\n for (const layer of this.sourcesWithLayers.vectorTiles._layerSpecs) {\n if (within && !within(layer)) continue;\n\n settings.set(layer.id, overrides.has(layer.id) ? overrides.get(layer.id) : record?.visible);\n }\n return settings;\n }\n\n /**\n * The base-map layer ids grouped by {@link BaseMapLayerGroupName}, for the\n * layers this module manages in the current style.\n *\n * @returns A record with an entry for every layer group (empty array when the\n * style has no layers for it). Layer ids keep their style draw order within\n * each group. Groups overlap by design (e.g. a city label is in both\n * `cityLabels` and `allPlaceLabels`), so a layer id can appear under several.\n *\n * @remarks\n * Computed lazily on first access and cached until the module reinitializes\n * (e.g. after a style change), so it always reflects the current style. The\n * returned record and its arrays are copies — mutate them freely, it won't\n * affect the module or a later call.\n *\n * @example\n * ```typescript\n * const layers = baseMap.getLayers();\n * console.log(layers.water); // ['Water - Fill', 'Water - Line', …]\n * console.log(layers.roads); // road line + surface-area layer ids\n * ```\n */\n getLayers(): Record<BaseMapLayerGroupName, string[]> {\n return mapValues(this.groupedLayers(), (layerIds) => [...layerIds]);\n }\n\n /**\n * The managed layer ids belonging to a single base-map layer group.\n *\n * @param group - The layer group to look up.\n * @returns The layer ids for that group, in style draw order (empty when none).\n * A fresh array each call, safe to mutate.\n *\n * @example\n * ```typescript\n * baseMap.getLayerIds('buildings3D'); // ['3D - Building', …]\n * ```\n */\n getLayerIds(group: BaseMapLayerGroupName): string[] {\n return [...this.groupedLayers()[group]];\n }\n\n // The group index for the current style, classified on first access. Callers never see this\n // record: getLayers/getLayerIds copy it, so a consumer mutating what they got back — an\n // in-place sort of the ids, say — cannot corrupt the index for the rest of the style's life.\n private groupedLayers(): LayersByGroup {\n this.layersByGroup ??= groupBaseMapLayers(this.sourcesWithLayers.vectorTiles._layerSpecs);\n return this.layersByGroup;\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(): BaseMapEvents {\n return this.moduleEvents<MapGeoJSONFeature, BaseMapEventScope>([BASE_MAP_SOURCE_ID], {\n scopeResolver: resolveBaseMapEventScope,\n });\n }\n}\n\n// The base map is the only module whose single source carries layers a caller can name: the\n// style's `metadata.group` taxonomy, surfaced as BaseMapLayerGroupName. Everywhere else a layer\n// subset is a rendering detail (casing vs line vs label), so scoping there is by feature instead.\nconst resolveBaseMapEventScope = (scope: BaseMapEventScope): ResolvedEventScope => ({\n layerFilter: buildLayerGroupFilter(scope.layerGroups),\n featureMatches: scope.features,\n});\n","import type { BaseMapKnob, BaseMapKnobId } from './types/baseMapKnobs';\nimport {\n type BaseMapLayerGroupName,\n type BaseMapRoadsConfig,\n baseMapLayerGroupNames,\n} from './types/baseMapModuleConfig';\n\nconst layerGroupContents: Record<BaseMapLayerGroupName, string> = {\n land: 'land use and land cover areas, and the map background',\n water: 'oceans, lakes and rivers',\n buildings2D: '2D building footprints',\n roads: 'road lines, their arrows, and road and transit surface areas',\n railways: 'railways and aerial cableways',\n ferries: 'ferry connections',\n borders: 'administrative boundaries, military and protected overlays, and their labels',\n buildings3D: '3D building models',\n natureLabels: 'labels of peaks, islands, rivers and water areas',\n roadLabels: 'road name labels',\n roadShields: 'highway shields',\n houseNumbers: 'house number labels',\n smallerTownLabels: 'neighbourhood, village, hamlet and town labels',\n stateLabels: 'state and province labels',\n cityLabels: 'city labels',\n allPlaceLabels: 'every place label, the other place-label groups included',\n capitalLabels: 'capital city labels',\n countryLabels: 'country name labels',\n};\n\nconst roadPartContents: Record<keyof BaseMapRoadsConfig, string> = {\n exitNumbers: 'Motorway exit number badges',\n arrows: 'One-way arrows drawn on roads',\n restricted: 'Dashed marking of roads with restricted access',\n underConstruction: 'Roads under construction',\n};\n\n// What a layer group holds, for the entry that toggles it.\nconst describeLayerGroup = (group: BaseMapLayerGroupName): string =>\n `The \\`${group}\\` layer group: ${layerGroupContents[group]}.`;\n\nconst buildCatalogue = (): readonly BaseMapKnob[] => [\n {\n id: 'visible',\n kind: 'toggle',\n description: 'Every base-map layer, beneath what the `groups` entries set.',\n },\n ...baseMapLayerGroupNames.map(\n (group): BaseMapKnob => ({\n id: `groups.${group}.visible`,\n kind: 'toggle',\n description: describeLayerGroup(group),\n }),\n ),\n ...(Object.keys(roadPartContents) as (keyof BaseMapRoadsConfig)[]).map(\n (part): BaseMapKnob => ({\n id: `roads.${part}.visible`,\n kind: 'toggle',\n description: `${roadPartContents[part]}; never shown while its layer group is hidden.`,\n }),\n ),\n];\n\n/**\n * Every base-map knob with its kind and description: `visible`, then one toggle per layer group in\n * {@link baseMapLayerGroupNames} order, then one per road part. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link BaseMapModuleConfig}, at the path its id names. None\n * carries a `default`: unset, a layer is drawn as the style ships it, and some groups ship hidden.\n *\n * Not knobs: `events`.\n *\n * @group Base Map\n */\nexport const baseMapKnobCatalogue: readonly BaseMapKnob[] = /* @__PURE__ */ buildCatalogue();\n\n/**\n * Every base-map knob id, in catalogue order.\n *\n * @group Base Map\n */\nexport const baseMapKnobIds: readonly BaseMapKnobId[] = /* @__PURE__ */ baseMapKnobCatalogue.map(({ id }) => id);\n","import type { FeatureCollection } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport {\n AbstractDataOwnedMapModule,\n assertNoReservedScopeNames,\n type CombinedEvents,\n GeoJSONSourceWithLayers,\n type ToBeAddedLayerSpec,\n type ToBeAddedLayerSpecWithoutSource,\n type UserEventsWithStates,\n} 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 * What `module.events.on('shown-features', ...)` receives. This module's `show` names the source\n * it writes, so the event names it too — the one module whose shown data is per source.\n *\n * @group Custom\n */\nexport type CustomGeoJSONShownFeatures<TSources extends Record<string, FeatureCollection>> = {\n [K in keyof TSources]: { sourceName: K; data: TSources[K] };\n}[keyof TSources];\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>> = CombinedEvents<\n MapGeoJSONFeature,\n CustomGeoJSONModuleConfig<TSources>,\n CustomGeoJSONShownFeatures<TSources>\n> & {\n [K in keyof TSources]: UserEventsWithStates<MapGeoJSONFeature>;\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 * @remarks\n * Because the sources and layers are caller-supplied, the module has no built-in defaults:\n * `resetConfig()` (or `applyConfig(undefined)`) returns to the configuration passed to\n * {@link CustomGeoJSONModule.create | create}, and `getConfig()` never becomes `undefined`.\n * {@link customGeoJSONKnobCatalogue} lists the settings of its configuration a knob holds.\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.create(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.create<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.create<{ 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 AbstractDataOwnedMapModule<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 // The sources `show` has put data on, which a style-change restore shows again.\n // Lazy-initialised because the base-class constructor invokes _initSourcesWithLayers\n // before subclass field initialisers run.\n private shownSourceNames!: Set<keyof TSources>;\n\n // The config the module was created with — what `resetConfig()` returns to.\n // Captured on the first `_applyConfig`, which the base-class constructor triggers.\n private initialConfig!: CustomGeoJSONModuleConfig<TSources>;\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 shownSourceNames.\n private readonly shownFeaturesHandlers: ((features: CustomGeoJSONShownFeatures<TSources>) => void)[] = [];\n\n /**\n * Creates a new {@link CustomGeoJSONModule} once the map is ready.\n *\n * `CustomGeoJSONModule` owns the sources, layers and images it adds, all suffixed per instance.\n * Every call therefore returns a **new, independent** instance, and stacking several of them on\n * one map is a supported thing to do.\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 create<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(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; only a clean style switch re-initialises without one,\n // and that resets to the creation config like `resetConfig()` does.\n const effectiveConfig = config ?? this.initialConfig;\n if (!restore) {\n this.shownSourceNames = new Set();\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 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\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 // A module whose sources are caller-supplied has no built-in defaults to fall back to,\n // so resetting returns to the config it was created with (see the class remarks).\n return this._applyConfig(this.initialConfig);\n }\n this.initialConfig ??= config;\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 // Also reveals the layers just added for a source that already shows features.\n this.applyVisibility(config);\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 }\n }\n }\n\n /** @ignore */\n protected applyVisibility(config: CustomGeoJSONModuleConfig<TSources> | undefined): void {\n const visible = config?.visible !== false;\n for (const sourceWithLayers of Object.values(this.sourcesWithLayers)) {\n sourceWithLayers.setLayersVisible(visible && !!sourceWithLayers.shownFeatures.features.length);\n }\n }\n\n /**\n * @ignore\n */\n protected discardShownData(): void {\n this.shownSourceNames.clear();\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n const previouslyShown = [...this.shownSourceNames].map(\n (sourceName) => [sourceName, this.sourcesWithLayers[sourceName].shownFeatures] as const,\n );\n this.initSourcesWithLayers(this.config, true);\n if (this.config) this._applyConfig(this.config);\n\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, data] of previouslyShown) {\n if (this.sourcesWithLayers[sourceName]) 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. The source's layers draw while it holds features and\n * the module is visible ({@link CustomGeoJSONModuleConfig.visible}).\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.shownSourceNames.add(sourceName);\n // A hidden module's layers are already hidden, and stay so.\n this.sourcesWithLayers[sourceName].show(normalized, { automaticVisibility: this.isVisible() });\n for (const handler of this.shownFeaturesHandlers) {\n handler({ sourceName, data: normalized });\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 this.shownSourceNames.delete(sourceName);\n return;\n }\n for (const name of Object.keys(this.sourcesWithLayers) as Array<keyof TSources>) {\n this.sourcesWithLayers[name].clear();\n }\n this.shownSourceNames.clear();\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 * Every source: the module's event-state methods act on all of them, as a `show` without a\n * source name does. Act on one source through its scope, `events.<sourceName>`.\n * @ignore\n */\n protected eventStateSources(): Array<keyof TSources> {\n return Object.keys(this.sourcesWithLayers) as Array<keyof TSources>;\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.on('shown-features', ({ sourceName, data }) => { ... });\n * module.events.on('config-change', (config) => { ... });\n * ```\n */\n get events(): CustomGeoJSONEvents<TSources> {\n const sourceNames = Object.keys(this.sourcesWithLayers) as Array<keyof TSources>;\n const scopes = {} as { [K in keyof TSources]: UserEventsWithStates<MapGeoJSONFeature> };\n for (const sourceName of sourceNames) {\n scopes[sourceName] = this.userEventsWithStates<MapGeoJSONFeature>([sourceName]);\n }\n\n // Module-wide events cover every source at once, and their `shown-features` names the\n // source that was written; each named scope covers the user events of one source.\n return this.buildEvents(\n this.moduleEventsWithShown<MapGeoJSONFeature, CustomGeoJSONShownFeatures<TSources>>(\n sourceNames,\n this.shownFeaturesHandlers,\n ),\n scopes,\n ) as CustomGeoJSONEvents<TSources>;\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 // Source names become properties on `events`, alongside its own methods. Every other module's\n // scope names are ours and covered by a unit test; these are the caller's, so check at runtime.\n assertNoReservedScopeNames(Object.keys(config.sources), 'CustomGeoJSONModule source');\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\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","import { type KnobDefinition, withIds } from '../shared/knobUtils';\nimport type { CustomGeoJSONKnob, CustomGeoJSONKnobId, CustomGeoJSONKnobValueOf } from './types/customGeoJSONKnobs';\n\nconst customGeoJSONKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'Every layer of every source.',\n default: true,\n },\n} as const satisfies { [ID in CustomGeoJSONKnobId]: KnobDefinition<CustomGeoJSONKnobValueOf<ID>> };\n\n/**\n * Every custom GeoJSON knob with its kind, description and default. Static data, readable with no\n * map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link CustomGeoJSONModuleConfig}, at the path its id names.\n * How the module draws is the caller's own MapLibre styling, so `visible` is the only one. Not knobs:\n * - each source's `layers`, raw MapLibre layer specs passed through untouched;\n * - each source's `cluster`, MapLibre source options forwarded verbatim and fixed at creation, and\n * its `sourceID`, a name;\n * - `images`, image payloads; and `events`.\n *\n * @group Custom\n */\nexport const customGeoJSONKnobCatalogue: readonly CustomGeoJSONKnob[] =\n /* @__PURE__ */ withIds(customGeoJSONKnobDefinitions);\n\n/**\n * Every custom GeoJSON knob id, in catalogue order.\n *\n * @group Custom\n */\nexport const customGeoJSONKnobIds: readonly CustomGeoJSONKnobId[] = /* @__PURE__ */ customGeoJSONKnobCatalogue.map(\n ({ id }) => id,\n);\n","import { isEqual, pick } from 'lodash-es';\nimport type { MapColorName } from '../styling';\nimport type { TomTomMap } from '../TomTomMap';\n\n/**\n * The map's accent colour — what a theme wants content drawn on top of the map to be — or `undefined`\n * when no theme has set one, in which case a module keeps its own default. See {@link TomTomMap.mapColors}.\n * @ignore\n */\nexport const mapAccentColor = (tomtomMap: TomTomMap): string | undefined => tomtomMap.mapColors.accent;\n\n/**\n * Runs `handler` whenever one of the named map colours changes, and only then: the other colours\n * moving leave what the module draws alone.\n * @ignore\n */\nexport const onMapColorsChange = (tomtomMap: TomTomMap, names: readonly MapColorName[], handler: () => void): void => {\n let followed = pick(tomtomMap.mapColors, names);\n tomtomMap._onMapColorsChange((colors) => {\n const current = pick(colors, names);\n if (isEqual(current, followed)) return;\n\n followed = current;\n handler();\n });\n};\n\n/**\n * Runs `handler` whenever the map's accent colour changes, and only then: a base-map colour moving\n * under it leaves what the modules draw alone.\n * @ignore\n */\nexport const onAccentColorChange = (tomtomMap: TomTomMap, handler: () => void): void =>\n onMapColorsChange(tomtomMap, ['accent'], handler);\n","import type { BeforeLayerConfig, PartsBeforeLayerConfig } from '../types/mapsSDKLayerSpecs';\nimport { mapStyleLayerIDs } from './layerIDs';\n\n/**\n * The part names a {@link PartsBeforeLayerConfig} takes in its record form, `all` aside.\n *\n * @ignore\n */\nexport type BeforeLayerPartOf<CONFIG extends PartsBeforeLayerConfig<string>> = Exclude<\n keyof Exclude<CONFIG, BeforeLayerConfig>,\n 'all'\n>;\n\n/**\n * The target a {@link PartsBeforeLayerConfig} names for one part: its own entry, else `all`, else\n * the single target. Undefined when it names none, which leaves the part at its default place.\n *\n * @ignore\n */\nexport const beforeLayerTargetOf = <PART extends string>(\n config: PartsBeforeLayerConfig<PART> | undefined,\n part: PART,\n): BeforeLayerConfig | undefined => (typeof config === 'string' ? config : (config?.[part] ?? config?.all));\n\n/**\n * The layer a {@link BeforeLayerConfig} places layers beneath: the map style layer it names, or\n * `topLayerID` for `'top'` — undefined, the top of the stack, unless the module keeps layers of its\n * own above everything.\n *\n * @ignore\n */\nexport const beforeLayerIDOf = (target: BeforeLayerConfig, topLayerID?: string): string | undefined =>\n target === 'top' ? topLayerID : mapStyleLayerIDs[target];\n\n/**\n * The anchor of one part of a {@link PartsBeforeLayerConfig}: `defaultBeforeID` when the config\n * names no target for it, else the layer its target names.\n *\n * @ignore\n */\nexport const partBeforeLayerIDOf = <PART extends string>(\n config: PartsBeforeLayerConfig<PART> | undefined,\n part: PART,\n defaultBeforeID: string | undefined,\n): string | undefined => {\n const target = beforeLayerTargetOf(config, part);\n return target === undefined ? defaultBeforeID : beforeLayerIDOf(target);\n};\n","import type { SymbolLayerSpecification } from 'maplibre-gl';\nimport { darkerShadeOf, lighterShadeOf } from '../../utils/colorUtils';\nimport type { FeatureLabelConfig, LabelConfig } from '../types/labelConfig';\nimport type { LightDark } from '../types/style';\n\ntype SymbolLayout = NonNullable<SymbolLayerSpecification['layout']>;\ntype SymbolPaint = NonNullable<SymbolLayerSpecification['paint']>;\n\n/**\n * The label colours a module's own overlay labels take on the active map theme: near-black text\n * haloed in white on a light style, and the two swapped on a dark one.\n * @ignore\n */\nexport const themeAdaptiveLabelColors = (lightDark: LightDark): { textColor: string; haloColor: string } => ({\n textColor: lightDark === 'dark' ? '#FFFFFF' : '#333333',\n haloColor: lightDark === 'dark' ? '#333333' : '#FFFFFF',\n});\n\n/**\n * The shade a label tinted after `cssColor` is drawn in, so it stands out from the halo of\n * {@link themeAdaptiveLabelColors}: darker on a light style, lighter on a dark one.\n * @ignore\n */\nexport const themeAdaptiveLabelShadeOf = (cssColor: string, lightDark: LightDark): string =>\n (lightDark === 'dark' ? lighterShadeOf(cssColor) : darkerShadeOf(cssColor)) ?? cssColor;\n\n/**\n * The symbol layout properties a {@link LabelConfig} sets. Unset fields are left out, so the result\n * spreads over a layer's own defaults.\n * @ignore\n */\nexport const labelLayoutProperties = (\n label: LabelConfig | undefined,\n): Pick<SymbolLayout, 'text-size' | 'text-font'> => ({\n ...(label?.size !== undefined && { 'text-size': label.size }),\n ...(label?.font !== undefined && { 'text-font': label.font }),\n});\n\n/**\n * The symbol paint properties a {@link LabelConfig} sets. Unset fields are left out, so the result\n * spreads over a layer's own defaults.\n * @ignore\n */\nexport const labelPaintProperties = (\n label: LabelConfig | undefined,\n): Pick<SymbolPaint, 'text-color' | 'text-halo-color' | 'text-halo-width' | 'text-opacity'> => ({\n ...(label?.color !== undefined && { 'text-color': label.color }),\n ...(label?.haloColor !== undefined && { 'text-halo-color': label.haloColor }),\n ...(label?.haloWidth !== undefined && { 'text-halo-width': label.haloWidth }),\n ...(label?.opacity !== undefined && { 'text-opacity': label.opacity }),\n});\n\n/**\n * The title written on a shown feature: what a {@link FeatureLabelConfig.title} function returns\n * for it, else the module's `defaultTitle`, which only runs when there is no such function.\n * @ignore\n */\nexport const featureTitle = <F>(\n feature: F,\n title: FeatureLabelConfig<F>['title'],\n defaultTitle: (feature: F) => string | undefined,\n): string | undefined => (typeof title === 'function' ? title(feature) : defaultTitle(feature));\n\n/**\n * The `text-field` of a label layer: a {@link FeatureLabelConfig.title} given as an expression,\n * else `fallback`.\n * @ignore\n */\nexport const titleTextField = <T>(\n title: FeatureLabelConfig<never>['title'],\n fallback: T,\n): Exclude<FeatureLabelConfig<never>['title'], (feature: never) => string> | T =>\n title !== undefined && typeof title !== 'function' ? title : fallback;\n","import type { ExpressionSpecification } from 'maplibre-gl';\nimport type { KnobRange } from '../../shared/knobs';\nimport { themeAdaptiveLabelColors } from '../../shared/layers/labelProperties';\nimport type { LightDark } from '../../shared/types/style';\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 colour on light styles; the light arm of {@link getThemeAdaptiveGeometryColors}. */\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/** How much denser a hovered or clicked geometry draws its fill when the config sets no factor, at most opaque. */\nexport const HIGHLIGHT_DEFAULT_FILL_OPACITY_FACTOR = 2;\n\n/** How much wider a hovered or clicked geometry draws its border when the config sets no factor. */\nexport const HIGHLIGHT_DEFAULT_LINE_WIDTH_FACTOR = 1.75;\n\n/**\n * The highlight fill opacity factor's range: it takes the default fill to 0.6 at most, which still\n * shows the map beneath.\n */\nexport const HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE: KnobRange = { min: 1, max: 4, step: 0.25, bounds: 'hard' };\n\n/**\n * The highlight border width factor's range: it takes the default border to 6 px at most, the\n * outline style's to 15 px.\n */\nexport const HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE: KnobRange = { min: 1, max: 3, step: 0.25, bounds: 'hard' };\n\n// ─── Fill styles (outline / filled / inverted) ────────────────────────────────\n\n/** Fill opacity for the outline fill style (transparent fill). */\nexport const OUTLINE_STYLE_FILL_OPACITY = 0;\n\n/** Line colour for the outline fill style. Neutral in both map themes — the accent is carried by the fill. */\nexport const OUTLINE_STYLE_LINE_COLOR = '#555555';\n\n/** Line opacity for the outline fill style. */\nexport const OUTLINE_STYLE_LINE_OPACITY = 0.9;\n\n/** Line width for the outline fill style. */\nexport const OUTLINE_STYLE_LINE_WIDTH = 5;\n\n/** Fill opacity for the filled/inverted fill style. */\nexport const FILLED_STYLE_FILL_OPACITY = 0.6;\n\n/** Line colour for the filled/inverted fill style (thin neutral border over a coloured fill). */\nexport const FILLED_STYLE_LINE_COLOR = 'grey';\n\n/** Line opacity for the filled/inverted fill style. */\nexport const FILLED_STYLE_LINE_OPACITY = 1;\n\n/** Line width for the filled/inverted fill style. */\nexport const FILLED_STYLE_LINE_WIDTH = 1;\n\n/**\n * `outline` on a feature drawn in the `'outline'` fill style, `otherwise` on any other.\n * @ignore\n */\nexport const byOutlineFillStyle = (\n outline: number | string | ExpressionSpecification,\n otherwise: number | string | ExpressionSpecification,\n): ExpressionSpecification => ['case', ['==', ['get', 'fillStyle'], 'outline'], outline, otherwise];\n\n// ─── Label styling ────────────────────────────────────────────────────────────\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\n/** Offset of border labels from the line, in ems `[x, y]`. */\nexport const BORDER_LABEL_OFFSET: [number, number] = [0, 1];\n\n// ─── Theme-adaptive colours ───────────────────────────────────────────────────\n\n/**\n * Geometry colours resolved for the active map theme. Covers the default fill/line colour and the\n * title/border label colours; the outline- and filled-style line colours are fixed neutrals\n * ({@link OUTLINE_STYLE_LINE_COLOR}, {@link FILLED_STYLE_LINE_COLOR}) and never vary by theme.\n * @param lightDark Whether the current map theme is light or dark.\n * @ignore\n */\nexport const getThemeAdaptiveGeometryColors = (lightDark: LightDark) => ({\n /** Default fill/line colour: navy on light, sky blue on dark. */\n defaultColor: lightDark === 'dark' ? '#5FA8D8' : DEFAULT_COLOR,\n ...themeAdaptiveLabelColors(lightDark),\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\n/**\n * Whether a feature is hovered or clicked. `recently-hovered` does not count: the SDK's layers draw\n * it as no state.\n * @ignore\n */\nexport const hasActiveEventState: ExpressionSpecification = [\n 'all',\n ['has', 'eventState'],\n ['!=', ['get', 'eventState'], 'recently-hovered'],\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\n/**\n * Returns the id a layer is added under, which carries its module instance's prefix when there is\n * one.\n * @ignore\n */\nexport const prefixLayerID = (layerName: string, layerIDPrefix?: string): string =>\n layerIDPrefix ? `${layerIDPrefix}-${layerName}` : layerName;\n\n// `paint` and `layout` are all this reads; everything else on an override is spread through\n// untouched, so an override does not have to be declared for the same layer kind as its base.\ntype LayerStyleMaps = { paint?: object; layout?: object };\n\n/**\n * Merges a base layer spec with a user override, deep-merging paint and layout instead of replacing them.\n * @ignore\n */\nexport const mergeLayer = <T extends LayerStyleMaps>(base: T, override: LayerStyleMaps | undefined): T => {\n if (!override) return base;\n\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 * Merges an entry of a module's `layers` configuration onto the layer it names, as {@link mergeLayer}\n * does, minus its `beforeID`: the module's `beforeLayerConfig` is what places its layers.\n * @ignore\n */\nexport const mergeLayerOverride = <T extends LayerStyleMaps>(\n base: T,\n override: (LayerStyleMaps & { beforeID?: string }) | undefined,\n): T => {\n if (!override) return base;\n\n const { beforeID: _placedByBeforeLayerConfig, ...rest } = override;\n return mergeLayer(base, rest);\n};\n","import { isNil } from 'lodash-es';\nimport type {\n DataDrivenPropertyValueSpecification,\n ExpressionSpecification,\n FillLayerSpecification,\n LineLayerSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { clampFactor } from '../../shared/knobUtils';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { hasActiveEventState } from '../../shared/layers/eventState';\nimport { scaledWhen } from '../../shared/layers/expressionTransforms';\nimport { labelLayoutProperties, labelPaintProperties } from '../../shared/layers/labelProperties';\nimport { mergeLayerOverride } from '../../shared/layers/utils';\nimport type { GeometriesModuleConfig, GeometryHighlightConfig } from '../types/geometriesModuleConfig';\nimport {\n BORDER_LABEL_MIN_ZOOM,\n BORDER_LABEL_OFFSET,\n BORDER_LABEL_SYMBOL_SPACING,\n BORDER_LABEL_TEXT_HALO_WIDTH,\n BORDER_LABEL_TEXT_SIZE,\n byOutlineFillStyle,\n DEFAULT_COLOR,\n DEFAULT_FILL_OPACITY,\n DEFAULT_LINE_OPACITY,\n DEFAULT_LINE_WIDTH,\n getThemeAdaptiveGeometryColors,\n HIGHLIGHT_DEFAULT_FILL_OPACITY_FACTOR,\n HIGHLIGHT_DEFAULT_LINE_WIDTH_FACTOR,\n HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE,\n HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE,\n OUTLINE_STYLE_FILL_OPACITY,\n OUTLINE_STYLE_LINE_COLOR,\n OUTLINE_STYLE_LINE_OPACITY,\n OUTLINE_STYLE_LINE_WIDTH,\n TITLE_PADDING,\n TITLE_SIZE,\n} from './constants';\n\n// The fill opacity and border width a geometry draws when the config sets none, by its fill style.\nconst fillOpacityByStyle = byOutlineFillStyle(OUTLINE_STYLE_FILL_OPACITY, DEFAULT_FILL_OPACITY);\nconst lineWidthByStyle = byOutlineFillStyle(OUTLINE_STYLE_LINE_WIDTH, DEFAULT_LINE_WIDTH);\n\n// The fill layer, coloured per feature or else in `defaultColor` — the map's accent when a theme set\n// one, the SDK's own navy otherwise.\nconst geometryFillSpec = (\n defaultColor: string | ExpressionSpecification,\n): LayerSpecTemplate<FillLayerSpecification> => ({\n type: 'fill',\n paint: {\n 'fill-color': ['coalesce', ['get', 'color'], defaultColor],\n 'fill-opacity': fillOpacityByStyle,\n 'fill-antialias': false,\n },\n});\n\n// The border layer. Features with the `'outline'` fill style fall back to `outlineStyleColor` instead\n// of `defaultColor`.\nconst geometryOutlineSpec = (\n defaultColor: string | ExpressionSpecification,\n outlineStyleColor: string | ExpressionSpecification = OUTLINE_STYLE_LINE_COLOR,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n paint: {\n 'line-color': byOutlineFillStyle(\n ['coalesce', ['get', 'color'], outlineStyleColor],\n ['coalesce', ['get', 'color'], defaultColor],\n ),\n 'line-opacity': byOutlineFillStyle(OUTLINE_STYLE_LINE_OPACITY, DEFAULT_LINE_OPACITY),\n 'line-width': lineWidthByStyle,\n },\n});\n\n// The fill opacity and border width in force, drawn `highlight` times larger on a hovered or\n// clicked geometry.\nconst highlightedFillOpacity = (\n opacity: DataDrivenPropertyValueSpecification<number>,\n highlight: GeometryHighlightConfig | undefined,\n) =>\n scaledWhen(\n hasActiveEventState,\n opacity,\n clampFactor(\n highlight?.fillOpacityFactor,\n HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE,\n HIGHLIGHT_DEFAULT_FILL_OPACITY_FACTOR,\n ),\n 1,\n );\n\nconst highlightedLineWidth = (\n width: DataDrivenPropertyValueSpecification<number>,\n highlight: GeometryHighlightConfig | undefined,\n) =>\n scaledWhen(\n hasActiveEventState,\n width,\n clampFactor(highlight?.lineWidthFactor, HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE, HIGHLIGHT_DEFAULT_LINE_WIDTH_FACTOR),\n );\n\n/**\n * Builds Geometry layer specifications for fill and outline layers.\n * @param defaultColor The map's colour for geometries — its accent, or the theme-adaptive default. An\n * expression in `fill.color`, then the configured top-level `color`, win over it.\n * @ignore\n */\nexport const buildGeometryLayerSpecs = (\n fillLayerId: string,\n outlineLayerId: string,\n config?: GeometriesModuleConfig,\n defaultColor = DEFAULT_COLOR,\n): [\n ToBeAddedLayerSpecWithoutSource<FillLayerSpecification>,\n ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>,\n] => {\n const fill = config?.fill;\n const line = config?.line;\n const highlight = config?.highlight;\n // A string `fill.color` or a `fill.palette` pick reaches the layers as each feature's `color` property.\n const fillColorExpression = Array.isArray(fill?.color) ? fill.color : undefined;\n const configuredColor = fillColorExpression ?? config?.color;\n const fillBase = geometryFillSpec(configuredColor ?? defaultColor);\n const outlineBase = geometryOutlineSpec(configuredColor ?? defaultColor, configuredColor);\n\n const fillLayerSpec: ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> = {\n ...fillBase,\n id: fillLayerId,\n paint: {\n ...fillBase.paint,\n 'fill-opacity': highlightedFillOpacity(fill?.opacity ?? fillOpacityByStyle, highlight),\n },\n };\n\n const outlineLayerSpec: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> = {\n ...outlineBase,\n id: outlineLayerId,\n paint: {\n ...outlineBase.paint,\n ...(!isNil(line?.color) && { 'line-color': line?.color }),\n 'line-width': highlightedLineWidth(line?.width ?? lineWidthByStyle, highlight),\n ...(!isNil(line?.opacity) && { 'line-opacity': line?.opacity }),\n },\n };\n\n return [\n mergeLayerOverride(fillLayerSpec, config?.layers?.fill),\n mergeLayerOverride(outlineLayerSpec, config?.layers?.line),\n ];\n};\n\n/**\n * Build geometry layer specification for title.\n * @ignore\n */\nexport const buildGeometryTitleLayerSpec = (\n layerId: string,\n config?: GeometriesModuleConfig,\n lightDark: LightDark = 'light',\n): ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification> => {\n const label = config?.label;\n const { textColor, haloColor } = getThemeAdaptiveGeometryColors(lightDark);\n\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>>(\n {\n type: 'symbol',\n id: layerId,\n layout: {\n 'text-field': label?.title ?? ['get', 'title'],\n 'text-padding': TITLE_PADDING,\n 'text-size': TITLE_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n ...labelLayoutProperties(label),\n 'symbol-placement': 'point',\n },\n paint: {\n 'text-color': textColor,\n 'text-halo-color': haloColor,\n 'text-halo-width': ['interpolate', ['linear'], ['zoom'], 6, 1, 10, 1.5],\n ...labelPaintProperties(label),\n 'text-translate-anchor': 'viewport',\n },\n },\n config?.layers?.label,\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 lightDark: LightDark = 'light',\n): ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification> => {\n const lineLabel = config?.lineLabel;\n const { textColor, haloColor } = getThemeAdaptiveGeometryColors(lightDark);\n\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>>(\n {\n type: 'symbol',\n id: layerId,\n minzoom: lineLabel?.minZoom ?? BORDER_LABEL_MIN_ZOOM,\n layout: {\n 'text-field': lineLabel?.title ?? ['get', 'title'],\n 'symbol-placement': 'line',\n 'text-size': BORDER_LABEL_TEXT_SIZE,\n 'text-font': [MAP_BOLD_FONT],\n ...labelLayoutProperties(lineLabel),\n 'symbol-spacing': lineLabel?.symbolSpacing ?? BORDER_LABEL_SYMBOL_SPACING,\n 'text-keep-upright': true,\n 'text-offset': lineLabel?.offset ?? BORDER_LABEL_OFFSET,\n },\n paint: {\n 'text-color': textColor,\n 'text-halo-color': haloColor,\n 'text-halo-width': BORDER_LABEL_TEXT_HALO_WIDTH,\n ...labelPaintProperties(lineLabel),\n },\n },\n config?.layers?.lineLabel,\n );\n};\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 { GeometriesModuleConfig } from './types/geometriesModuleConfig';\nimport type { DisplayGeometryProps, ExtraGeometryDisplayProps } from './types/geometryDisplayProps';\nimport type { GeometryFillStyle } from './types/geometryFillStyle';\n\n/**\n * The title of a feature without one of its own: a literal `label.title`, else its address. An\n * expression stays on the title layer, which evaluates it per feature.\n */\nconst buildTitle = (\n feature: Feature<Polygon | MultiPolygon, DisplayGeometryProps | GeoJsonProperties>,\n config: GeometriesModuleConfig,\n): string | undefined => {\n const text = config.label?.title;\n return typeof text === 'string' && text ? text : feature.properties?.address?.freeformAddress;\n};\n\n/**\n * The colour a feature takes from `fill.color`, else its turn in `fill.palette`. An expression in\n * `fill.color` stays on the layers, as it cannot be stored as a feature property, and still wins over\n * the palette.\n */\nconst buildColor = (config: GeometriesModuleConfig, index: number): string | undefined => {\n const { color, palette } = config.fill ?? {};\n if (color !== undefined) {\n return typeof color === 'string' ? color : undefined;\n }\n return palette?.length ? palette[index % palette.length] : undefined;\n};\n\n/** Finds the longest coordinate array in a MultiPolygon for title placement. */\nconst getLongestArray = (coordinates: Position[][][]) =>\n coordinates.flat().reduce((result, ring) => (ring.length > result.length ? ring : result), []);\n\n/**\n * Converts a polygon into a world-minus-polygon donut for the `'inverted'` fill style.\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 `fillStyle: '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 fillStyle?: GeometryFillStyle;\n title?: string;\n color?: string;\n id?: string;\n};\n/**\n * Prepares polygon features for display by applying fill style, colors, and titles.\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 properties = (feature.properties ?? {}) as InputFeatureProps;\n const effectiveFillStyle = properties.fillStyle ?? config.fillStyle;\n const processedFeature = effectiveFillStyle === 'inverted' ? invertFeature(feature as PolygonFeature) : feature;\n const processedProperties = (processedFeature.properties ?? {}) as InputFeatureProps;\n const title = processedProperties.title ?? buildTitle(processedFeature, config);\n const color = processedProperties.color ?? buildColor(config, index);\n return {\n ...processedFeature,\n properties: {\n ...processedFeature.properties,\n ...(effectiveFillStyle && { fillStyle: effectiveFillStyle }),\n ...(title && { title }),\n ...(color && { color }),\n id: processedProperties.id ?? generateId(),\n },\n };\n }),\n});\n\n/**\n * The points the geometries' titles are placed at: each one's `placeCoordinates` property when set,\n * else for a MultiPolygon the center of its longest ring's bounding box.\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 { isEqual, omit } from 'lodash-es';\nimport type {\n FillLayerSpecification,\n LineLayerSpecification,\n MapGeoJSONFeature,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport {\n AbstractDataOwnedMapModule,\n type BeforeLayerConfig,\n type CombinedEvents,\n GeoJSONSourceWithLayers,\n type MapModuleCommonConfig,\n type ToBeAddedLayerSpecWithoutSource,\n type UserEventsWithStates,\n} from '../shared';\nimport { mapAccentColor, onAccentColorChange } from '../shared/accentColor';\nimport { type BeforeLayerPartOf, beforeLayerIDOf, beforeLayerTargetOf } from '../shared/layers/beforeLayer';\nimport { applyLayersVisibility, changeLayerProps, moveLayerBefore } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { getThemeAdaptiveGeometryColors } from './layers/constants';\nimport {\n buildGeometryLayerSpecs,\n buildGeometryLineLabelLayerSpec,\n buildGeometryTitleLayerSpec,\n} from './layers/geometryLayers';\nimport { prepareGeometryForDisplay, prepareTitleForDisplay } from './prepareGeometryForDisplay';\nimport type { GeometriesModuleConfig, GeometryBeforeLayerConfig } from './types/geometriesModuleConfig';\nimport type { ExtraGeometryDisplayProps } from './types/geometryDisplayProps';\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 * Event surface of a polygon module ({@link GeometriesModule}, {@link ReachableRangesModule}): the\n * module's own events, plus one named scope per surface it manages.\n *\n * @group Geometries\n */\nexport type GeometriesEvents<CFG = GeometriesModuleConfig> = CombinedEvents<MapGeoJSONFeature, CFG, PolygonFeatures> & {\n /** The fills and outlines. */\n geometry: UserEventsWithStates<MapGeoJSONFeature>;\n /** The labels rendered on top of them. */\n geometryLabel: UserEventsWithStates<MapGeoJSONFeature>;\n};\n\ntype GeometryPart = BeforeLayerPartOf<GeometryBeforeLayerConfig>;\n// Fill first: when both parts target the same layer, the border ends up above the fill.\nconst geometryParts: readonly GeometryPart[] = ['fill', 'line'];\n\n// The settings the prepared features never read.\nconst layerOnlyConfigKeys = [\n 'beforeLayerConfig',\n 'visible',\n 'events',\n 'highlight',\n 'layers',\n] as const satisfies readonly (keyof GeometriesModuleConfig)[];\n\n// Geometries prepared again for a new config keep, by position, the ids and event states of the ones\n// they replace: preparing gives a feature without an id of its own a new one every time.\nconst withShownIdentity = (\n prepared: PolygonFeatures<ExtraGeometryDisplayProps>,\n shown: PolygonFeatures,\n): PolygonFeatures<ExtraGeometryDisplayProps> => {\n if (prepared.features.length !== shown.features.length) return prepared;\n\n return {\n ...prepared,\n features: prepared.features.map((feature, index) => {\n const shownProperties = shown.features[index].properties;\n const eventState = shownProperties?.eventState;\n return {\n ...feature,\n properties: { ...feature.properties, id: shownProperties?.id, ...(eventState && { eventState }) },\n };\n }),\n };\n};\n\n/**\n * Base of the modules that draw polygons with the geometry layers — fills, borders, border labels and\n * titles: {@link GeometriesModule} and {@link ReachableRangesModule}.\n *\n * @remarks\n * A subclass says how its own config reads as the geometry layers' ({@link displayConfig}) and how\n * what it is shown becomes the polygons drawn ({@link prepareShown}); visibility, layer ordering,\n * events and restoring after a style change are shared.\n *\n * @group Geometries\n */\nexport abstract class AbstractGeometriesModule<\n CFG extends MapModuleCommonConfig &\n Pick<GeometriesModuleConfig, 'visible' | 'beforeLayerConfig' | 'highlight' | 'layers'>,\n> extends AbstractDataOwnedMapModule<GeometrySourcesWithLayers, CFG> {\n private titleLayerSpecs!: ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>;\n private geometryFillLayerSpecs!: ToBeAddedLayerSpecWithoutSource<FillLayerSpecification>;\n private geometryOutlineLayerSpecs!: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>;\n // Always added, and hidden while `lineLabel` is unset, so setting it after `create` draws the\n // labels without re-adding the source's layers.\n private lineLabelLayerSpecs!: ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>;\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 // What `show` was last given, prepared again for every later config and style change. Config\n // colours, fill styles and titles are baked into the shown features, so those cannot be reused.\n private lastRawInput: PolygonFeatures | null = null;\n private readonly shownFeaturesHandlers: ((features: PolygonFeatures) => void)[] = [];\n\n protected constructor(map: TomTomMap, config?: CFG) {\n super(map, config);\n // Colours the caller chose win over the accent (`TomTomMap.mapColors`); a top-level `color`, a\n // `fill.color` or a `fill.palette` colours both fill and border, leaving nothing to redraw.\n onAccentColorChange(map, () => {\n const display = this.displayConfig(this.config) ?? {};\n if (display.color !== undefined || display.fill?.color !== undefined || display.fill?.palette?.length) {\n return;\n }\n this.updateLayers(display);\n this.applyVisibility(this.config);\n });\n }\n\n /**\n * What the ids of this module's sources and layers start with, before the instance index. Unique\n * per subclass, whose instances are counted apart.\n * @ignore\n */\n protected abstract get idPrefix(): string;\n\n /**\n * The geometry layers' config for this module's own. Called while the module is constructed, so it\n * reads nothing but `config`.\n * @ignore\n */\n protected abstract displayConfig(config: CFG | undefined): GeometriesModuleConfig | undefined;\n\n /**\n * The polygons drawn for what `show` was given, before the geometry layers' own preparation.\n * @ignore\n */\n protected abstract prepareShown(input: PolygonFeatures, config: CFG | undefined): PolygonFeatures;\n\n /** Current light/dark map theme, re-read on every layer/data build so style switches repaint. */\n protected get lightDark() {\n return this.tomtomMap.styleLightDarkTheme;\n }\n\n /**\n * The map's colour for what carries no `color` under a config without a top-level `color`: the\n * accent when a theme has set one, otherwise a colour that follows the active light/dark theme.\n */\n protected get defaultColor(): string {\n return mapAccentColor(this.tomtomMap) ?? getThemeAdaptiveGeometryColors(this.lightDark).defaultColor;\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(config?: CFG, restore?: boolean): GeometrySourcesWithLayers {\n if (!restore) {\n const idPrefix = this.idPrefix;\n this.sourceID = `${idPrefix}-${this.instanceIndex}`;\n this.titleSourceID = `${idPrefix}Title-${this.instanceIndex}`;\n const layerIdPrefix = `${idPrefix}-${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 display = this.displayConfig(config);\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n display,\n this.defaultColor,\n );\n const titleLayerSpec = buildGeometryTitleLayerSpec(this.titleLayerID, display, this.lightDark);\n this.titleLayerSpecs = titleLayerSpec;\n this.geometryFillLayerSpecs = geometryFillSpec;\n this.geometryOutlineLayerSpecs = geometryOutlineSpec;\n\n const lineLabelSpec = buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, display, this.lightDark);\n this.lineLabelLayerSpecs = lineLabelSpec;\n\n return {\n geometry: new GeoJSONSourceWithLayers(this.mapLibreMap, this.sourceID, [\n geometryFillSpec,\n geometryOutlineSpec,\n lineLabelSpec,\n ]),\n geometryLabel: new GeoJSONSourceWithLayers(this.mapLibreMap, this.titleSourceID, [titleLayerSpec]),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: CFG | undefined) {\n const display = this.displayConfig(config);\n this.updateLayers(display);\n const shown = this.sourcesWithLayers.geometry.shownFeatures;\n // Preparing inverts and re-titles every polygon, so a change that only moves or hides layers\n // keeps the shown ones. An empty source is drawn again.\n if (\n !shown.features.length ||\n !isEqual(omit(config, layerOnlyConfigKeys), omit(this.config, layerOnlyConfigKeys))\n ) {\n this.displayAgain(config, shown);\n }\n if (display?.beforeLayerConfig || this.displayConfig(this.config)?.beforeLayerConfig) {\n this.applyBeforeLayerConfig(display?.beforeLayerConfig, 'top');\n }\n this.applyVisibility(config);\n return config;\n }\n\n /** @ignore */\n protected applyVisibility(config: CFG | undefined): void {\n const { geometry, geometryLabel } = this.sourcesWithLayers;\n const visible = config?.visible !== false && !!geometry.shownFeatures.features.length;\n applyLayersVisibility([this.geometryFillLayerSpecs, this.geometryOutlineLayerSpecs], visible, this.mapLibreMap);\n applyLayersVisibility(\n [this.lineLabelLayerSpecs],\n visible && this.displayConfig(config)?.lineLabel !== undefined,\n this.mapLibreMap,\n );\n applyLayersVisibility(\n [this.titleLayerSpecs],\n visible && !!geometryLabel.shownFeatures.features.length,\n this.mapLibreMap,\n );\n }\n\n // The fill is its own layer; the border/outline and its optional line labels move together as the\n // \"line\" part.\n private get partLayerIDs(): Record<GeometryPart, string[]> {\n return {\n fill: [this.fillLayerID],\n line: [this.outlineLayerID, this.lineLabelLayerID],\n };\n }\n\n // A part the config names no target for goes to `fallback`. `'top'` resolves to the title layer,\n // so titles stay on top: it is where a part sits by default.\n private applyBeforeLayerConfig(\n beforeLayerConfig: GeometryBeforeLayerConfig | undefined,\n fallback: BeforeLayerConfig,\n ) {\n const partLayerIDs = this.partLayerIDs;\n for (const part of geometryParts) {\n const target = beforeLayerTargetOf(beforeLayerConfig, part) ?? fallback;\n for (const layerID of partLayerIDs[part]) {\n moveLayerBefore(this.mapLibreMap, layerID, beforeLayerIDOf(target, this.titleLayerID));\n }\n }\n }\n\n private updateLayers(display: GeometriesModuleConfig | undefined) {\n const [geometryFillSpec, geometryOutlineSpec] = buildGeometryLayerSpecs(\n this.fillLayerID,\n this.outlineLayerID,\n display,\n this.defaultColor,\n );\n const newTitleLayerSpecs = buildGeometryTitleLayerSpec(this.titleLayerID, display, this.lightDark);\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 const newLineLabelSpec = buildGeometryLineLabelLayerSpec(this.lineLabelLayerID, display, this.lightDark);\n changeLayerProps(newLineLabelSpec, this.lineLabelLayerSpecs, this.mapLibreMap);\n this.lineLabelLayerSpecs = newLineLabelSpec;\n }\n\n /**\n * @ignore\n */\n protected discardShownData() {\n this.lastRawInput = null;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl(): void {\n // The polygons are prepared again for the new style, keeping the ids and event states the old\n // source showed; the new source starts empty.\n const shown = this.sourcesWithLayers.geometry.shownFeatures;\n this.initSourcesWithLayers(this.config, true);\n this.displayAgain(this.config, shown);\n this._applyConfig(this.config);\n }\n\n /**\n * Draws what `show` was last given again, prepared for `config`, keeping the identity of the\n * polygons `shown` before.\n * @ignore\n */\n protected displayAgain(config: CFG | undefined, shown: PolygonFeatures): void {\n if (this.lastRawInput) {\n this.display(withShownIdentity(this.prepare(this.lastRawInput, config), shown));\n }\n }\n\n private prepare(input: PolygonFeatures, config: CFG | undefined): PolygonFeatures<ExtraGeometryDisplayProps> {\n return prepareGeometryForDisplay(this.prepareShown(input, config), this.displayConfig(config));\n }\n\n private display(prepared: PolygonFeatures<ExtraGeometryDisplayProps>): void {\n const geometry = this.sourcesWithLayers.geometry;\n geometry.show(prepared, { automaticVisibility: false });\n this.sourcesWithLayers.geometryLabel.show(prepareTitleForDisplay(geometry.shownFeatures), {\n automaticVisibility: false,\n });\n }\n\n /**\n * Replaces what the module shows; the subclass's `show`.\n * @ignore\n */\n protected async showPolygons(input: PolygonFeatures): Promise<void> {\n await this.waitUntilModuleReady();\n this.lastRawInput = input;\n this.display(this.prepare(input, this.config));\n this.applyVisibility(this.config);\n for (const handler of this.shownFeaturesHandlers) {\n handler(input);\n }\n }\n\n /**\n * Removes everything the module shows.\n *\n * @remarks\n * - Clears both the polygons and their labels\n * - Does not reset styling configuration\n * - Module remains initialized and ready for new data\n *\n * @example\n * ```typescript\n * await module.clear();\n * await module.show(newData);\n * ```\n */\n async clear() {\n await this.waitUntilModuleReady();\n this.lastRawInput = null;\n this.sourcesWithLayers.geometry.clear();\n this.sourcesWithLayers.geometryLabel.clear();\n }\n\n /**\n * Returns the polygons and labels currently drawn, as prepared for display.\n *\n * @example\n * ```typescript\n * const shown = module.getShown();\n * console.log(`Polygons: ${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 /** @ignore */\n protected eventStateSources(): (keyof GeometrySourcesWithLayers)[] {\n return ['geometry'];\n }\n\n /**\n * Gets the events interface for handling user interactions with the polygons.\n *\n * @returns The module's events, with `on` / `off` for handlers and `where` for scoping.\n *\n * @remarks\n * **Supported Events:**\n * - `click`: User clicks on a polygon\n * - `contextmenu`: User right-clicks on a polygon\n * - `hover`: Mouse enters a polygon\n * - `long-hover`: Mouse hovers over a polygon for extended time\n *\n * @example\n * ```typescript\n * module.events.on('click', (feature, lngLat) => {\n * console.log('Clicked:', feature.properties);\n * console.log('Location:', lngLat);\n * });\n *\n * // Only the polygons, not their labels\n * module.events.geometry.on('hover', (feature) => showTooltip(feature.properties.title));\n * ```\n */\n get events(): GeometriesEvents<CFG> {\n return this.buildEvents(\n // Module-wide events cover both surfaces; the scopes below single one out.\n this.moduleEventsWithShown<MapGeoJSONFeature, PolygonFeatures>(\n ['geometry', 'geometryLabel'],\n this.shownFeaturesHandlers,\n ),\n {\n geometry: this.userEventsWithStates<MapGeoJSONFeature>(['geometry']),\n geometryLabel: this.userEventsWithStates<MapGeoJSONFeature>(['geometryLabel']),\n },\n );\n }\n}\n","import type { PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { AbstractGeometriesModule } from './AbstractGeometriesModule';\nimport type { GeometriesModuleConfig } from './types/geometriesModuleConfig';\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 * - Reachable ranges have a module of their own, {@link ReachableRangesModule}\n *\n * **Styling:**\n * - One colour, a palette of your own, or the map's accent by default\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-org/maps-sdk/map';\n *\n * // Initialize module\n * const geometriesModule = await GeometriesModule.create(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.create(map, {\n * fill: { color: '#FF5733', opacity: 0.3 },\n * line: { color: '#C70039', width: 3 },\n * label: {\n * title: ['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 AbstractGeometriesModule<GeometriesModuleConfig> {\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 * - `color`: Base colour for fill and border\n * - `fill`: Fill color and opacity\n * - `line`: Border/outline styling\n * - `label`: Center label text and styling\n * - `beforeLayerConfig`: Layer ordering — a single target, or `{ all, fill, line }` to split them\n *\n * **Multiple Instances:**\n * `GeometriesModule` owns the sources, layers and images it adds, all suffixed per instance.\n * Every call therefore returns a **new, independent** instance, so you can put several on the\n * same map, each managing a different set of geometries with different styles.\n *\n * @example\n * Default initialization:\n * ```typescript\n * const geometriesModule = await GeometriesModule.create(map);\n * ```\n *\n * @example\n * With custom styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.create(map, {\n * fill: { color: 'blue', opacity: 0.25 },\n * line: { color: 'darkblue', width: 2, opacity: 0.8 },\n * label: {\n * title: ['get', 'title']\n * },\n * beforeLayerConfig: 'top'\n * });\n * ```\n *\n * @example\n * Data-driven styling:\n * ```typescript\n * const geometriesModule = await GeometriesModule.create(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 create(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(map, config);\n }\n\n /** @ignore */\n protected get idPrefix(): string {\n return 'geometry';\n }\n\n /** @ignore */\n protected displayConfig(config: GeometriesModuleConfig | undefined): GeometriesModuleConfig | undefined {\n return config;\n }\n\n /** @ignore */\n protected prepareShown(geometries: PolygonFeatures, config: GeometriesModuleConfig | undefined): PolygonFeatures {\n const transform = config?.transformFeaturesForDisplay;\n return transform ? transform(geometries) : geometries;\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 `label` is not set\n * - `color`: Override fill color per feature\n * - `fillStyle`: Override the module's `fillStyle` 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 a place's geometry:\n * ```typescript\n * import { geocodeOne, geometryData } from '@tomtom-org/maps-sdk/services';\n *\n * const amsterdam = await geocodeOne('Amsterdam');\n * await geometries.show(await geometryData({ geometries: [amsterdam] }));\n * ```\n */\n async show(geometries: PolygonFeatures) {\n await this.showPolygons(geometries);\n }\n}\n","import {\n byOutlineFillStyle,\n FILLED_STYLE_FILL_OPACITY,\n FILLED_STYLE_LINE_COLOR,\n FILLED_STYLE_LINE_OPACITY,\n FILLED_STYLE_LINE_WIDTH,\n OUTLINE_STYLE_FILL_OPACITY,\n OUTLINE_STYLE_LINE_COLOR,\n OUTLINE_STYLE_LINE_OPACITY,\n OUTLINE_STYLE_LINE_WIDTH,\n} from './layers/constants';\nimport type { GeometriesModuleConfig } from './types/geometriesModuleConfig';\nimport type { FillStyledGeometryConfigOptions } from './types/geometryConfigOptions';\n\n/**\n * Config that draws each polygon by its own fill style, optionally coloured from a palette.\n *\n * Includes data-driven expressions so each feature's `fillStyle` property controls its\n * styling, line labels config, and positions layers below map labels.\n *\n * Reachable ranges have their own module, {@link ReachableRangesModule}, drawn in these styles too.\n *\n * @example\n * ```typescript\n * const zones = await GeometriesModule.create(map, fillStyledGeometryConfig({ palette: ['#e63312', '#1d3f8a'] }));\n * ```\n *\n * @group Geometries\n */\nexport const fillStyledGeometryConfig = ({\n palette,\n beforeLayerConfig = 'lowestLabel',\n}: FillStyledGeometryConfigOptions = {}): GeometriesModuleConfig => {\n return {\n beforeLayerConfig,\n line: {\n // outline fill style: thick colored line; filled/inverted: thin grey line\n width: byOutlineFillStyle(OUTLINE_STYLE_LINE_WIDTH, FILLED_STYLE_LINE_WIDTH),\n color: byOutlineFillStyle(\n ['coalesce', ['get', 'color'], OUTLINE_STYLE_LINE_COLOR],\n FILLED_STYLE_LINE_COLOR,\n ),\n opacity: byOutlineFillStyle(OUTLINE_STYLE_LINE_OPACITY, FILLED_STYLE_LINE_OPACITY),\n },\n fill: {\n ...(palette && { palette }),\n // outline fill style: transparent fill; filled/inverted: semi-transparent fill\n opacity: byOutlineFillStyle(OUTLINE_STYLE_FILL_OPACITY, FILLED_STYLE_FILL_OPACITY),\n },\n // Non-undefined lineLabel is required for GeometriesModule to always create the line label layer.\n lineLabel: {},\n };\n};\n","/**\n * How {@link GeometriesModule} fills a polygon.\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.fillStyle} at config level, or per-feature in\n * `properties.fillStyle`.\n *\n * @group Geometries\n */\nexport type GeometryFillStyle = 'filled' | 'outline' | 'inverted';\n\n/**\n * All valid {@link GeometryFillStyle} values.\n *\n * @group Geometries\n */\nexport const geometryFillStyles = ['filled', 'outline', 'inverted'] as const;\n","import type { KnobRange, LabelConfig } from '../shared';\nimport {\n defaultOf,\n everyOf,\n type KnobDefinition,\n LABEL_HALO_WIDTH_RANGE,\n LABEL_SIZE_RANGE,\n OPACITY_RANGE,\n type ValueAt,\n withIds,\n ZOOM_RANGE,\n} from '../shared/knobUtils';\nimport { MAP_BOLD_FONT, mapFonts } from '../shared/layers/commonLayerProps';\nimport {\n BORDER_LABEL_MIN_ZOOM,\n BORDER_LABEL_OFFSET,\n BORDER_LABEL_SYMBOL_SPACING,\n BORDER_LABEL_TEXT_HALO_WIDTH,\n BORDER_LABEL_TEXT_SIZE,\n DEFAULT_FILL_OPACITY,\n DEFAULT_LINE_OPACITY,\n DEFAULT_LINE_WIDTH,\n HIGHLIGHT_DEFAULT_FILL_OPACITY_FACTOR,\n HIGHLIGHT_DEFAULT_LINE_WIDTH_FACTOR,\n HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE,\n HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE,\n OUTLINE_STYLE_FILL_OPACITY,\n OUTLINE_STYLE_LINE_OPACITY,\n OUTLINE_STYLE_LINE_WIDTH,\n TITLE_SIZE,\n} from './layers/constants';\nimport type {\n GeometriesKnob,\n GeometriesKnobId,\n GeometriesKnobValueOf,\n GeometryLabelKnobLeaf,\n} from './types/geometriesKnobs';\nimport { type GeometryFillStyle, geometryFillStyles } from './types/geometryFillStyle';\nimport type {\n ReachableRangesKnob,\n ReachableRangesKnobId,\n ReachableRangesKnobValueOf,\n} from './types/reachableRangesKnobs';\n\ntype LabelPart = 'label' | 'lineLabel';\ntype ModuleKnobId = Exclude<GeometriesKnobId, `${LabelPart}.${GeometryLabelKnobLeaf}`>;\ntype HighlightKnobId = Extract<GeometriesKnobId, `highlight.${string}`>;\ntype RangesModuleKnobId = Exclude<ReachableRangesKnobId, `label.${GeometryLabelKnobLeaf}`>;\n\nconst LINE_WIDTH_RANGE: KnobRange = { min: 0, max: 20, step: 0.5, bounds: 'hard-min' };\nconst SYMBOL_SPACING_RANGE: KnobRange = { min: 50, max: 1000, step: 10, bounds: 'hard-min' };\nconst LINE_LABEL_OFFSET_RANGE: KnobRange = { min: -5, max: 5, step: 0.1, bounds: 'soft' };\n\n// How a hovered or clicked `subject` draws its fill and border.\nconst highlightKnobDefinitions = (subject: string) =>\n ({\n 'highlight.fillOpacityFactor': {\n kind: 'factor',\n description: `Multiplier on the fill opacity of a hovered or clicked ${subject}, at most opaque; \\`1\\` draws it as any other.`,\n range: HIGHLIGHT_FILL_OPACITY_FACTOR_RANGE,\n default: HIGHLIGHT_DEFAULT_FILL_OPACITY_FACTOR,\n },\n 'highlight.lineWidthFactor': {\n kind: 'factor',\n description: `Multiplier on the border width of a hovered or clicked ${subject}; \\`1\\` draws it as any other.`,\n range: HIGHLIGHT_LINE_WIDTH_FACTOR_RANGE,\n default: HIGHLIGHT_DEFAULT_LINE_WIDTH_FACTOR,\n },\n }) as const satisfies { [ID in HighlightKnobId]: KnobDefinition<GeometriesKnobValueOf<ID>> };\n\n// Both modules fill the same three ways; only what a fill style shades differs.\nconst fillStyleKnobDefinition = (description: string) =>\n ({\n kind: 'enum',\n description,\n options: everyOf<GeometryFillStyle>()(geometryFillStyles),\n default: 'filled',\n }) as const satisfies KnobDefinition<GeometryFillStyle>;\n\nconst moduleKnobDefinitions = {\n color: {\n kind: 'color',\n description:\n \"Base colour of the fill and the border. Unset: the map's accent if a theme sets one, else a navy on light styles and a light blue on dark ones.\",\n },\n visible: {\n kind: 'toggle',\n description: 'Everything the module draws: fills, borders, center labels and border labels.',\n default: true,\n },\n fillStyle: fillStyleKnobDefinition(\n '`filled` shades the polygon, `outline` draws only a thick border, `inverted` shades the outside.',\n ),\n 'fill.color': {\n kind: 'color',\n description: 'Fill colour, over `fill.palette` and `color`; a border without its own `line.color` follows it.',\n },\n 'fill.palette': {\n kind: 'colors',\n description: 'CSS colours giving each feature the next fill colour in turn, over `color`.',\n },\n 'fill.opacity': {\n kind: 'number',\n description: `Fill opacity, which \\`highlight.fillOpacityFactor\\` multiplies while hovered or clicked. Unset: ${DEFAULT_FILL_OPACITY}, or ${OUTLINE_STYLE_FILL_OPACITY} with the \\`outline\\` fill style.`,\n range: OPACITY_RANGE,\n },\n 'line.color': {\n kind: 'color',\n description: \"Border colour, over a feature's own `color` and every fill colour.\",\n },\n 'line.opacity': {\n kind: 'number',\n description: `Border opacity. Unset: ${DEFAULT_LINE_OPACITY}, or ${OUTLINE_STYLE_LINE_OPACITY} with the \\`outline\\` fill style.`,\n range: OPACITY_RANGE,\n },\n 'line.width': {\n kind: 'number',\n description: `Border width in pixels, which \\`highlight.lineWidthFactor\\` multiplies while hovered or clicked. Unset: ${DEFAULT_LINE_WIDTH}, or ${OUTLINE_STYLE_LINE_WIDTH} with the \\`outline\\` fill style.`,\n range: LINE_WIDTH_RANGE,\n },\n ...highlightKnobDefinitions('geometry'),\n 'lineLabel.minZoom': {\n kind: 'number',\n description: 'Lowest zoom the border labels are drawn at; setting any `lineLabel` knob draws them.',\n range: ZOOM_RANGE,\n default: BORDER_LABEL_MIN_ZOOM,\n },\n 'lineLabel.symbolSpacing': {\n kind: 'number',\n description: 'Distance between repeated border labels, in pixels.',\n range: SYMBOL_SPACING_RANGE,\n default: BORDER_LABEL_SYMBOL_SPACING,\n },\n 'lineLabel.offset': {\n kind: 'offset',\n description: 'Shift of the border labels off the line, in ems: x along it, y across it.',\n range: LINE_LABEL_OFFSET_RANGE,\n default: BORDER_LABEL_OFFSET,\n },\n} as const satisfies { [ID in ModuleKnobId]: KnobDefinition<GeometriesKnobValueOf<ID>> };\n\nconst labelNames: Record<LabelPart, string> = {\n label: 'center labels',\n lineLabel: 'border labels',\n};\n\nconst labelKnobDefinitions: {\n [LEAF in GeometryLabelKnobLeaf]: (part: LabelPart) => KnobDefinition<ValueAt<LabelConfig, LEAF>>;\n} = {\n size: (part) => ({\n kind: 'number',\n description: `Text size of the ${labelNames[part]}, in pixels.`,\n range: LABEL_SIZE_RANGE,\n default: part === 'label' ? TITLE_SIZE : BORDER_LABEL_TEXT_SIZE,\n }),\n color: (part) => ({\n kind: 'color',\n description: `Text colour of the ${labelNames[part]}. Unset follows the map's light/dark theme.`,\n }),\n haloColor: (part) => ({\n kind: 'color',\n description: `Halo colour of the ${labelNames[part]}. Unset follows the map's light/dark theme.`,\n }),\n haloWidth: (part) => ({\n kind: 'number',\n description: `Halo width of the ${labelNames[part]}, in pixels.`,\n range: LABEL_HALO_WIDTH_RANGE,\n // The center label's halo grows with the zoom.\n ...defaultOf(part === 'lineLabel' ? BORDER_LABEL_TEXT_HALO_WIDTH : undefined),\n }),\n opacity: (part) => ({\n kind: 'number',\n description: `Text opacity of the ${labelNames[part]}.`,\n range: OPACITY_RANGE,\n default: 1,\n }),\n font: (part) => ({\n kind: 'enums',\n description: `Font faces of the ${labelNames[part]}, in order: the first one available wins.`,\n options: mapFonts,\n default: [MAP_BOLD_FONT],\n }),\n};\n\n// The label knobs at `${prefix}.<leaf>`, worded and defaulted as the module's `part` labels.\nconst labelEntries = <ID extends string>(part: LabelPart, prefix: string) =>\n (Object.keys(labelKnobDefinitions) as GeometryLabelKnobLeaf[]).map((leaf) => ({\n id: `${prefix}.${leaf}` as ID,\n ...labelKnobDefinitions[leaf](part),\n }));\n\nconst buildCatalogue = (): readonly GeometriesKnob[] => {\n const moduleEntries = withIds(moduleKnobDefinitions);\n const isLineLabelKnob = ({ id }: { id: ModuleKnobId }) => id.startsWith('lineLabel.');\n\n return [\n ...moduleEntries.filter((entry) => !isLineLabelKnob(entry)),\n ...labelEntries<GeometriesKnobId>('label', 'label'),\n ...labelEntries<GeometriesKnobId>('lineLabel', 'lineLabel'),\n ...moduleEntries.filter(isLineLabelKnob),\n ] as GeometriesKnob[];\n};\n\n/**\n * Every geometries knob with its kind, description, range or options and, where one value holds\n * everywhere, its default: the module-wide knobs, the fill and border, how a hovered or clicked\n * geometry draws them, then the center and border labels. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link GeometriesModuleConfig}, at the path its id names. Not\n * knobs: `beforeLayerConfig`, `layers`, the labels' `title` and\n * `transformFeaturesForDisplay`, which take structured, MapLibre or function values; and `events`.\n * Setting any `lineLabel` knob draws the border labels.\n *\n * @group Geometries\n */\nexport const geometriesKnobCatalogue: readonly GeometriesKnob[] = /* @__PURE__ */ buildCatalogue();\n\n/**\n * Every geometries knob id, in catalogue order.\n *\n * @group Geometries\n */\nexport const geometriesKnobIds: readonly GeometriesKnobId[] = /* @__PURE__ */ geometriesKnobCatalogue.map(\n ({ id }) => id,\n);\n\nconst rangesModuleKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'Everything the module draws: bands, borders and their labels.',\n default: true,\n },\n fillStyle: fillStyleKnobDefinition(\n \"`filled` shades each band, `outline` draws only each range's border, `inverted` shades the outside of each range.\",\n ),\n 'fill.palette': {\n kind: 'colors',\n description:\n \"CSS colours of the bands, the innermost first, starting over after the last. Empty: a ramp from the map's accent toward its land colour, reversed for `inverted`.\",\n },\n ...highlightKnobDefinitions('band'),\n} as const satisfies { [ID in RangesModuleKnobId]: KnobDefinition<ReachableRangesKnobValueOf<ID>> };\n\nconst buildRangesCatalogue = (): readonly ReachableRangesKnob[] =>\n [\n ...withIds(rangesModuleKnobDefinitions),\n ...labelEntries<ReachableRangesKnobId>('lineLabel', 'label'),\n ] as ReachableRangesKnob[];\n\n/**\n * Every reachable ranges knob with its kind, description, range or options and, where one value holds\n * everywhere, its default: the module-wide knobs, how a hovered or clicked band draws, then the labels\n * along each range's border. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link ReachableRangesModuleConfig}, at the path its id names.\n * Not knobs: `beforeLayerConfig`, `layers` and `label.title`, which take structured, MapLibre or function\n * values; and `events`.\n *\n * @group Geometries\n */\nexport const reachableRangesKnobCatalogue: readonly ReachableRangesKnob[] = /* @__PURE__ */ buildRangesCatalogue();\n\n/**\n * Every reachable ranges knob id, in catalogue order.\n *\n * @group Geometries\n */\nexport const reachableRangesKnobIds: readonly ReachableRangesKnobId[] =\n /* @__PURE__ */ reachableRangesKnobCatalogue.map(({ id }) => id);\n","import {\n budgetUnits,\n getPosition,\n type PolygonFeature,\n type PolygonFeatures,\n type ReachableRangeBudget,\n} from '@tomtom-org/maps-sdk/core';\nimport { area, difference, featureCollection, mask, union } from '@turf/turf';\nimport { groupBy, sortBy } from 'lodash-es';\nimport { mixColor } from '../utils/colorUtils';\nimport type { GeometryFillStyle } from './types/geometryFillStyle';\nimport type { ReachableRangesModuleConfig, ReachableRangeTitleFn } from './types/reachableRangesModuleConfig';\n\n/** How far toward the land colour the outermost band of a ramp goes: still a tint of the accent. */\nconst RAMP_FADE = 0.7;\n\n/**\n * `count` band colours, innermost first: `innermost`, fading toward `outermost` band by band.\n * @ignore\n */\nexport const bandColorRamp = (innermost: string, outermost: string, count: number): string[] =>\n Array.from(\n { length: count },\n (_, rank) => mixColor(innermost, outermost, count > 1 ? (RAMP_FADE * rank) / (count - 1) : 0) ?? innermost,\n );\n\n/** A range with its title, and its rank among the ranges of its origin: 0 for the innermost. */\ntype RankedRange = { feature: PolygonFeature; squareMeters: number; rank: number; title: string };\n\nconst titleOf = (feature: PolygonFeature, index: number, title: ReachableRangeTitleFn | undefined): string => {\n if (title) return title(feature, index);\n\n const budget = feature.properties?.budget as Partial<ReachableRangeBudget> | undefined;\n if (budget?.value !== undefined && budget.type) {\n return `${budget.value} ${budgetUnits[budget.type] ?? budget.type}`;\n }\n return String(index + 1);\n};\n\n// The ranges of each origin, largest first; ranges with no origin form one group.\nconst rankByOrigin = (features: PolygonFeature[], title: ReachableRangeTitleFn | undefined): RankedRange[][] => {\n const ranges = features.map((feature, index) => ({\n feature,\n squareMeters: area(feature),\n title: titleOf(feature, index, title),\n }));\n const byOrigin = groupBy(ranges, ({ feature }) => JSON.stringify(getPosition(feature.properties?.origin)));\n return Object.values(byOrigin).map((group) =>\n sortBy(group, ({ squareMeters }) => -squareMeters).map((range, index) => ({\n ...range,\n rank: group.length - 1 - index,\n })),\n );\n};\n\n// A fill of the `'inverted'` style carries no title, so no label traces a ring's hole, and no fill style,\n// so it is not inverted again.\nconst invertedFill = (feature: PolygonFeature, geometry: PolygonFeature['geometry'], color: string): PolygonFeature => {\n const { title: _title, fillStyle: _fillStyle, ...properties } = feature.properties ?? {};\n return { ...feature, geometry, properties: { ...properties, color } };\n};\n\n// The `'inverted'` fill: the world outside every origin's largest range, then a ring outside each smaller\n// one, up to the next larger. Each range's border, labelled, is drawn as a `'filled'` copy.\nconst invertedRanges = (groups: RankedRange[][], colorOf: (rank: number) => string): PolygonFeature[] => {\n const largest = groups.map((group) => group[0].feature);\n const reachable = (largest.length > 1 && union(featureCollection(largest))) || largest[0];\n const outermostRank = Math.max(...groups.map((group) => group[0].rank));\n const fills = [invertedFill(largest[0], mask(reachable).geometry, colorOf(outermostRank))];\n\n for (const group of groups) {\n for (const [index, { feature, rank }] of group.entries()) {\n if (index === 0) continue;\n\n const ring = difference(featureCollection([group[index - 1].feature, feature]));\n fills.push(invertedFill(feature, ring?.geometry ?? feature.geometry, colorOf(rank)));\n }\n }\n\n const borders = groups.flat().map(({ feature, rank, title }) => {\n const { id: _id, ...properties } = feature.properties ?? {};\n return {\n ...feature,\n id: undefined,\n properties: { ...properties, title, fillStyle: 'filled' as GeometryFillStyle, color: colorOf(rank) },\n };\n });\n return [...fills, ...borders];\n};\n\nconst layOutRanges = (\n groups: RankedRange[][],\n fillStyle: GeometryFillStyle,\n colorOf: (rank: number) => string,\n): PolygonFeature[] => {\n if (fillStyle === 'inverted') return invertedRanges(groups, colorOf);\n\n // Largest first, so a smaller range draws on top of the larger ones around it.\n return sortBy(groups.flat(), ({ squareMeters }) => -squareMeters).map(({ feature, rank, title }) => ({\n ...feature,\n properties: { ...feature.properties, title, fillStyle, color: colorOf(rank) },\n }));\n};\n\n/**\n * The reachable ranges given to {@link ReachableRangesModule.show}, ready to draw: titled, coloured by\n * their band, and laid out for the fill style.\n *\n * @param bandColors - The band colours for a given number of bands, innermost first. A band past the\n * last colour starts over from the first.\n * @ignore\n */\nexport const prepareReachableRanges = (\n ranges: PolygonFeatures,\n { fillStyle = 'filled', label }: Pick<ReachableRangesModuleConfig, 'fillStyle' | 'label'>,\n bandColors: (count: number) => string[],\n): PolygonFeatures => {\n const groups = rankByOrigin(ranges.features as PolygonFeature[], label?.title);\n const colors = bandColors(Math.max(0, ...groups.map((group) => group.length)));\n const colorOf = (rank: number) => colors[rank % colors.length];\n const features = groups.length ? layOutRanges(groups, fillStyle, colorOf) : [];\n return { type: 'FeatureCollection', ...(ranges.bbox && { bbox: ranges.bbox }), features };\n};\n","import type { PolygonFeatures } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification } from 'maplibre-gl';\nimport { onMapColorsChange } from '../shared/accentColor';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { AbstractGeometriesModule, type GeometriesEvents } from './AbstractGeometriesModule';\nimport { fillStyledGeometryConfig } from './geometryConfigs';\nimport {\n FILLED_STYLE_FILL_OPACITY,\n FILLED_STYLE_LINE_OPACITY,\n getThemeAdaptiveGeometryColors,\n} from './layers/constants';\nimport { bandColorRamp, prepareReachableRanges } from './prepareReachableRanges';\nimport type { GeometriesModuleConfig } from './types/geometriesModuleConfig';\nimport type { ReachableRangesModuleConfig } from './types/reachableRangesModuleConfig';\n\n/**\n * Event surface of {@link ReachableRangesModule}: the module's own events, plus one named scope per\n * surface it manages.\n *\n * @group Geometries\n */\nexport type ReachableRangesEvents = GeometriesEvents<ReachableRangesModuleConfig>;\n\n// With the `'inverted'` fill style, the ranges' borders are drawn as `'filled'` copies over the rings.\nconst isBorder: ExpressionSpecification = ['==', ['get', 'fillStyle'], 'filled'];\n\n/**\n * Reachable Ranges Module: the areas reachable from one or more origins within a budget of time,\n * distance, fuel or charge, as `calculateReachableRanges` returns them, drawn as bands.\n *\n * @remarks\n * **What it does for you:**\n * - Titles each range from its budget (`'30 min'`, `'10 km'`, `'20 % remaining'`) and labels its border\n * - Ranks the ranges of each origin, innermost first, and colours them by rank\n * - Draws them in any order given: a smaller range always sits on top of the larger ones around it\n * - With `fillStyle: 'inverted'`, dims the world outside the largest range of every origin and draws a\n * ring outside each smaller one\n *\n * **Colours:** the bands ramp from the map's accent (innermost) toward its land colour (outermost),\n * both read from {@link TomTomMap.mapColors} and followed as they change — so a theme set with\n * `StylingFoundationsModule.setMapColors`, e.g. from the map-theme plugin's `deriveMapColors`, recolours the\n * bands too. `fill.palette` sets your own instead.\n *\n * **Multiple Instances:** the module owns the sources and layers it adds, suffixed per instance, so\n * several can share a map.\n *\n * @example\n * ```typescript\n * import { calculateReachableRanges } from '@tomtom-org/maps-sdk/services';\n * import { ReachableRangesModule } from '@tomtom-org/maps-sdk/map';\n *\n * const ranges = await ReachableRangesModule.create(map);\n * await ranges.show(\n * await calculateReachableRanges(\n * [10, 20, 30].map((value) => ({ origin: [4.9, 52.37], budget: { type: 'timeMinutes', value } })),\n * ),\n * );\n *\n * ranges.applyConfig({ fillStyle: 'inverted' });\n * ```\n *\n * @see [Reachable Ranges Module Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/reachable-ranges)\n *\n * @group Geometries\n */\nexport class ReachableRangesModule extends AbstractGeometriesModule<ReachableRangesModuleConfig> {\n /**\n * Waits for the map to be ready, then creates a new module on it.\n *\n * @param tomtomMap - The TomTomMap instance.\n * @param config - The module's configuration.\n * @returns A new, independent instance: every call adds its own sources and layers.\n *\n * @example\n * ```typescript\n * const ranges = await ReachableRangesModule.create(map, { fillStyle: 'outline' });\n * ```\n */\n static async create(tomtomMap: TomTomMap, config?: ReachableRangesModuleConfig): Promise<ReachableRangesModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new ReachableRangesModule(tomtomMap, config);\n }\n\n private constructor(map: TomTomMap, config?: ReachableRangesModuleConfig) {\n super(map, config);\n // Without a palette the bands ramp from the accent toward the land colour, baked into each band.\n onMapColorsChange(map, ['accent', 'land'], () => {\n if (this.config?.fill?.palette?.length) return;\n\n this.displayAgain(this.config, this.sourcesWithLayers.geometry.shownFeatures);\n });\n }\n\n /** @ignore */\n protected get idPrefix(): string {\n return 'reachableRange';\n }\n\n /** @ignore */\n protected displayConfig(config: ReachableRangesModuleConfig | undefined): GeometriesModuleConfig {\n const base = fillStyledGeometryConfig({ beforeLayerConfig: config?.beforeLayerConfig });\n // The title is the ranges' own, written into each feature; the layer only styles it.\n const { title: _title, ...label } = config?.label ?? {};\n if (config?.fillStyle !== 'inverted') {\n return { ...base, highlight: config?.highlight, layers: config?.layers, lineLabel: { ...label } };\n }\n\n return {\n ...base,\n highlight: config.highlight,\n layers: config.layers,\n line: { ...base.line, opacity: ['case', isBorder, FILLED_STYLE_LINE_OPACITY, 0] },\n fill: { ...base.fill, opacity: ['case', isBorder, 0, FILLED_STYLE_FILL_OPACITY] },\n lineLabel: {\n ...label,\n opacity: ['case', isBorder, (label.opacity ?? 1) as ExpressionSpecification, 0],\n offset: ['case', isBorder, ['literal', [0, -1]], ['literal', [0, 1]]],\n },\n };\n }\n\n /** @ignore */\n protected prepareShown(ranges: PolygonFeatures, config: ReachableRangesModuleConfig | undefined): PolygonFeatures {\n return prepareReachableRanges(ranges, config ?? {}, (count) => {\n if (config?.fill?.palette?.length) return config.fill.palette;\n\n const ramp = bandColorRamp(this.defaultColor, this.landColor, count);\n // Out of reach deepens away from the origin, so the inverted bands ramp the other way.\n return config?.fillStyle === 'inverted' ? ramp.reverse() : ramp;\n });\n }\n\n // What the outermost band fades toward: the map's land, or the theme's background tone before a\n // styling foundations module has published it.\n private get landColor(): string {\n return this.tomtomMap.mapColors.land ?? getThemeAdaptiveGeometryColors(this.lightDark).haloColor;\n }\n\n /**\n * Displays reachable ranges, replacing the ones shown.\n *\n * @param ranges - The ranges, as `calculateReachableRanges` returns them: any number of budgets,\n * from any number of origins, in any order. Each range's `budget` titles it and its `origin` groups\n * it with the others from the same place.\n *\n * @example\n * ```typescript\n * // Two origins, three budgets each, in one call\n * const origins = [[4.9, 52.37], [4.48, 51.92]];\n * await ranges.show(\n * await calculateReachableRanges(\n * origins.flatMap((origin) =>\n * [5, 10, 15].map((value) => ({ origin, budget: { type: 'distanceKM', value } })),\n * ),\n * ),\n * );\n * ```\n */\n async show(ranges: PolygonFeatures) {\n await this.showPolygons(ranges);\n }\n}\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// Each base-map POI category, which names the style's `poi-<category>` sprite, with the POI categories\n// that have it as their direct equivalent.\nconst directCategories = {\n aboriginal_land: ['ABORIGINAL_LAND'],\n airport: ['AIRPORT'],\n amusement_park: ['AMUSEMENT_PARK'],\n atm: ['ATM'],\n bank: ['BANK'],\n bar: ['BAR'],\n beach_resort: ['BEACH'],\n border_control: ['TRANSPORT_ACCESS', 'BORDER_CONTROL'],\n bus_station: ['BUS_STOP'],\n cafe: ['CAFE', 'CAFE_PUB'],\n camping_ground: ['CAMPING_GROUND'],\n car_rental: ['CAR_RENTAL'],\n casino_and_gambling: ['CASINO_AND_GAMBLING'],\n cemetery: ['CEMETERY'],\n charging_location: ['CHARGING_LOCATION'],\n cinema: ['CINEMA', 'MOVIE_THEATER'],\n clinic: ['HEALTHCARE_SERVICE'],\n clothing_shop: ['CLOTHING_SHOP'],\n coffee_shop: ['COFFEE_SHOP'],\n college_or_university: ['COLLEGE_OR_UNIVERSITY'],\n commercial_area: [\n 'AGRICULTURE',\n 'BUSINESS_PARK',\n 'COMMERCIAL_BUILDING',\n 'COMPANY',\n 'RESIDENTIAL_ACCOMMODATION',\n 'RESIDENTIAL_ENTRANCE',\n ],\n conference_center: ['EXHIBITION_CONVENTION_CENTER'],\n consumer_service: ['CLUB_AND_ASSOCIATION', 'PUBLIC_AMENITY', 'PEDESTRIAN_SUBWAY', 'WELFARE_ORGANIZATION'],\n courthouse: ['COURTHOUSE'],\n cultural_center: ['COMMUNITY_CENTER', 'CULTURAL_CENTER'],\n dentist: ['DENTIST'],\n department_store: ['DEPARTMENT_STORE'],\n doctor: ['DOCTOR'],\n emergency_ward_entrance: ['EMERGENCY_MEDICAL_SERVICE', 'EMERGENCY_ROOM'],\n fast_food: ['FAST_FOOD'],\n ferry_terminal: ['FERRY_TERMINAL'],\n fire_station: ['FIRE_STATION'],\n fitness_center: ['FITNESS_CLUB_AND_CENTER'],\n food_and_drinks_shop: ['WINERY'],\n foreign_exchange: ['FOREIGN_EXCHANGE', 'EXCHANGE'],\n fuel_station: ['FUEL_STATION'],\n gold_exchange: ['GOLD_EXCHANGE'],\n golf_course: ['GOLF_COURSE'],\n government_office: ['EMBASSY', 'GOVERNMENT_OFFICE'],\n heliport_or_helipad: ['HELIPORT_OR_HELIPAD'],\n historic_site: ['HISTORIC_SITE'],\n holiday_home: ['HOLIDAY_RENTAL'],\n hospital: ['GENERAL_HOSPITAL_AND_POLYCLINIC', 'HOSPITAL'],\n hotel_or_motel: ['HOTEL_OR_MOTEL'],\n ice_rink: ['ICE_RINK'],\n industrial_area: ['INDUSTRIAL_BUILDING', 'PORT_WAREHOUSE_FACILITY', 'PRIMARY_RESOURCE_UTILITY'],\n landmark: ['NATURAL_FEATURE'],\n mall: ['MALL'],\n manufacturing_facility: ['MANUFACTURING_FACILITY'],\n marina: ['MARINA'],\n marketplace: ['MARKETPLACE'],\n media_facility: ['MEDIA_FACILITY'],\n mountain_pass: ['MOUNTAIN_PASS'],\n museum: ['MUSEUM'],\n ngo_office: ['NON_GOVERNMENTAL_ORGANIZATION'],\n nightlife: ['CONCERT_HALL', 'ENTERTAINMENT', 'NIGHTLIFE'],\n park_and_recreation_area: ['PARK_AND_RECREATION_AREA', 'TRAIL_SYSTEM'],\n park_and_recreation_facility: ['LEISURE_CENTER', 'FLYING_CLUB', 'WATER_SPORT'],\n parking_facility: ['PARKING_GARAGE'],\n pharmacy: ['PHARMACY'],\n police_station: ['POLICE_STATION'],\n post_office: ['POST_OFFICE'],\n prison: ['PRISON'],\n protected_land: ['PROTECTED_LAND'],\n pub: ['PUB'],\n public_library: ['PUBLIC_LIBRARY'],\n public_transport_stop: ['PUBLIC_TRANSPORT_STOP'],\n railway_station: ['RAILWAY_STATION'],\n religious_site: ['CHURCH', 'GURUDWARA', 'MOSQUE', 'PAGODA', 'RELIGIOUS_SITE', 'SYNAGOG', 'TEMPLE'],\n rental_car_parking: ['RENTAL_CAR_PARKING'],\n research_facility: ['RESEARCH_FACILITY'],\n rest_and_service_area: ['REST_AREA'],\n restaurant: ['RESTAURANT', 'RESTAURANT_AREA'],\n school_or_kindergarten: ['SCHOOL'],\n shop_or_store: ['SHOP_OR_STORE'],\n sport_facility: ['ADVENTURE_SPORTS_FACILITY', 'SPORTS_CENTER', 'TENNIS_COURT'],\n stadium: ['STADIUM'],\n supermarket: ['SUPERMARKET'],\n swimming_pool: ['SWIMMING_POOL'],\n taxi_stand: ['TAXI_STAND'],\n theater: ['OPERA_HOUSE', 'THEATER'],\n toll_plaza: ['TOLL_GATE'],\n tourist_attraction: ['TOURIST_ATTRACTION'],\n tourist_information: ['TOURIST_INFORMATION'],\n truck_stop: ['TRUCK_STOP'],\n vehicle_dealer: ['VEHICLE_DEALER'],\n vehicle_inspection: [\n 'MOTORING_ORGANIZATION_OFFICE',\n 'TRAFFIC_CONTROL_DEPARTMENT',\n 'TRANSPORT_AUTHORITY_VEHICLE_REGISTRATION',\n ],\n vehicle_repair: ['VEHICLE_REPAIR'],\n vehicle_wash: ['CAR_WASH'],\n veterinary: ['VETERINARY'],\n viewpoint: ['HILL', 'MOUNTAIN_PEAK', 'VIEWPOINT'],\n weighbridge: ['WEIGH_STATION'],\n zoo_or_aquarium: ['AQUATIC_ZOO', 'ARBORETA_AND_BOTANICAL_GARDENS', 'ZOOS_ARBORETA_BOTANICAL_GARDEN'],\n} as const satisfies Record<string, readonly POICategory[]>;\n\n// The POI categories without a direct equivalent, under the base-map category closest to them.\nconst closestCategories = {\n airport: ['AIRFIELD', 'AIRLINE_ACCESS', 'PRIVATE_AIRPORT', 'PUBLIC_AIRPORT'],\n animal_shelter: ['ANIMAL_SHELTER'],\n bank: ['DIVERSIFIED_FINANCIALS'],\n bar: ['COCKTAIL_BAR'],\n beach_resort: ['BEACH_CLUB'],\n border_control: ['SECURED_ENTRANCE'],\n cafe: ['INTERNET_CAFE'],\n cafeteria: ['CAFETERIA'],\n camping_ground: ['CARAVAN_SITE', 'RECREATIONAL_CAMPING_GROUND', 'REST_CAMP'],\n cemetery: ['MAUSOLEUM_OR_GRAVE'],\n childcare: ['CHILDCARE', 'PRE_SCHOOL'],\n cinema: ['DRIVE_IN_MOVIES'],\n clothing_shop: ['CHILDREN_S_CLOTHES', 'MEN_S_CLOTHING', 'SPECIALTY_CLOTHING_SHOP', 'WOMEN_S_CLOTHING'],\n college_or_university: ['JUNIOR_COLLEGE_OR_COMMUNITY_COLLEGE'],\n comedy_club: ['COMEDY_CLUB'],\n commercial_area: [\n 'ADVERTISING_COMPANY',\n 'AGRICULTURAL_TECHNOLOGY',\n 'AIRLINE_COMPANY',\n 'APARTMENT_RENTAL',\n 'AUTOMOBILE_COMPANY',\n 'BUILDING',\n 'BUSINESS_SERVICES',\n 'BUS_CHARTER_COMPANY',\n 'CABLE_AND_TELEPHONE_COMPANY',\n 'CLEANING_SERVICES',\n 'COMPUTER_AND_DATA_SERVICES',\n 'CONSTRUCTION_COMPANY',\n 'DELIVERY_SERVICE',\n 'ELECTRONICS_COMPANY',\n 'EQUIPMENT_RENTAL',\n 'FARM',\n 'FLATS_OR_APARTMENT_COMPLEX',\n 'HORTICULTURE',\n 'IMPORT_OR_EXPORT_AND_DISTRIBUTION',\n 'INSURANCE_COMPANY',\n 'INVESTMENT_ADVISOR',\n 'LEGAL_SERVICES',\n 'MINING_COMPANY',\n 'MOVING_AND_STORAGE_COMPANY',\n 'OIL_AND_NATURAL_GAS',\n 'PHARMACEUTICAL_COMPANY',\n 'PRIMARY_PRODUCER',\n 'PUBLISHING_TECHNOLOGIES',\n 'REAL_ESTATE_AGENT',\n 'REAL_ESTATE_COMPANY',\n 'RETIREMENT_COMMUNITY',\n 'SCHOOL_BUS_COMPANY',\n 'SERVICE_COMPANY',\n 'SOFTWARE_COMPANY',\n 'TAX_SERVICES',\n 'TELECOMMUNICATIONS',\n 'TRANSPORT_COMPANY',\n 'TRAVEL_AGENT',\n 'WEDDING_SERVICES',\n ],\n consumer_service: ['PUBLIC_CALL_BOX', 'PUBLIC_BATHROOM'],\n doctor: ['GENERAL_PRACTITIONER', 'SPECIALIST'],\n emergency_ward_entrance: ['AMBULANCE_UNIT', 'RESCUE_SERVICE'],\n food_and_drinks_shop: ['MICROBREWERY'],\n historic_site: ['BATTLEFIELD', 'MEMORIAL'],\n holiday_home: ['BUNGALOW_RENTAL', 'CHALET_RENTAL', 'COTTAGE_RENTAL', 'VILLA_RENTAL'],\n horse_riding: ['HORSE_RIDING'],\n hospital: ['BLOOD_BANK', 'HOSPITAL_FOR_WOMEN_AND_CHILDREN', 'HOSPITAL_OF_CHINESE_MEDICINE', 'SPECIAL_HOSPITAL'],\n hotel_or_motel: ['BANDB_OR_GUEST_HOUSE', 'CABINS_LODGES', 'HOSTEL', 'HOTEL', 'MOTEL', 'RESORT'],\n ice_cream_shop: ['ICE_CREAM_PARLOR'],\n ice_rink: ['ICE_HOCKEY_ARENA'],\n industrial_area: ['CHEMICAL_COMPANY', 'MECHANICAL_ENGINEERING', 'QUARRY'],\n juice_bar: ['JUICE_BAR'],\n landmark: [\n 'ARCH',\n 'BAY',\n 'BRIDGE',\n 'CAPE',\n 'COVE',\n 'DAM',\n 'DUNE',\n 'ISLAND',\n 'LOCALE',\n 'MARSH',\n 'MONUMENT',\n 'OASIS',\n 'OBSERVATORY',\n 'PARKWAY',\n 'PLAIN_OR_FLAT',\n 'PLATEAU',\n 'PAN',\n 'RAPIDS',\n 'REEF',\n 'RESERVOIR',\n 'RIVER_CROSSING',\n 'ROCKS',\n 'STATUE',\n 'TOWER',\n 'TUNNEL',\n 'VALLEY',\n 'WATER_HOLE',\n 'WELL',\n ],\n manufacturing_facility: ['AUTOMOBILE_MANUFACTURING', 'MANUFACTURING_COMPANY'],\n marketplace: ['FARMERS_MARKET', 'FOOD_MARKET', 'INFORMAL_MARKET', 'PUBLIC_MARKET'],\n military_airfield: ['MILITARY_AIRPORT'],\n mourning_facility: ['FUNERAL_SERVICE_AND_MORTUARIES'],\n nightlife: [\n 'AMUSEMENT_PLACE',\n 'BETTING_STATION',\n 'CABARET_THEATER',\n 'DISCO_CLUB',\n 'FAIRGROUND',\n 'JAZZ_CLUB',\n 'KARAOKE_CLUB',\n 'MUSIC_CENTER',\n ],\n park_and_recreation_area: [\n 'ADVENTURE_VEHICLE_TRAIL',\n 'FISHING_AND_HUNTING_AREA',\n 'FOREST_AREA',\n 'HIKING_TRAIL',\n 'HISTORICAL_PARK',\n 'HORSE_RIDING_TRAIL',\n 'MOUNTAIN_BIKE_TRAIL',\n 'PARK',\n 'PICNIC_AREA',\n 'RECREATION_AREA',\n 'ROCK_CLIMBING_TRAIL',\n 'WILDERNESS_AREA',\n ],\n parking_facility: ['OPEN_CAR_PARKING_AREA'],\n personal_care_service: [\n 'BEAUTY_SALON',\n 'HAIRDRESSER',\n 'NAIL_SALON',\n 'PERSONAL_CARE_FACILITY',\n 'PERSONAL_SERVICE',\n 'SAUNA_SOLARIUM_MASSAGE',\n ],\n pharmacy: [\n 'DRUG_STORE',\n 'CANNABIS_SHOP',\n 'MEDICINAL_AND_RECREATIONAL_DISPENSARY',\n 'MEDICINAL_MARIJUANA_DISPENSARY',\n 'RECREATIONAL_MARIJUANA_DISPENSARY',\n ],\n planetarium: ['PLANETARIUM'],\n post_depot: ['COURIER_DROP_BOX'],\n public_transport_stop: [\n 'BUS_LINES',\n 'PASSENGER_TRANSPORT_TICKET_OFFICE',\n 'STREETCAR_STOP',\n 'SUBWAY_STATION',\n 'SUBWAY_ENTRANCE',\n ],\n railway_station: ['INTERNATIONAL_RAILROAD_STATION', 'NATIONAL_RAILROAD_STATION', 'STATION_ACCESS', 'URBAN_STATION'],\n restaurant: [\n 'AFGHAN_RESTAURANT',\n 'AFRICAN_RESTAURANT',\n 'AMERICAN_RESTAURANT',\n 'ARABIAN_RESTAURANT',\n 'ARMENIAN_RESTAURANT',\n 'ASIAN_RESTAURANT',\n 'AUSTRALIAN_RESTAURANT',\n 'AUSTRIAN_RESTAURANT',\n 'BANQUET_ROOMS',\n 'BARBECUE_RESTAURANT',\n 'BASQUE_RESTAURANT',\n 'BELGIAN_RESTAURANT',\n 'BISTRO',\n 'BOLIVIAN_RESTAURANT',\n 'BRAZILIAN_RESTAURANT',\n 'BRITISH_RESTAURANT',\n 'BUFFET_RESTAURANT',\n 'BULGARIAN_RESTAURANT',\n 'BURMESE_RESTAURANT',\n 'CALIFORNIAN_RESTAURANT',\n 'CAMBODIAN_RESTAURANT',\n 'CANADIAN_RESTAURANT',\n 'CARIBBEAN_RESTAURANT',\n 'CATERING_SERVICES',\n 'CHICKEN_RESTAURANT',\n 'CHILEAN_RESTAURANT',\n 'CHINESE_RESTAURANT',\n 'COLOMBIAN_RESTAURANT',\n 'CORSICAN_RESTAURANT',\n 'CREOLE_RESTAURANT',\n 'CREPERIE',\n 'CUBAN_RESTAURANT',\n 'CZECH_RESTAURANT',\n 'DANISH_RESTAURANT',\n 'DINNER_THEATER',\n 'DONGBEI_RESTAURANT',\n 'DOUGHNUT_RESTAURANT',\n 'DUTCH_RESTAURANT',\n 'EGYPTIAN_RESTAURANT',\n 'ENGLISH_RESTAURANT',\n 'ETHIOPIAN_RESTAURANT',\n 'FINNISH_RESTAURANT',\n 'FONDUE_RESTAURANT',\n 'FRENCH_RESTAURANT',\n 'FUSION_RESTAURANT',\n 'GERMAN_RESTAURANT',\n 'GREEK_RESTAURANT',\n 'GRILL_RESTAURANT',\n 'HAMBURGER_RESTAURANT',\n 'HAWAIIAN_RESTAURANT',\n 'HOT_POT_RESTAURANT',\n 'HUNAN_RESTAURANT',\n 'HUNGARIAN_RESTAURANT',\n 'INDIAN_RESTAURANT',\n 'INDONESIAN_RESTAURANT',\n 'INTERNATIONAL_RESTAURANT',\n 'IRANIAN_RESTAURANT',\n 'IRISH_RESTAURANT',\n 'ISRAELI_RESTAURANT',\n 'ITALIAN_RESTAURANT',\n 'JAMAICAN_RESTAURANT',\n 'JAPANESE_RESTAURANT',\n 'JEWISH_RESTAURANT',\n 'KOREAN_RESTAURANT',\n 'KOSHER_RESTAURANT',\n 'LATIN_AMERICAN_RESTAURANT',\n 'LEBANESE_RESTAURANT',\n 'MEDITERRANEAN_RESTAURANT',\n 'MEXICAN_RESTAURANT',\n 'MIDDLE_EASTERN_RESTAURANT',\n 'MONGOLIAN_RESTAURANT',\n 'MOROCCAN_RESTAURANT',\n 'NEPALESE_RESTAURANT',\n 'NORWEGIAN_RESTAURANT',\n 'ORGANIC_FOOD_RESTAURANT',\n 'ORIENTAL_RESTAURANT',\n 'PAKISTANI_RESTAURANT',\n 'PERUVIAN_RESTAURANT',\n 'PHILIPPINE_RESTAURANT',\n 'PIZZERIA',\n 'POLISH_RESTAURANT',\n 'PORTUGUESE_RESTAURANT',\n 'RUSSIAN_RESTAURANT',\n 'SALAD_BAR',\n 'SANDWICH_RESTAURANT',\n 'SCANDINAVIAN_RESTAURANT',\n 'SEAFOOD',\n 'SHANDONG_RESTAURANT',\n 'SICHUAN_RESTAURANT',\n 'SNACKS_RESTAURANT',\n 'SOUL_FOOD',\n 'SOUP_RESTAURANT',\n 'SPANISH_RESTAURANT',\n 'STEAK_HOUSE',\n 'SUSHI_RESTAURANT',\n 'SWEDISH_RESTAURANT',\n 'SWISS_RESTAURANT',\n 'SYRIAN_RESTAURANT',\n 'TAIWANESE_RESTAURANT',\n 'TAKEOUT_FOOD',\n 'TAPAS_RESTAURANT',\n 'TEPPANYAKI_RESTAURANT',\n 'THAI_RESTAURANT',\n 'TIBETAN_RESTAURANT',\n 'TUNISIAN_RESTAURANT',\n 'TURKISH_RESTAURANT',\n 'VEGETARIAN_RESTAURANT',\n 'VENEZUELAN_RESTAURANT',\n 'VIETNAMESE_RESTAURANT',\n 'WESTERN_RESTAURANT',\n ],\n school_or_kindergarten: [\n 'ART_SCHOOL',\n 'DANCE_STUDIO_AND_SCHOOL',\n 'DRIVING_SCHOOL',\n 'HIGH_SCHOOL',\n 'LANGUAGE_SCHOOL',\n 'MIDDLE_SCHOOL',\n 'PRIMARY_SCHOOL',\n 'SENIOR_HIGH_SCHOOL',\n 'VOCATIONAL_SCHOOL',\n ],\n shop_or_store: [\n 'AGRICULTURAL_SUPPLIES',\n 'ANTIQUE_ART_SHOP',\n 'BAGS_LEATHERWEAR',\n 'BAKERY',\n 'BEAUTY_SUPPLIES',\n 'BOATING_EQUIPMENT_AND_ACCESSORIES',\n 'BOOK_SHOP',\n 'BUTCHER',\n 'CAMERAS_AND_PHOTOGRAPHY',\n 'CARPET_FLOOR_COVERINGS',\n 'CHRISTMAS_OR_HOLIDAY_SHOP',\n 'COMPUTER_AND_COMPUTER_SUPPLIES',\n 'CONSTRUCTION_MATERIAL_AND_EQUIPMENT',\n 'CONSUMER_ELECTRONICS',\n 'CONVENIENCE_STORE',\n 'CURTAINS_TEXTILES',\n 'C_DS_DVD_VIDEOS',\n 'DELICATESSEN',\n 'DO_IT_YOURSELF_CENTERS',\n 'DRIVE_THROUGH_BOTTLE_SHOP',\n 'DRY_CLEANER',\n 'ELECTRICAL_APPLIANCES_SHOP',\n 'FACTORY_OUTLET',\n 'FISHMONGER',\n 'FLORISTS',\n 'FOOTWEAR_SHOE_REPAIRS',\n 'FURNITURE_HOME_FURNISHINGS',\n 'GARDEN_CENTERS_SERVICES',\n 'GIFTS_CARDS_NOVELTIES_SOUVENIRS',\n 'GLASSWARE_CERAMIC_SHOP',\n 'GLASS_WINDOWS_STORE',\n 'GREENGROCER',\n 'GROCERY_STORE',\n 'HARDWARE_STORE',\n 'HOBBY_SHOP',\n 'HOUSE_AND_GARDEN_FURNITURE_AND_FITTINGS',\n 'JEWELRY_CLOCKS_WATCHES',\n 'KITCHENS_BATHROOMS',\n 'LAUNDRY',\n 'LIGHTING_SHOPS',\n 'LOCAL_SPECIALITIES_SHOP',\n 'LOTTERY_SHOP',\n 'MEDICAL_SUPPLIES_EQUIPMENT',\n 'MOBILE_PHONE_SHOP',\n 'MUSIC_INSTRUMENTS_STORE',\n 'NEWSAGENTS_TOBACCONISTS',\n 'OFFICE_EQUIPMENT',\n 'OPTICIAN',\n 'FOOD_AND_DRINKS_SHOP',\n 'PAINTING_DECORATING',\n 'PAWN_SHOP',\n 'PET_SUPPLIES',\n 'PHOTOCOPY_SHOP',\n 'PHOTO_LAB_OR_DEVELOPMENT',\n 'RECYCLING_SHOP',\n 'RETAIL_OUTLET',\n 'SECURITY_PRODUCTS',\n 'SHOPPING_SERVICE',\n 'SPECIALTY_FOODS',\n 'SPORTS_EQUIPMENT_AND_CLOTHING',\n 'TAILOR_SHOP',\n 'TOYS_GAMES_SHOP',\n 'VARIETY_STORE',\n 'VIDEO_RENTAL_SHOP',\n 'WHOLESALE_CLUB',\n 'WINE_SPIRITS',\n ],\n sport_facility: [\n 'ATHLETICS_TRACK',\n 'BASEBALL_PARK',\n 'BASKETBALL_ARENA',\n 'BOWLING_CENTER',\n 'CRICKET_GROUND',\n 'HOCKEY_CLUB',\n 'HORSE_RACING_TRACK',\n 'RACE_TRACK',\n 'RUGBY_GROUND',\n 'SQUASH_COURT',\n 'THEMATIC_SPORT_CENTER',\n ],\n stadium: ['FOOTBALL_STADIUM', 'MOTOR_RACING_STADIUM', 'MULTI_PURPOSE_STADIUM', 'NETBALL_STADIUM', 'SOCCER_STADIUM'],\n stock_exchange: ['STOCK_EXCHANGE'],\n taxi_stand: ['TAXI_LIMOUSINE_AND_SHUTTLE_SERVICE'],\n tea_house: ['TEA_HOUSE'],\n theater: ['AMPHITHEATER'],\n tourist_attraction: ['CAVE', 'MINERAL_OR_HOT_SPRINGS', 'NATURAL_TOURIST_ATTRACTION'],\n transport_access: ['BOAT_LAUNCHING_RAMP', 'HARBOR', 'YACHT_BASIN'],\n vehicle_dealer: [\n 'ATV_DEALER',\n 'BOAT_DEALER',\n 'BUS_DEALER',\n 'CAR_DEALER',\n 'MOTORCYCLE_DEALER',\n 'RECREATIONAL_VEHICLE_DEALER',\n 'TRUCK_DEALER',\n 'VAN_DEALER',\n ],\n vehicle_repair: [\n 'BODYSHOP',\n 'CAR_GLASS_REPLACEMENT_SHOP',\n 'CAR_REPAIR_AND_SERVICE',\n 'HOME_APPLIANCE_REPAIR',\n 'MOTORCYCLE_REPAIR',\n 'TIRE_SERVICE',\n 'TRUCK_REPAIR_AND_SERVICE',\n ],\n veterinary: ['ANIMAL_SERVICE'],\n viewpoint: ['RIDGE'],\n winter_sports: ['SKI_RESORT'],\n zoo_or_aquarium: ['WILDLIFE_PARK', 'ZOO'],\n} as const satisfies Record<string, readonly POICategory[]>;\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 = POICategory;\n\nconst baseMapCategories: Partial<Record<POICategory, string>> = /* @__PURE__ */ Object.fromEntries(\n [directCategories, closestCategories].flatMap((table: Record<string, readonly POICategory[]>) =>\n Object.entries(table).flatMap(([baseMapCategory, categories]) =>\n categories.map((category) => [category, baseMapCategory]),\n ),\n ),\n);\n\n/**\n * @ignore\n */\nexport const toBaseMapPOICategory = (category: POICategory): string | undefined => baseMapCategories[category];\n\n/**\n * Every base-map POI category, each naming the `poi-<category>` sprite the base-map style ships: what\n * the `pin-clustered` cluster pin's `match` expression enumerates to pre-load every sprite into the atlas.\n *\n * @ignore\n */\nexport const baseMapPOICategoryValues: readonly string[] = /* @__PURE__ */ [\n ...new Set([...Object.keys(directCategories), ...Object.keys(closestCategories)]),\n];\n\n// Each group of the base map style's `POI` / `POI - Micro` layers, which picks their colour and micro\n// icon, with its base-map categories. Groups mirror the match arms of the style's `text-color`.\nconst groupCategories: Record<string, readonly string[]> = {\n eat_and_drink: [\n 'bar',\n 'cafe',\n 'cafeteria',\n 'coffee_shop',\n 'fast_food',\n 'ice_cream_shop',\n 'juice_bar',\n 'pub',\n 'restaurant',\n 'tea_house',\n ],\n shopping: [\n 'clothing_shop',\n 'department_store',\n 'food_and_drinks_shop',\n 'mall',\n 'marketplace',\n 'shop_or_store',\n 'supermarket',\n ],\n finance: ['atm', 'bank', 'foreign_exchange', 'gold_exchange', 'stock_exchange'],\n lodging: ['camping_ground', 'holiday_home', 'hotel_or_motel'],\n transport: [\n 'airport',\n 'bus_station',\n 'ferry_terminal',\n 'heliport_or_helipad',\n 'marina',\n 'public_transport_stop',\n 'railway_station',\n 'taxi_stand',\n 'transport_access',\n ],\n driving: [\n 'border_control',\n 'car_rental',\n 'charging_location',\n 'fuel_station',\n 'rest_and_service_area',\n 'toll_plaza',\n 'truck_stop',\n 'vehicle_dealer',\n 'vehicle_inspection',\n 'vehicle_repair',\n 'vehicle_wash',\n 'weighbridge',\n ],\n parking: ['parking_facility', 'rental_car_parking'],\n healthcare: ['clinic', 'dentist', 'doctor', 'emergency_ward_entrance', 'hospital', 'pharmacy', 'veterinary'],\n education: ['childcare', 'college_or_university', 'research_facility', 'school_or_kindergarten'],\n cultural: ['cultural_center', 'historic_site', 'museum', 'planetarium', 'theater'],\n leisure: [\n 'amusement_park',\n 'beach_resort',\n 'casino_and_gambling',\n 'cinema',\n 'comedy_club',\n 'nightlife',\n 'park_and_recreation_facility',\n 'tourist_attraction',\n 'tourist_information',\n 'zoo_or_aquarium',\n ],\n sport: [\n 'fitness_center',\n 'golf_course',\n 'horse_riding',\n 'ice_rink',\n 'sport_facility',\n 'stadium',\n 'swimming_pool',\n 'winter_sports',\n ],\n outdoor: ['aboriginal_land', 'landmark', 'mountain_pass', 'park_and_recreation_area', 'viewpoint'],\n protected: ['protected_land'],\n public: [\n 'cemetery',\n 'courthouse',\n 'fire_station',\n 'government_office',\n 'police_station',\n 'post_depot',\n 'post_office',\n 'prison',\n 'public_library',\n ],\n military: ['military_airfield'],\n religion: ['religious_site'],\n business: [\n 'animal_shelter',\n 'commercial_area',\n 'conference_center',\n 'consumer_service',\n 'industrial_area',\n 'manufacturing_facility',\n 'media_facility',\n 'mourning_facility',\n 'ngo_office',\n 'personal_care_service',\n ],\n};\n\nconst baseMapCategoryGroups: Record<string, string> = /* @__PURE__ */ Object.fromEntries(\n Object.entries(groupCategories).flatMap(([group, categories]) => categories.map((category) => [category, group])),\n);\n\n/**\n * @ignore\n */\nexport const toBaseMapPOIGroup = (category: POICategory): string | undefined => {\n const baseMapCategory = toBaseMapPOICategory(category);\n return baseMapCategory ? baseMapCategoryGroups[baseMapCategory] : undefined;\n};\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` marker type 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 * How far a pin is shifted from its point, in screen pixels: the tip sinks into it rather than\n * standing on it. Text drawn on the pin shifts with it.\n * @ignore\n */\nexport const PIN_TRANSLATE: [number, number] = [0, 5];\n\n/**\n * @ignore\n */\nexport const pinIconBasePaint: SymbolLayerSpecification['paint'] = {\n 'icon-translate': PIN_TRANSLATE,\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","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';\nimport type { PlaceClusterBadgeConfig } from '../types/placesModuleConfig';\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` marker type 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 marker type'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// Default radius (in pixels) used by MapLibre when clustering pins for the\n// `pin-clustered` marker type. 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.\nexport const DEFAULT_CLUSTER_RADIUS_PX = 60;\n// MapLibre's own default (source `maxzoom` 18, minus one), stated so entry points can start above it.\nexport const CLUSTER_MAX_ZOOM = 17;\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.\nexport const DEFAULT_SINGLE_BADGE_OFFSET: [number, number] = [12, -14];\n\n// Radius of the small top-right badge for single-category clusters.\nexport const DEFAULT_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.\nexport const DEFAULT_MIXED_BADGE_RADIUS = 15;\n\nexport const DEFAULT_BADGE_COLOR = '#000000';\nexport const DEFAULT_BADGE_STROKE_COLOR = '#FFFFFF';\nexport const DEFAULT_BADGE_STROKE_WIDTH = 1.5;\nexport const DEFAULT_BADGE_TEXT_COLOR = '#FFFFFF';\nexport const DEFAULT_BADGE_TEXT_SIZE = 12;\n\n// The single-category badge circle and its count read the same offset, so they cannot drift apart.\nconst singleBadgeOffset = (badge?: PlaceClusterBadgeConfig) => badge?.offset ?? DEFAULT_SINGLE_BADGE_OFFSET;\n\nconst badgeCirclePaint = (\n badge: PlaceClusterBadgeConfig | undefined,\n radius: number,\n): NonNullable<CircleLayerSpecification['paint']> => ({\n 'circle-radius': radius,\n 'circle-color': badge?.color ?? DEFAULT_BADGE_COLOR,\n 'circle-stroke-color': badge?.strokeColor ?? DEFAULT_BADGE_STROKE_COLOR,\n 'circle-stroke-width': badge?.strokeWidth ?? DEFAULT_BADGE_STROKE_WIDTH,\n});\n\nconst countTextLayout = (badge?: PlaceClusterBadgeConfig): NonNullable<SymbolLayerSpecification['layout']> => ({\n 'text-field': ['get', 'point_count_abbreviated'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': badge?.label?.size ?? DEFAULT_BADGE_TEXT_SIZE,\n 'text-allow-overlap': true,\n 'text-ignore-placement': true,\n});\n\n/**\n * 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 = (\n badge?: PlaceClusterBadgeConfig,\n): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isSingleCategoryClusterFilter,\n paint: {\n ...badgeCirclePaint(badge, badge?.radius?.single ?? DEFAULT_SINGLE_BADGE_RADIUS),\n 'circle-translate': singleBadgeOffset(badge),\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 = (\n badge?: PlaceClusterBadgeConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isSingleCategoryClusterFilter,\n layout: countTextLayout(badge),\n paint: {\n 'text-color': badge?.label?.color ?? DEFAULT_BADGE_TEXT_COLOR,\n 'text-translate': singleBadgeOffset(badge),\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 = (\n badge?: PlaceClusterBadgeConfig,\n): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n filter: isMixedCategoryClusterFilter,\n paint: badgeCirclePaint(badge, badge?.radius?.mixed ?? DEFAULT_MIXED_BADGE_RADIUS),\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 = (\n badge?: PlaceClusterBadgeConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n filter: isMixedCategoryClusterFilter,\n layout: countTextLayout(badge),\n paint: {\n 'text-color': badge?.label?.color ?? DEFAULT_BADGE_TEXT_COLOR,\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` marker type 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 { IconOffset } from '../../shared/layers/iconOffset';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { parseSvg } from '../../shared/resources';\nimport type { PlacesMarkerType, PlacesModuleConfig } from '../types/placesModuleConfig';\n\n/**\n * Map of icon IDs to their text offset scale factors and the icon's own pixel offset.\n * @ignore\n */\nexport type IconPositioningMap = Map<string, { heightScale: number; widthScale: number; offset: IconOffset }>;\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 markerType The places marker type ('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 markerType?: PlacesMarkerType,\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 isCenteredMarkerType = markerType === 'base-map' || markerType === 'circle-icon';\n\n // Centered marker types (base-map / circle-icon) scale relative to POI icon dimensions.\n // Pins scale relative to default pin dimensions.\n const refHeight = isCenteredMarkerType ? DEFAULT_MAP_POI_HEIGHT_PX : DEFAULT_PIN_HEIGHT_PX;\n const refWidth = isCenteredMarkerType ? 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 and pixel offsets for custom icons.\n * An icon gets an entry when it has a non-standard image size (`heightScale`/`widthScale`\n * default to `1` otherwise) or specifies `offsetX`/`offsetY` (default `0` otherwise).\n * @ignore\n */\nexport const buildCustomIconScalesMap = (\n config: PlacesModuleConfig | undefined,\n instanceIndex: number,\n): IconPositioningMap => {\n const iconScales: IconPositioningMap = new Map();\n const customIcons = config?.icon?.categoryIcons ?? [];\n\n for (const icon of customIcons) {\n if (icon.image) {\n const scales = calculateIconScale(icon.image, config?.markerType);\n const hasOffset = icon.offsetX !== undefined || icon.offsetY !== undefined;\n\n if (scales === undefined && !hasOffset) {\n continue;\n }\n const entry = {\n heightScale: scales?.heightScale ?? 1,\n widthScale: scales?.widthScale ?? 1,\n offset: { x: icon.offsetX ?? 0, y: icon.offsetY ?? 0 },\n };\n // Base icon ID with instance suffix\n const suffixedIconId = suffixNumber(icon.id, instanceIndex);\n iconScales.set(suffixedIconId, entry);\n\n // If this icon has an availability level, also add the availability-suffixed version\n // (e.g., \"CHARGING_LOCATION-available-0\")\n if (icon.availabilityLevel) {\n const availabilitySuffixedId = suffixNumber(`${icon.id}-${icon.availabilityLevel}`, instanceIndex);\n iconScales.set(availabilitySuffixedId, entry);\n }\n }\n }\n\n return iconScales;\n};\n","import type { DataDrivenPropertyValueSpecification } from 'maplibre-gl';\nimport type { CustomImage } from '../types/image';\nimport { ICON_ID } from './symbolLayers';\n\n/**\n * Pixel offset of an icon from its anchor point.\n * @ignore\n */\nexport type IconOffset = { x: number; y: number };\n\n/**\n * Collects the non-zero pixel offsets from a list of custom images, keyed by icon ID.\n *\n * Entries without an `image` are skipped: they reference an existing sprite icon by ID,\n * which the module never registers and therefore cannot reposition.\n * @ignore\n */\nexport const buildCustomIconOffsets = (icons: CustomImage[] = []): Map<string, IconOffset> => {\n const iconOffsets = new Map<string, IconOffset>();\n\n for (const icon of icons) {\n if (!icon.image) {\n continue;\n }\n\n const offset = { x: icon.offsetX ?? 0, y: icon.offsetY ?? 0 };\n if (offset.x === 0 && offset.y === 0) {\n continue;\n }\n\n iconOffsets.set(icon.id, offset);\n }\n\n return iconOffsets;\n};\n\n/**\n * Builds a data-driven `icon-offset` expression from icon ID / offset pairs, matched against\n * the feature's `matchedProperty`, `iconID` by default. Icons absent from the pairs, or present\n * with a zero offset, fall back to `fallback`, no offset by default. Returns `undefined` when no\n * icon needs an offset, so callers can leave `icon-offset` off the layout entirely.\n *\n * Takes the pairs rather than a record shape so a caller that stores offsets alongside other\n * per-icon data can hand over just the offsets.\n * @ignore\n */\nexport const buildIconOffsetExpression = (\n iconOffsets: Iterable<readonly [string, IconOffset]>,\n matchedProperty: string = ICON_ID,\n fallback: DataDrivenPropertyValueSpecification<[number, number]> = [0, 0],\n): DataDrivenPropertyValueSpecification<[number, number]> | undefined => {\n const offsetCaseExpression: unknown[] = ['case'];\n for (const [iconId, offset] of iconOffsets) {\n if (offset.x === 0 && offset.y === 0) {\n continue;\n }\n offsetCaseExpression.push(['==', ['get', matchedProperty], iconId], ['literal', [offset.x, offset.y]]);\n }\n\n if (offsetCaseExpression.length === 1) {\n return undefined;\n }\n\n // A bare offset is no expression, so it cannot stand as the last arm as it is.\n offsetCaseExpression.push(\n Array.isArray(fallback) && typeof fallback[0] === 'number' ? ['literal', fallback] : fallback,\n );\n return offsetCaseExpression as DataDrivenPropertyValueSpecification<[number, number]>;\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 { toPinSpriteImageID } from '../../shared/imageUtils';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport type { EVAvailabilityConfig, PlacesMarkerType, PlacesModuleConfig } 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 === 'CHARGING_LOCATION' && 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.3;\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 markerType: PlacesMarkerType,\n): string | undefined => {\n if (!config.evAvailability?.visible || !isEVStationWithAvailability(place)) {\n return undefined;\n }\n\n const ratio = getAvailabilityRatio(place);\n const threshold = config.evAvailability.threshold ?? DEFAULT_EV_AVAILABILITY_THRESHOLD;\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 pins: use CDN availability sprites when no custom icons are defined\n if (!hasCustomIcons && markerType === 'pin') {\n return toPinSpriteImageID(`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 { PlacesMarkerType } 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 pins 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 markerType The places marker type ('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 markerType?: PlacesMarkerType,\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 isCenteredMarkerType = markerType === 'base-map' || markerType === 'circle-icon';\n const hasCustomOffset = customTextOffset !== undefined;\n\n // For centered marker types with a custom offset, use simple text-offset\n if (isCenteredMarkerType && 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 = isCenteredMarkerType ? 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 marker types (base-map / circle-icon) use larger vertical offsets to match native map styling\n const baseTopOffset = isCenteredMarkerType ? DEFAULT_TEXT_OFFSET_Y * 2 : DEFAULT_TEXT_OFFSET_Y;\n // iconScaleMultiplier accounts for this layer's own icon-size (e.g. the larger\n // `selected` pin layer) — without it, a custom icon's offset would ignore which\n // layer it's rendered on, unlike the fallback offset above.\n const topOffset = baseTopOffset * scales.heightScale * iconScaleMultiplier;\n const sideOffset = DEFAULT_TEXT_OFFSET_X * scales.widthScale * iconScaleMultiplier;\n\n // Centered marker types (centered icon anchor) → no vertical adjustment for side anchors\n // Pins (bottom-anchored) → shift labels upward to align with visual center\n const pinVerticalAdjustment = isCenteredMarkerType ? 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 type { DataDrivenPropertyValueSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { buildIconOffsetExpression } from '../../shared/layers/iconOffset';\nimport {\n labelLayoutProperties,\n labelPaintProperties,\n themeAdaptiveLabelColors,\n} from '../../shared/layers/labelProperties';\nimport { TITLE } from '../../shared/layers/symbolLayers';\nimport type { PlaceLayerName, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport type { IconPositioningMap } from './customIconScales';\nimport { getAvailabilityColorExpression } from './evAvailabilityHelpers';\nimport { getTextOffset } from './textOffsetCalculator';\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?: IconPositioningMap,\n): SymbolLayerSpecification['layout'] => {\n const labelConfig = config?.label;\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 // marker type is present. `circle-icon` inherits pin-style offsets from pinLayerBaseSpec which must\n // be replaced with centered offsets.\n const needsOffsetRecalculation =\n hasCustomIcons || labelConfig?.offset !== undefined || config?.markerType === '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 ...labelLayoutProperties(labelConfig),\n ...(textField !== undefined && { 'text-field': textField }),\n ...customLayer?.layout,\n };\n\n // Text label position: recalculated when custom icons, a custom offset, or the\n // circle-icon marker type are present; otherwise the base layout's offsets pass through.\n //\n if (needsOffsetRecalculation) {\n const layoutWithTextOffset = {\n ...layout,\n ...getTextOffset(\n layout['icon-size'],\n iconTextOffsetScales ?? new Map(),\n config?.markerType,\n labelConfig?.offset,\n ),\n };\n // Icon's own pixel offset — independent of the text-offset recalculation above,\n // applied whenever any custom icon specifies `offsetX`/`offsetY`. Skip `micro`: it\n // never binds `icon-image` to `iconID` (base-map/pin-clustered marker types render the\n // style's own group-driven native sprite there, not the custom category icon), so\n // matching on `iconID` would shift that unrelated native sprite instead.\n const iconOffset =\n layerName !== 'micro' &&\n iconTextOffsetScales &&\n buildIconOffsetExpression([...iconTextOffsetScales].map(([iconId, { offset }]) => [iconId, offset]));\n\n return iconOffset ? { ...layoutWithTextOffset, 'icon-offset': iconOffset } : layoutWithTextOffset;\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 customLayer = config?.layers?.[layerName];\n const { textColor: baseTextColor, haloColor: baseHaloColor } = themeAdaptiveLabelColors(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 `label.*` 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 ...labelPaintProperties(config?.label),\n ...customLayer?.paint,\n };\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';\nimport type { StyleKnobId } from '../../styling';\n\n/**\n * The knobs that restyle what the builders below copy off the style's `POI` and `POI - Micro`\n * layers: their text and icon sizes, text colour and halo.\n * @ignore\n */\nexport const poiLookKnobIds = [\n 'labels.sizeFactor',\n 'symbols.sizeFactor',\n 'pois.sizeFactor',\n 'pois.label.color',\n 'pois.label.haloColor',\n] as const satisfies readonly StyleKnobId[];\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> => JSON.parse(JSON.stringify(textSize)?.replaceAll('name', TITLE));\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' marker type.\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 { beforeLayerTargetOf, partBeforeLayerIDOf } from '../../shared/layers/beforeLayer';\nimport { SELECTED_PIN_ICON_SIZE } from '../../shared/layers/commonLayerProps';\nimport { hasActiveEventState } from '../../shared/layers/eventState';\nimport { titleTextField } from '../../shared/layers/labelProperties';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport { mergeLayer } from '../../shared/layers/utils';\nimport type {\n PlaceLayerName,\n PlaceLayersConfig,\n PlacesMarkerType,\n PlacesModuleConfig,\n} from '../types/placesModuleConfig';\nimport { buildCustomIconScalesMap, type IconPositioningMap } 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// A minimal placeholder spec used to keep a layer registered but hidden. This mirrors\n// the pattern in `TrafficAreaAnalyticsModule` (toggling `layout.visibility`), so marker type\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\nconst hasNoActiveEventState: ExpressionSpecification = ['!', hasActiveEventState];\n\n/**\n * @ignore\n */\nexport const SELECTED_COLOR = '#3f9cd9';\n\n/**\n * The marker types whose layers copy the look of the style's POI layers.\n * @ignore\n */\nexport const poiLookMarkerTypes: ReadonlySet<PlacesMarkerType> = new Set(['base-map', 'circle-icon', 'pin-clustered']);\n\n/**\n * @ignore\n */\nexport const pinLayerSpec: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...pinLayerBaseSpec,\n filter: hasNoActiveEventState,\n};\n\n// Derives a `selected` variant from a base spec: match only on event state,\n// allow text overlap so selected labels stay visible, and paint text in the selected colour.\nconst toSelectedSpec = (\n base: LayerSpecTemplate<SymbolLayerSpecification>,\n selectedColor: string,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...base,\n filter: hasActiveEventState,\n layout: {\n ...base.layout,\n 'text-allow-overlap': true,\n },\n paint: {\n ...base.paint,\n 'text-color': selectedColor,\n },\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 = (\n selectedColor: string = SELECTED_COLOR,\n): LayerSpecTemplate<SymbolLayerSpecification> =>\n toSelectedSpec(\n { ...pinLayerBaseSpec, layout: { ...pinLayerBaseSpec.layout, 'icon-size': SELECTED_PIN_ICON_SIZE } },\n selectedColor,\n );\n\n// Per-marker-type builders: each returns the layer spec for one (marker type, role) pair.\n// `buildPlacesLayerSpecs` dispatches to these based on `config.markerType`; roles a marker type\n// doesn't render fall back to `HIDDEN_LAYER_SPEC` so layers stay registered but\n// invisible (marker type switches toggle `visibility` rather than adding/removing layers).\n\nconst buildBaseMapSelectedLayerSpec = (\n mapLibreMap: MapLibreMap,\n selectedColor: string,\n): LayerSpecTemplate<SymbolLayerSpecification> => toSelectedSpec(buildBaseMapPOILayerSpec(mapLibreMap), selectedColor);\n\nconst buildCircleIconMainLayerSpec = (mapLibreMap: MapLibreMap): LayerSpecTemplate<SymbolLayerSpecification> => ({\n ...buildCircleBaseLayerSpec(mapLibreMap),\n filter: hasNoActiveEventState,\n});\n\nconst buildCircleIconSelectedLayerSpec = (\n mapLibreMap: MapLibreMap,\n selectedColor: string,\n): LayerSpecTemplate<SymbolLayerSpecification> => toSelectedSpec(buildCircleBaseLayerSpec(mapLibreMap), selectedColor);\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?: IconPositioningMap,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const labelConfig = config?.label;\n const customLayer = config?.layers?.[layerName];\n const evAvailabilityEnabled = config?.evAvailability?.visible === true;\n\n const textField = titleTextField(\n labelConfig?.title,\n evAvailabilityEnabled ? buildTextFieldExpression(config, evAvailabilityEnabled) : undefined,\n );\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// Where the `places` part of `beforeLayerConfig` puts its layers. `selected` and `clusterMixedCount`\n// share the part's anchor, the top of the stack by default, and stack in that order; with a target,\n// `micro` leaves the place labels for the markers.\nconst placesPartAnchors = (\n config: PlacesModuleConfig | undefined,\n): { microBeforeID: string; partAnchor: { beforeID?: string } } => {\n const placesBeforeID = partBeforeLayerIDOf(config?.beforeLayerConfig, 'places', undefined);\n return {\n microBeforeID:\n beforeLayerTargetOf(config?.beforeLayerConfig, 'places') === undefined\n ? mapStyleLayerIDs.lowestPlaceLabel\n : 'main',\n partAnchor: placesBeforeID ? { beforeID: placesBeforeID } : {},\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 selectedColor: string = SELECTED_COLOR,\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 marker type switches only update its filter/paint/layout\n // (fast path via `updateLayersAndSource` + `changeLayerProps`) rather than adding or\n // removing a layer. For marker types 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` marker type 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?.markerType === 'base-map') {\n main = { ...buildBaseMapPOILayerSpec(mapLibreMap), minzoom: 10 };\n micro = buildPOIMicroLikeLayerSpec(mapLibreMap);\n selected = buildBaseMapSelectedLayerSpec(mapLibreMap, selectedColor);\n } else if (config?.markerType === 'circle-icon') {\n main = buildCircleIconMainLayerSpec(mapLibreMap);\n selected = buildCircleIconSelectedLayerSpec(mapLibreMap, selectedColor);\n } else if (config?.markerType === 'pin-clustered') {\n // The pin layers stay the same as the regular `pin` marker type 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` marker type 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, hasNoActiveEventState] };\n selected = {\n ...selectedPinLayerSpec(selectedColor),\n filter: ['all', isNotClusterFilter, hasActiveEventState],\n };\n const microBase = buildPOIMicroLikeLayerSpec(mapLibreMap);\n micro = {\n ...microBase,\n filter: microBase.filter\n ? (['all', isNotClusterFilter, microBase.filter] as ExpressionSpecification)\n : isNotClusterFilter,\n };\n const badge = config?.cluster?.badge;\n cluster = buildClusterPinLayerSpec();\n clusterBadge = buildClusterBadgeCircleLayerSpec(badge);\n clusterCount = buildClusterCountTextLayerSpec(badge);\n clusterMixedBadge = buildClusterMixedBadgeCircleLayerSpec(badge);\n clusterMixedCount = buildClusterMixedCountTextLayerSpec(badge);\n } else {\n // pin (default)\n main = pinLayerSpec;\n selected = selectedPinLayerSpec(selectedColor);\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 const { microBeforeID, partAnchor } = placesPartAnchors(config);\n\n return {\n micro: {\n ...withConfig(micro, config, 'micro', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: microBeforeID,\n },\n main: {\n ...withConfig(main, config, 'main', styleLightDarkTheme, iconTextOffsetScales),\n beforeID: 'selected',\n },\n selected: {\n ...withConfig(selected, config, 'selected', styleLightDarkTheme, iconTextOffsetScales),\n ...partAnchor,\n },\n cluster: { ...mergeLayer(cluster, config?.layers?.cluster), beforeID: 'clusterBadge' },\n clusterBadge: { ...mergeLayer(clusterBadge, config?.layers?.clusterBadge), beforeID: 'clusterCount' },\n clusterCount: { ...mergeLayer(clusterCount, config?.layers?.clusterCount), beforeID: 'clusterMixedBadge' },\n clusterMixedBadge: {\n ...mergeLayer(clusterMixedBadge, config?.layers?.clusterMixedBadge),\n beforeID: 'clusterMixedCount',\n },\n clusterMixedCount: { ...mergeLayer(clusterMixedCount, config?.layers?.clusterMixedCount), ...partAnchor },\n ...config?.layers?.additional,\n };\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { MAP_REGULAR_FONT } from '../../shared/layers/commonLayerProps';\nimport { themeAdaptiveLabelColors, themeAdaptiveLabelShadeOf } from '../../shared/layers/labelProperties';\nimport { mergeLayer } from '../../shared/layers/utils';\nimport type { PlaceConnectionsConfig } from '../types/placesModuleConfig';\nimport { SELECTED_COLOR } from './placesLayers';\n\n/**\n * Connection colours, per map theme — line mirrors the places `selected` highlight. The module's\n * colour, when set, takes the line, and its shade for the theme the label.\n * @ignore\n */\nexport const getThemeAdaptiveConnectionColors = (lightDark: LightDark, color?: string) => {\n const dark = lightDark === 'dark';\n const labelHaloColor = themeAdaptiveLabelColors(lightDark).haloColor;\n if (color) {\n return { lineColor: color, labelColor: themeAdaptiveLabelShadeOf(color, lightDark), labelHaloColor };\n }\n return { lineColor: dark ? '#5AB6F0' : SELECTED_COLOR, labelColor: dark ? '#A9D6F5' : '#1a5f8a', labelHaloColor };\n};\n\n/**\n * @ignore\n */\nexport type ConnectionLayerSpecs = {\n line: LayerSpecTemplate<LineLayerSpecification>;\n label: LayerSpecTemplate<SymbolLayerSpecification>;\n};\n\n/**\n * The dashed line both place overlays draw, connections and entry points.\n * @ignore\n */\nexport const dashedLineSpec = (\n color: string,\n dashArray: [number, number],\n opacity: number,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n layout: { 'line-cap': 'round' },\n paint: {\n 'line-color': color,\n 'line-width': 1.5,\n 'line-dasharray': dashArray,\n 'line-opacity': opacity,\n },\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 buildBaseLineSpec = (lineColor: string): LayerSpecTemplate<LineLayerSpecification> => {\n const base = dashedLineSpec(lineColor, [2, 2], 0.9);\n return { ...base, layout: { 'line-join': 'round', ...base.layout } };\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 buildBaseLabelSpec = (\n labelColor: string,\n labelHaloColor: string,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line-center',\n 'text-field': ['get', 'label'],\n 'text-font': [MAP_REGULAR_FONT],\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': labelColor,\n 'text-halo-color': labelHaloColor,\n 'text-halo-width': 1.5,\n },\n});\n\n/**\n * Builds layer specifications for rendering place-to-place connections, coloured by\n * {@link getThemeAdaptiveConnectionColors}.\n * @ignore\n */\nexport const buildConnectionLayerSpecs = (\n config: PlaceConnectionsConfig | undefined,\n color: string | undefined,\n lightDark: LightDark = 'light',\n): ConnectionLayerSpecs => {\n const { lineColor, labelColor, labelHaloColor } = getThemeAdaptiveConnectionColors(\n lightDark,\n config?.color ?? color,\n );\n return {\n line: mergeLayer(buildBaseLineSpec(lineColor), config?.layers?.line),\n label: mergeLayer(\n buildBaseLabelSpec(config?.label?.color ?? labelColor, config?.label?.haloColor ?? labelHaloColor),\n config?.layers?.label,\n ),\n };\n};\n","import type { CircleLayerSpecification, ExpressionSpecification, LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { hasActiveEventState } from '../../shared/layers/eventState';\nimport { mergeLayer } from '../../shared/layers/utils';\nimport type { PlaceEntryPointsConfig } from '../types/placesModuleConfig';\nimport { dashedLineSpec, getThemeAdaptiveConnectionColors } from './connectionsLayers';\n\nexport const ENTRY_POINTS_DEFAULT_MIN_ZOOM = 15;\n\n// The connections palette, the module's colour included: its darker label colour on light maps, its\n// line colour on dark ones.\nconst getThemeAdaptiveEntryPointColors = (lightDark: LightDark, color: string | undefined) => {\n const { lineColor, labelColor, labelHaloColor } = getThemeAdaptiveConnectionColors(lightDark, color);\n return { color: lightDark === 'dark' ? lineColor : labelColor, strokeColor: labelHaloColor };\n};\n\nconst isMainEntryPoint: ExpressionSpecification = ['==', ['get', 'type'], 'main'];\n\n/**\n * @ignore\n */\nexport type EntryPointLayerSpecs = {\n line: LayerSpecTemplate<LineLayerSpecification>;\n point: LayerSpecTemplate<CircleLayerSpecification>;\n};\n\nconst buildBaseLineSpec = (color: string, minZoom: number): LayerSpecTemplate<LineLayerSpecification> => ({\n ...dashedLineSpec(color, [1, 2], 0.8),\n minzoom: minZoom,\n filter: ['==', ['geometry-type'], 'LineString'],\n});\n\n// Unclassified entry points (reverse geocoding) share the `minor` size. A hovered or clicked one grows.\nconst buildBasePointSpec = (\n color: string,\n strokeColor: string,\n minZoom: number,\n): LayerSpecTemplate<CircleLayerSpecification> => ({\n type: 'circle',\n minzoom: minZoom,\n filter: ['==', ['geometry-type'], 'Point'],\n paint: {\n 'circle-color': color,\n 'circle-radius': ['+', ['case', isMainEntryPoint, 5, 3.5], ['case', hasActiveEventState, 1.5, 0]],\n 'circle-stroke-width': 1.5,\n 'circle-stroke-color': strokeColor,\n },\n});\n\n/**\n * Builds layer specifications for rendering the entry points of places.\n *\n * `lowestUnclusteredZoom` is a floor under `config.minZoom`: the first zoom where places are no\n * longer clustered, when they are. `moduleColor` is `PlacesModuleConfig.color`, else the map's accent,\n * if either is set.\n * @ignore\n */\nexport const buildEntryPointLayerSpecs = (\n config: PlaceEntryPointsConfig | undefined,\n lightDark: LightDark = 'light',\n lowestUnclusteredZoom = 0,\n moduleColor?: string,\n): EntryPointLayerSpecs => {\n const { color, strokeColor } = getThemeAdaptiveEntryPointColors(lightDark, moduleColor);\n const minZoom = Math.max(config?.minZoom ?? ENTRY_POINTS_DEFAULT_MIN_ZOOM, lowestUnclusteredZoom);\n return {\n line: mergeLayer(buildBaseLineSpec(color, minZoom), config?.layers?.line),\n point: mergeLayer(buildBasePointSpec(color, strokeColor, minZoom), config?.layers?.point),\n };\n};\n","import type { CommonPlaceProps, Place } from '@tomtom-org/maps-sdk/core';\nimport type {\n CircleLayerSpecification,\n DataDrivenPropertyValueSpecification,\n LineLayerSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport type { MapStylePOICategory } from '../../pois/util/poiCategoryMapping';\nimport type {\n CustomImage,\n FeatureLabelConfig,\n GeoJSONSourceClusterOptions,\n HasAdditionalLayersConfig,\n LabelConfig,\n MapModuleCommonConfig,\n PartsBeforeLayerConfig,\n PinIconConfig,\n SerializableCustomImage,\n SerializableFeatureLabelConfig,\n SerializablePinIconConfig,\n ToBeAddedLayerSpecTemplate,\n} from '../../shared';\n\n/**\n * The kind of marker places are drawn with.\n *\n * @remarks\n * - `pin`: Traditional map pin markers (teardrop shape)\n * - `circle-icon`: Simple circular markers\n * - `base-map`: Mimics the map's built-in POI layer style with category icons (combines `main`, `selected`, and `micro` layers). For micro-only rendering, hide the `main` layer via `layers.main.layout.visibility = 'none'`.\n * - `pin-clustered`: Same teardrop pins as `pin` for un-clustered places, with nearby\n * places aggregated into a single icon. Single-category clusters render the matching\n * base-map POI sprite with a small count badge in the top-right; mixed-category\n * clusters render a larger centred count circle in place of the icon. Cluster features\n * expose `clusterBaseMapIconIDs` (comma-separated, deduplicated) on their properties.\n * {@link PlacesModuleConfig.cluster} configures both.\n *\n * @group Places\n */\nexport type PlacesMarkerType = (typeof placesMarkerTypes)[number];\n\n/**\n * All valid {@link PlacesMarkerType} values.\n *\n * @group Places\n */\nexport const placesMarkerTypes = ['pin', 'circle-icon', 'base-map', 'pin-clustered'] as const;\n\n/**\n * Configuration for EV charging station availability display.\n *\n * @remarks\n * **Opt-In Feature:**\n * - Hidden by default - set `visible: true` to show it\n * - Requires fetching availability via {@link getPlacesWithEVAvailability}\n * - Each PlacesModule can enable/disable independently\n *\n * When visible, displays formatted availability text (e.g., \"3/10\") below the station name\n * with color-coding based on availability ratio: green (high), orange (limited), red (none/low).\n *\n * @example\n * ```typescript\n * // Enable with defaults\n * const places = await PlacesModule.create(map, {\n * evAvailability: { visible: true }\n * });\n * const stations = await discoverPlaces({ filters: { poiCategories: ['CHARGING_LOCATION'] } });\n * places.show(await getPlacesWithEVAvailability(stations));\n *\n * // Custom threshold and format\n * const places = await PlacesModule.create(map, {\n * evAvailability: {\n * visible: true,\n * threshold: 0.5,\n * formatText: (available, total) => `${available} of ${total}`\n * }\n * });\n *\n * // Combine with custom icon\n * const places = await PlacesModule.create(map, {\n * icon: {\n * categoryIcons: [{\n * id: 'CHARGING_LOCATION',\n * image: customEvIconSvg,\n * pixelRatio: 2\n * }]\n * },\n * evAvailability: { visible: true }\n * });\n * ```\n *\n * @group Places\n */\nexport type EVAvailabilityConfig = {\n /**\n * Shows the availability on EV charging station pins.\n *\n * @remarks\n * Must be explicitly set to `true` to display availability.\n */\n visible?: boolean;\n\n /**\n * Availability ratio threshold for determining available vs occupied.\n *\n * @remarks\n * - `ratio >= threshold`: Available (green)\n * - `ratio < threshold`: Occupied (red)\n *\n * @default 0.3\n */\n threshold?: number;\n\n /**\n * Custom function to format the availability text.\n *\n * @param available - Number of available charging points\n * @param total - Total number of charging points\n * @returns Formatted availability text\n *\n * @default (available, total) => `${available}/${total}`\n */\n formatText?: (available: number, total: number) => string;\n};\n\n/**\n * The default place icon: the pin drawn for clicked locations and addresses without a specific\n * category. A `style.fillColor` left out takes {@link PlacesModuleConfig.color}.\n *\n * @group Places\n */\nexport type DefaultPlaceIconConfig = PinIconConfig;\n\n/**\n * Picks the icon of each feature a module draws, from the feature itself.\n *\n * @remarks\n * - `imageID`: `fn` returns the id of an image in the style's sprite, or of one the module adds.\n * - `poiCategory`: `fn` returns a POI category, drawn with the icon the SDK has for it, so data\n * that is not a search result can still look like one.\n *\n * `fn` returning `undefined` leaves that feature the icon it has without a mapping.\n *\n * @typeParam F - The feature `fn` receives.\n *\n * @group Places\n */\nexport type IconMapping<F> =\n | { to: 'imageID'; fn: (feature: F) => string | undefined }\n | { to: 'poiCategory'; fn: (feature: F) => MapStylePOICategory | undefined };\n\n/**\n * Configuration for place marker icons.\n *\n * Controls the visual appearance of place markers including custom icons.\n *\n * @example\n * ```typescript\n * // Custom icons for specific categories\n * const iconConfig: PlaceIconConfig = {\n * categoryIcons: [\n * { id: 'RESTAURANT', image: '/icons/food.png', pixelRatio: 2 },\n * { id: 'HOTEL_OR_MOTEL', image: '/icons/hotel.png', pixelRatio: 2 }\n * ]\n * };\n * ```\n *\n * @group Places\n */\nexport type PlaceIconConfig<P extends CommonPlaceProps = CommonPlaceProps> = {\n /**\n * Configuration for the default place icon.\n * * The default icon is the one used for clicked locations and addresses without a specific category, or via custom mapping.\n */\n default?: DefaultPlaceIconConfig;\n\n /**\n * Array of custom icons for specific place categories.\n *\n * When provided, places matching these categories will use the custom icons\n * instead of the default style.\n *\n * @remarks\n * A place takes the entry for its own category, else the first entry for the map category its\n * category falls in, as the map's own POIs do: `RESTAURANT` reaches an `ITALIAN_RESTAURANT`, and\n * `CAFE` a `CAFE_PUB`. That second match takes only entries with an `image` and no\n * `availabilityLevel`, so the same list given to {@link POIsModuleConfig.icon} restyles both alike.\n */\n categoryIcons?: CustomImage<MapStylePOICategory>[];\n\n /**\n * Custom mapping to determine the icon for a given place.\n *\n * @remarks\n * `fn` receives places of the shape the module was created for, so it reads your own\n * properties typed. A `poiCategory` resolves through the marker type, as a place's own\n * category does.\n *\n * @example\n * ```typescript\n * // Direct image ID mapping\n * mapping: {\n * to: 'imageID',\n * fn: (place) => place.properties.poi?.name?.includes('urgent') ? 'urgent-icon' : 'default-icon'\n * }\n *\n * // POI category mapping, from a property of your own\n * type Venue = CommonPlaceProps & { venueCategory?: MapStylePOICategory };\n * await PlacesModule.create<Venue>(map, {\n * icon: { mapping: { to: 'poiCategory', fn: (place) => place.properties.venueCategory ?? 'RESTAURANT' } },\n * });\n * ```\n */\n mapping?: IconMapping<Place<P>>;\n};\n\n/**\n * The content, look and position of the labels next to place markers.\n *\n * @remarks\n * An unset {@link LabelConfig} field keeps the marker type's own label: under `base-map` the map\n * style's POI labels; otherwise `#333333` text on a `#FFFFFF` halo (the reverse on a dark map),\n * 14 to 16 pixels and a 1 to 1.5 pixel halo as the zoom grows, fully opaque. The label of a hovered\n * or clicked place takes {@link PlacesModuleConfig.color} instead.\n *\n * Set labels here rather than with MapLibre's `setPaintProperty` / `setLayoutProperty` on the\n * module's layers: a style change and a marker type change rebuild those layers from this config,\n * dropping anything written on them directly. `title` as a function of the place has no MapLibre\n * equivalent at all.\n *\n * @example\n * ```typescript\n * // Simple text from place name\n * const label: PlaceLabelConfig = {\n * title: (place) => place.properties.poi?.name || place.properties.address.freeformAddress\n * };\n *\n * // Using MapLibre expression with styling\n * const styledLabel: PlaceLabelConfig = {\n * title: ['get', 'title'],\n * size: 14,\n * color: '#333',\n * haloColor: '#fff',\n * haloWidth: 2\n * };\n * ```\n *\n * @group Places\n */\nexport type PlaceLabelConfig<P extends CommonPlaceProps = CommonPlaceProps> = FeatureLabelConfig<Place<P>> & {\n /**\n * Text offset from the icon in ems.\n *\n * Applies uniformly to all anchor positions (top, left, right).\n * Positive values increase distance from icon.\n *\n * @default undefined (uses automatic offset calculation based on icon size)\n *\n * @example\n * ```typescript\n * offset: 1.5 // Position text 1.5em away from icon in whichever direction the renderer chooses\n * ```\n */\n offset?: number;\n};\n\n/**\n * Configuration for custom place layer styling with MapLibre specifications.\n *\n * @remarks\n * Provides fine-grained control over place marker layers, allowing customization\n * of both the main place markers and highlighted/selected place markers.\n *\n * The layer IDs are derived from the PlacesModule instance prefix plus the key suffix:\n * - `main`: The primary layer for displaying all places\n * - `selected`: The layer for displaying highlighted/clicked places (rendered on top)\n *\n * All fields are optional. When a field is not provided, the default styling will be used.\n *\n * @example\n * ```typescript\n * const config: PlacesModuleConfig = {\n * layers: {\n * main: {\n * layout: {\n * 'icon-size': 1.2,\n * 'text-size': 14\n * },\n * paint: {\n * 'text-color': '#333'\n * }\n * },\n * selected: {\n * layout: {\n * 'icon-size': 1.5,\n * 'text-allow-overlap': true\n * },\n * paint: {\n * 'text-color': '#3f9cd9'\n * }\n * }\n * }\n * };\n * ```\n *\n * @group Places\n */\nexport type PlaceLayersConfig = {\n /**\n * Main place marker layer specification.\n *\n * @remarks\n * Controls the visual appearance of all place markers on the map.\n * This layer renders places that are not in a highlighted/selected state.\n */\n main?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n\n /**\n * Micro place marker layer specification.\n *\n * @remarks\n * Always registered and stacked below the `main` layer, but only renders features\n * under the `base-map` marker type — in that mode it mirrors the map style's\n * `POI - Micro` layer so place markers blend with the base map's lower-density\n * POI rendering. For other marker types the layer is hidden via\n * `layout.visibility = 'none'` and acts as a no-op, making marker type switches cheap\n * (no add/remove layer churn).\n */\n micro?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n\n /**\n * Selected place marker layer specification.\n *\n * @remarks\n * Controls the visual appearance of places when they are highlighted or selected\n * (e.g., on hover or click events). This layer is rendered on top of the main layer\n * to ensure selected places are always visible.\n *\n * This layer reacts to 'hover' and 'click' event states.\n */\n selected?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n\n /**\n * Cluster pin layer specification.\n *\n * @remarks\n * Always registered and stacked above `selected`, but only renders under the\n * `pin-clustered` marker type and only for single-category clusters — every leaf in\n * the cluster shares the same base-map sprite, picked via a `match` over the\n * aggregated `clusterBaseMapIconIDs` property. Mixed-category clusters fall\n * through to {@link clusterMixedBadge} + {@link clusterMixedCount} instead.\n * For other marker types the layer is hidden via `layout.visibility = 'none'`.\n */\n cluster?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n\n /**\n * Cluster count badge background layer specification.\n *\n * @remarks\n * Circle drawn at the top-right of each single-category cluster pin.\n * Acts as the background for {@link clusterCount}. Only visible under the\n * `pin-clustered` marker type and gated to single-category clusters; mixed\n * clusters use {@link clusterMixedBadge} instead.\n */\n clusterBadge?: Partial<ToBeAddedLayerSpecTemplate<CircleLayerSpecification>>;\n\n /**\n * Cluster count text layer specification.\n *\n * @remarks\n * Count rendered on top of {@link clusterBadge} for single-category\n * clusters. Reads the `point_count_abbreviated` property MapLibre sets on\n * every cluster feature. Only visible under the `pin-clustered` marker type;\n * mixed clusters use {@link clusterMixedCount} instead.\n */\n clusterCount?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n\n /**\n * Centred count badge for mixed-category clusters under the\n * `pin-clustered` marker type.\n *\n * @remarks\n * Replaces the cluster icon entirely when a cluster spans multiple POI\n * categories — the count circle takes the centre with\n * {@link clusterMixedCount} rendered on top. Filter is an \"all clusters\n * with a mixed dedup-concat\" check so single-category clusters fall\n * through to {@link cluster} + {@link clusterBadge} instead.\n */\n clusterMixedBadge?: Partial<ToBeAddedLayerSpecTemplate<CircleLayerSpecification>>;\n\n /**\n * Cluster count text on top of {@link clusterMixedBadge}.\n *\n * @remarks\n * Centred count for mixed-category clusters; mirrors\n * {@link clusterCount} but with no translate so it lands inside the\n * centred badge.\n */\n clusterMixedCount?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n} & HasAdditionalLayersConfig;\n\n/**\n * @group Places\n */\nexport type PlaceLayerName = keyof PlaceLayersConfig;\n\n/**\n * Describes a single connection between two places, to be rendered as a line on the map.\n *\n * @remarks\n * Each endpoint can be given either as a full {@link Place} object or as the `id` of a\n * place that is currently displayed by the {@link PlacesModule} (via `show()`). When an\n * id is given, the module resolves it against its currently shown places to obtain the\n * coordinates; connections referencing unknown ids are silently skipped.\n *\n * An id resolves each time the connections are drawn — on `showConnections()`, a configuration\n * change and a style change. `show()` and `clear()` drop every connection, so none outlives the\n * places it referred to, and none comes back when those places are shown again.\n *\n * Extra arbitrary properties are preserved on the connection and passed to the optional\n * label function, making it easy to render metadata such as distance or travel time\n * along the line.\n *\n * @example\n * ```typescript\n * const connection: PlaceConnectionDisplay = {\n * from: stationPlace, // full Place object\n * to: 'cafe-id-123', // or a shown place's id\n * distanceMeters: 420 // extra metadata available to the label function\n * };\n * ```\n *\n * @group Places\n */\nexport type PlaceConnectionDisplay = {\n /**\n * The first endpoint of the connection. Either a Place object or the id of a\n * place currently shown by the module.\n */\n from: Place | string;\n\n /**\n * The second endpoint of the connection. Either a Place object or the id of a\n * place currently shown by the module.\n */\n to: Place | string;\n\n /**\n * Optional stable id for this connection. When omitted, an id is generated.\n */\n id?: string;\n\n /**\n * Arbitrary extra metadata, preserved on the connection and passed to the label\n * function.\n */\n [key: string]: unknown;\n};\n\n/**\n * MapLibre layer customization for connection lines and labels.\n *\n * @remarks\n * The layer IDs are derived from the PlacesModule instance prefix plus the key suffix:\n * - `line`: the connection line (default: dashed)\n * - `label`: the text label placed along the line\n *\n * @group Places\n */\nexport type PlaceConnectionsLayersConfig = {\n /**\n * Connection line layer specification.\n */\n line?: Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>;\n\n /**\n * Connection label layer specification.\n */\n label?: Partial<ToBeAddedLayerSpecTemplate<SymbolLayerSpecification>>;\n};\n\n/**\n * @group Places\n */\nexport type PlaceConnectionLayerName = keyof PlaceConnectionsLayersConfig;\n\n/**\n * Configuration for rendering connection lines between places.\n *\n * @remarks\n * Controls the appearance of connection lines, the optional label drawn along each\n * line, and MapLibre layer overrides.\n *\n * @example\n * ```typescript\n * const config: PlacesModuleConfig = {\n * connections: {\n * label: { title: (connection) => `${connection.distanceMeters} m` },\n * layers: {\n * line: { paint: { 'line-color': '#3f9cd9' } }\n * }\n * }\n * };\n * ```\n *\n * @group Places\n */\nexport type PlaceConnectionsConfig = {\n /**\n * The labels along the connection lines: their text and colours.\n */\n label?: PlaceConnectionLabelConfig;\n\n /**\n * Colour of the connection lines, over the module's {@link PlacesModuleConfig.color}. Unset, they\n * take that colour, else the map's accent, else a blue that follows the light/dark theme.\n */\n color?: string;\n\n /**\n * Custom MapLibre layer specifications for connection line and label.\n */\n layers?: PlaceConnectionsLayersConfig;\n};\n\n/**\n * The labels {@link PlacesModule} draws along its connection lines.\n *\n * @group Places\n */\nexport type PlaceConnectionLabelConfig = {\n /**\n * The text along a connection line.\n *\n * @remarks\n * Receives the original {@link PlaceConnectionDisplay} (including any custom\n * metadata). When omitted, no label is rendered.\n */\n title?: (connection: PlaceConnectionDisplay) => string;\n\n /**\n * Colour of the labels. Unset: a darker shade of the line colour on a light map, a lighter one\n * on a dark map.\n */\n color?: string;\n\n /**\n * Colour of the halo around the labels. Unset: `'#FFFFFF'` on a light map, `'#333333'` on a dark\n * one, as for the places' own labels.\n */\n haloColor?: string;\n};\n\n/**\n * Appearance of the cluster count badge under the `pin-clustered` marker type.\n *\n * @remarks\n * Each field applies to single-category and mixed-category clusters alike. The matching\n * {@link PlaceLayersConfig} entry (`clusterBadge`, `clusterCount`, `clusterMixedBadge`,\n * `clusterMixedCount`) overrides it for the paint and layout properties it names.\n *\n * @group Places\n */\nexport type PlaceClusterBadgeConfig = {\n /**\n * Badge fill color.\n *\n * @default '#000000'\n */\n color?: DataDrivenPropertyValueSpecification<string>;\n\n /**\n * Badge outline color.\n *\n * @default '#FFFFFF'\n */\n strokeColor?: DataDrivenPropertyValueSpecification<string>;\n\n /**\n * Badge outline width in pixels.\n *\n * @default 1.5\n */\n strokeWidth?: DataDrivenPropertyValueSpecification<number>;\n\n /**\n * Badge radius in pixels, per cluster kind.\n *\n * @remarks\n * `single` is the small badge tucked into the corner of a single-category\n * cluster's icon; `mixed` is the centred badge that replaces the icon\n * entirely for mixed-category clusters, so it defaults larger.\n *\n * @default { single: 11, mixed: 15 }\n */\n radius?: {\n single?: number;\n mixed?: number;\n };\n\n /**\n * Offset of the single-category badge from the cluster icon, in viewport pixels.\n *\n * @remarks\n * Applied to the badge circle and the count text together so they can't\n * drift apart. Mixed-category clusters are always centred and ignore this.\n *\n * @default [12, -14]\n */\n offset?: [number, number];\n\n /**\n * The count inside the badge.\n *\n * @remarks\n * `color` defaults to `'#FFFFFF'` and `size` to 12 pixels.\n */\n label?: Pick<LabelConfig, 'color' | 'size'>;\n};\n\n/**\n * Configuration for the `pin-clustered` marker type.\n *\n * @remarks\n * Ignored by every other {@link PlacesMarkerType}.\n *\n * @example\n * ```typescript\n * const places = await PlacesModule.create(map, {\n * markerType: 'pin-clustered',\n * cluster: {\n * badge: { color: '#3f9cd9', radius: { single: 13, mixed: 18 } }\n * }\n * });\n * ```\n *\n * @group Places\n */\nexport type PlacesClusterConfig = {\n /**\n * Appearance of the cluster count badge.\n */\n badge?: PlaceClusterBadgeConfig;\n\n /**\n * Clustering options forwarded verbatim to the underlying GeoJSON source.\n *\n * @remarks\n * - Changing them with {@link PlacesModule.applyConfig} recreates the places source.\n * - `cluster` itself is not settable: `markerType: 'pin-clustered'` switches clustering on.\n * - `clusterProperties` land on each cluster feature's properties, for layer overrides to read\n * with `['get', '<key>']`. The key `clusterBaseMapIconIDs` is reserved for the module's own.\n *\n * @example\n * ```typescript\n * const places = await PlacesModule.create(map, {\n * markerType: 'pin-clustered',\n * cluster: {\n * source: {\n * clusterRadius: 100,\n * clusterMaxZoom: 14,\n * clusterMinPoints: 3,\n * // Total charging points across the cluster\n * clusterProperties: { connectors: ['+', ['get', 'connectorCount']] }\n * },\n * },\n * layers: {\n * clusterCount: { layout: { 'text-field': ['get', 'connectors'] } }\n * }\n * });\n * ```\n *\n * @default { clusterRadius: 60, clusterMaxZoom: 17 }\n *\n * @see https://maplibre.org/maplibre-style-spec/sources/#cluster-properties\n */\n source?: Omit<GeoJSONSourceClusterOptions, 'cluster' | 'clusterProperties'> & {\n /**\n * Aggregators keyed by the cluster property they produce. Each value is\n * a `[operator, mapExpression]` pair (e.g. `['+', ['get', 'count']]`) or\n * the advanced `[reduceExpression, mapExpression]` form.\n */\n clusterProperties?: Record<string, unknown>;\n };\n};\n\n/**\n * Which shown places get their entry points drawn.\n *\n * @remarks\n * - `all`: every place that has entry points.\n * - `selected`: hovered or clicked places, the ones the `selected` layer highlights. A\n * `recently-hovered` place counts too, so the pointer can cross the map to an entrance.\n * - `clicked`: clicked places, which stay clicked until the next click lands elsewhere.\n *\n * `selected` and `clicked` follow the module's event states, which exist once an event handler is\n * registered on the module or {@link PlacesModule.putEventState} is called. Hovering or clicking an\n * entry point keeps the entry points of its place drawn when a handler covers them: one on `events`\n * or `events.entryPoints`, not on `events.places` alone.\n *\n * @group Places\n */\nexport type PlaceEntryPointsShowFor = 'all' | 'selected' | 'clicked';\n\n/**\n * MapLibre layer customization for entry points.\n *\n * @remarks\n * The layer IDs are derived from the PlacesModule instance prefix:\n * - `point`: a circle on each entry point, larger for a `main` one than for the rest\n * - `line`: a dotted line from the place to each of its entry points\n *\n * @group Places\n */\nexport type PlaceEntryPointsLayersConfig = {\n /**\n * Entry point circle layer specification.\n */\n point?: Partial<ToBeAddedLayerSpecTemplate<CircleLayerSpecification>>;\n\n /**\n * Place-to-entry-point line layer specification.\n */\n line?: Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>;\n};\n\n/**\n * Configuration for drawing the entry points (entrances) of the shown places.\n *\n * @remarks\n * Draws `properties.entryPoints` of each place, whichever service returned it.\n *\n * @example\n * ```typescript\n * const places = await PlacesModule.create(map, {\n * entryPoints: { showFor: 'clicked', minZoom: 16 }\n * });\n * // The handler is what makes places clickable, and so what lets them reach the `click` state.\n * places.events.on('click', (feature) => console.log(feature.properties.title));\n * ```\n *\n * @group Places\n */\nexport type PlaceEntryPointsConfig = {\n /**\n * Which places get their entry points drawn.\n *\n * @default 'selected'\n */\n showFor?: PlaceEntryPointsShowFor;\n\n /**\n * Zoom level below which no entry point is drawn.\n *\n * @remarks\n * Under the `pin-clustered` marker type it is never lower than the first zoom without clusters:\n * `cluster.source.clusterMaxZoom` plus one, 18 by default.\n *\n * @default 15\n */\n minZoom?: number;\n\n /**\n * Custom MapLibre layer specifications for the entry point circles and lines.\n */\n layers?: PlaceEntryPointsLayersConfig;\n};\n\n/**\n * Where the places module's layers sit in the map's layer stack: one target for everything it\n * draws, or one per part of it.\n *\n * @remarks\n * The parts, named after the module's event scopes, and where each sits by default:\n * - `places` — the markers and their labels, selected places, clusters with their badges and\n * counts, and entry points. Above the map's layers, except the small POI dots the `base-map` and\n * `pin-clustered` marker types draw, which sit below the map's place labels. A target puts them\n * all beneath it, the dots right under the markers.\n * - `connections` — the lines {@link PlacesModule.showConnections} draws, and their labels. Below\n * the map's labels.\n *\n * Each part keeps its own layers' order wherever it goes. A target the style lacks puts the part\n * on top.\n *\n * @example\n * ```typescript\n * // The markers under the map's labels, the connections where they are\n * const underLabels: PlacesBeforeLayerConfig = { places: 'lowestLabel' };\n * // Everything on top of the map\n * const everything: PlacesBeforeLayerConfig = 'top';\n * ```\n *\n * @group Places\n */\nexport type PlacesBeforeLayerConfig = PartsBeforeLayerConfig<'places' | 'connections'>;\n\n/**\n * Configuration options for the PlacesModule.\n *\n * Controls the appearance and behavior of place markers and labels displayed on the map.\n *\n * @example\n * ```typescript\n * // Basic configuration\n * const config: PlacesModuleConfig = {\n * markerType: 'pin',\n * color: '#E4572E',\n * label: {\n * title: (place) => place.properties.poi?.name || 'Unknown',\n * size: 12\n * }\n * };\n *\n * // Advanced configuration with custom properties\n * const config: PlacesModuleConfig = {\n * markerType: 'base-map',\n * icon: {\n * categoryIcons: [\n * { id: 'RESTAURANT', image: '/icons/restaurant.png' }\n * ]\n * },\n * label: {\n * title: ['get', 'title'],\n * size: 14,\n * color: '#333',\n * haloColor: '#fff',\n * haloWidth: 2\n * },\n * extraFeatureProps: {\n * category: (place) => place.properties.poi?.localizedCategories?.[0],\n * hasParking: (place) => place.properties.poi?.name?.includes('parking')\n * }\n * };\n * ```\n *\n * @see {@link PlacesModuleSerializableConfig} for the part that survives JSON.\n *\n * @group Places\n */\nexport type PlacesModuleConfig<P extends CommonPlaceProps = CommonPlaceProps> = MapModuleCommonConfig & {\n /**\n * The kind of marker all places are drawn with.\n *\n * @remarks\n * {@link PlaceIconConfig.categoryIcons} replace its icon for the categories they name, under\n * every marker type.\n *\n * @default 'pin'\n */\n markerType?: PlacesMarkerType;\n\n /**\n * The colour of this module's own drawing: the default pin fill, the label of a hovered or\n * clicked place, connection lines and entry points. Connection labels take a darker shade of it\n * on a light map and a lighter one on a dark map; entry points take the darker one on a light map.\n *\n * @remarks\n * A more specific setting wins for its element: `icon.default.style.fillColor` for the pin,\n * `label.color` or a `layers.*` paint override for a label or layer. Unset, it is the map's\n * accent colour ({@link TomTomMap.mapColors}), and without one the SDK's own colours. Cluster\n * badges do not take it: {@link PlaceClusterBadgeConfig.color} sets them.\n */\n color?: string;\n\n /**\n * Whether the module draws anything: places, connections and entry points.\n *\n * @remarks\n * `show()` and `clear()` never change it — a hidden module keeps its data until\n * {@link PlacesModule.setVisible} shows it again. Layers the marker type does not use, and any hidden\n * through {@link PlacesModuleConfig.layers | layers}, stay hidden while the module is shown.\n *\n * @default true\n */\n visible?: boolean;\n\n /**\n * The default pin, and custom icons per place category.\n */\n icon?: PlaceIconConfig<P>;\n\n /**\n * How the `pin-clustered` marker type clusters places and draws the cluster badge.\n */\n cluster?: PlacesClusterConfig;\n\n /**\n * The labels next to place markers: their content, styling and position.\n */\n label?: PlaceLabelConfig<P>;\n\n /**\n * MapLibre overrides for the places layers, for control the other settings do not offer.\n *\n * @remarks\n * Each override lays its `layout` and `paint` properties over the layer's own, one by one, and\n * wins over `label` and `color` for the properties it names.\n */\n layers?: PlaceLayersConfig;\n\n /**\n * Where the module's layers sit in the map's layer stack, for everything it draws or per part.\n *\n * @remarks\n * Left out, every layer sits where {@link PlacesBeforeLayerConfig} says it does by default.\n */\n beforeLayerConfig?: PlacesBeforeLayerConfig;\n\n /**\n * Configuration for EV charging station availability display.\n *\n * @remarks\n * Hidden by default - set `visible: true` to show availability on EV stations.\n * Requires calling {@link getPlacesWithEVAvailability} to fetch availability data.\n *\n * @default undefined (disabled)\n */\n evAvailability?: EVAvailabilityConfig;\n\n /**\n * Configuration for connection lines drawn between places via\n * {@link PlacesModule.showConnections}.\n *\n * @remarks\n * When omitted, connections rendered via `showConnections` use default styling\n * (a dashed line) and no label.\n */\n connections?: PlaceConnectionsConfig;\n\n /**\n * Configuration for drawing the entry points of the shown places.\n *\n * @remarks\n * When omitted, entry points are not drawn.\n */\n entryPoints?: PlaceEntryPointsConfig;\n\n /**\n * Additional properties to compute for each place feature.\n *\n * These properties are added to the feature and can be used in styling expressions\n * or event handlers. Values can be static or computed from place data.\n *\n * @remarks\n * Useful for:\n * - Adding computed flags for conditional styling via 'layers' config.\n * - Extracting nested properties for easier access\n * - Adding business logic properties\n *\n * @example\n * ```typescript\n * extraFeatureProps: {\n * // Static property\n * source: 'search-results',\n *\n * // Computed property\n * category: (place) => place.properties.poi?.categories?.[0],\n * brand: (place) => place.properties.poi?.brands?.[0] ?? '',\n * hasOpeningHours: (place) => !!place.properties.poi?.openingHours,\n *\n * // Complex computation\n * distanceFromCenter: (place) => {\n * const coords = place.geometry.coordinates;\n * return Math.sqrt(Math.pow(coords[0] - 4.9, 2) + Math.pow(coords[1] - 52.3, 2));\n * }\n * }\n * ```\n */\n extraFeatureProps?: Record<string, ExtraFeatureProp<P>>;\n};\n\n/**\n * A value a GeoJSON feature property can hold.\n *\n * @group Places\n */\nexport type FeaturePropValue =\n | string\n | number\n | boolean\n | null\n | undefined\n | FeaturePropValue[]\n | { [key: string]: FeaturePropValue };\n\n/**\n * An entry of {@link PlacesModuleConfig.extraFeatureProps}: a fixed value written onto every place\n * feature, or a function computing one per place.\n *\n * @typeParam P - The place properties the callbacks receive. Name it on the config\n * (`PlacesModuleConfig<MyPlaceProps>`) to have callbacks typed for the places you show.\n *\n * @group Places\n */\nexport type ExtraFeatureProp<P extends CommonPlaceProps = CommonPlaceProps> =\n | FeaturePropValue\n | ((place: Place<P>) => FeaturePropValue);\n\n/**\n * The part of a {@link PlacesModuleConfig} that survives JSON, for saving\n * `getConfig()` and replaying it with `applyConfig()`.\n *\n * @remarks\n * It leaves out what JSON cannot carry, which a replay adds back in code:\n * - `label.title` as a function — a MapLibre expression serializes.\n * - `connections.label.title`, `evAvailability.formatText` and `icon.mapping`, all functions.\n * - `extraFeatureProps` entries that are functions — fixed values serialize.\n * - `icon.default.image` and `icon.categoryIcons[].image` as an `HTMLImageElement` — see {@link SerializableCustomImage}.\n *\n * @example\n * ```typescript\n * const saved = JSON.stringify(places.getConfig());\n *\n * const state: PlacesModuleSerializableConfig = JSON.parse(saved);\n * places.applyConfig({\n * ...state,\n * connections: {\n * ...state.connections,\n * label: { ...state.connections?.label, title: (connection) => `${connection.distanceMeters} m` },\n * },\n * });\n * ```\n *\n * @group Places\n */\nexport type PlacesModuleSerializableConfig = Omit<\n PlacesModuleConfig,\n 'label' | 'icon' | 'evAvailability' | 'connections' | 'extraFeatureProps'\n> & {\n label?: SerializableFeatureLabelConfig<PlaceLabelConfig>;\n icon?: Omit<PlaceIconConfig, 'default' | 'categoryIcons' | 'mapping'> & {\n default?: SerializablePinIconConfig;\n categoryIcons?: SerializableCustomImage<MapStylePOICategory>[];\n };\n evAvailability?: Omit<EVAvailabilityConfig, 'formatText'>;\n connections?: Omit<PlaceConnectionsConfig, 'label'> & { label?: Omit<PlaceConnectionLabelConfig, 'title'> };\n extraFeatureProps?: Record<string, FeaturePropValue>;\n};\n","import type { Places } from '@tomtom-org/maps-sdk/core';\nimport type { Feature, FeatureCollection, LineString, Point, Position } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type { EventState } from '../../shared';\nimport { isClickEventType } from '../../shared/eventUtils';\nimport { findFeatureByRefId } from '../../shared/featureId';\nimport type { PlaceEntryPointFeature, PlaceEntryPointProps } from '../types/entryPointDisplay';\nimport type { DisplayPlaceProps } from '../types/placeDisplayProps';\nimport type { PlaceEntryPointsConfig, PlaceEntryPointsShowFor } from '../types/placesModuleConfig';\n\n/**\n * Each entry point is two features sharing one id: its circle, then its line. The circle comes\n * first, so an id lookup (event states, event payloads) always lands on it.\n * @ignore\n */\nexport type EntryPointFeatureCollection = FeatureCollection<Point | LineString, PlaceEntryPointProps>;\n\nexport const ENTRY_POINTS_DEFAULT_SHOW_FOR: PlaceEntryPointsShowFor = 'selected';\n\nconst toEntryPointID = (placeID: string, entryPointIndex: number): string =>\n `${placeID}-entry-point-${entryPointIndex}`;\n\nconst isShownFor = (showFor: PlaceEntryPointsShowFor, eventState: EventState | undefined): boolean => {\n if (showFor === 'all') return true;\n\n if (showFor === 'selected') return eventState !== undefined;\n\n return isClickEventType(eventState);\n};\n\n/**\n * Turns the entry points of the shown places into circle and line features, keeping those of the\n * places `config.showFor` selects.\n *\n * `previous` is what is drawn now: the event states the events proxy put on entry points carry\n * over, and an entry point in a matching state keeps the entry points of its place drawn.\n * @ignore\n */\nexport const prepareEntryPointsForDisplay = (\n places: Places<DisplayPlaceProps>,\n previous: EntryPointFeatureCollection,\n config: PlaceEntryPointsConfig | undefined,\n): EntryPointFeatureCollection => {\n const features: Feature<Point | LineString, PlaceEntryPointProps>[] = [];\n if (!config) return { type: 'FeatureCollection', features };\n\n const showFor = config.showFor ?? ENTRY_POINTS_DEFAULT_SHOW_FOR;\n const previousEventStates = new Map<string, EventState>();\n for (const feature of previous.features) {\n if (feature.properties.eventState) {\n previousEventStates.set(feature.properties.id, feature.properties.eventState);\n }\n }\n\n for (const place of places.features) {\n const placeID = place.properties.id;\n const entryPoints = place.properties.entryPoints ?? [];\n const ids = entryPoints.map((_, entryPointIndex) => toEntryPointID(placeID, entryPointIndex));\n const shown =\n isShownFor(showFor, place.properties.eventState) ||\n ids.some((id) => isShownFor(showFor, previousEventStates.get(id)));\n if (!shown) continue;\n\n for (const [entryPointIndex, { position, ...entryPoint }] of entryPoints.entries()) {\n const id = ids[entryPointIndex];\n const eventState = previousEventStates.get(id);\n const properties: PlaceEntryPointProps = { ...entryPoint, id, placeID, entryPointIndex };\n features.push(\n // Only the circle carries the state: the events proxy clears it there alone.\n {\n type: 'Feature',\n id,\n geometry: { type: 'Point', coordinates: position },\n properties: eventState ? { ...properties, eventState } : properties,\n },\n {\n type: 'Feature',\n id,\n geometry: { type: 'LineString', coordinates: [place.geometry.coordinates, position] },\n properties,\n },\n );\n }\n }\n\n return { type: 'FeatureCollection', features };\n};\n\n// A line runs from the place to the entry point, so its second vertex is the entry point.\nconst toEntryPointPosition = (geometry: Point | LineString): Position =>\n geometry.type === 'Point' ? geometry.coordinates : geometry.coordinates[1];\n\n/**\n * Builds what `events.entryPoints` handlers receive from a hit on either entry point layer.\n * @ignore\n */\nexport const toEntryPointEventFeature = (\n feature: MapGeoJSONFeature,\n shownPlaces: Places<DisplayPlaceProps>,\n): PlaceEntryPointFeature => {\n // Only the entry point layers feed this, and they hold nothing but these features.\n const properties = feature.properties as PlaceEntryPointProps;\n return {\n type: 'Feature',\n id: properties.id,\n geometry: { type: 'Point', coordinates: toEntryPointPosition(feature.geometry as Point | LineString) },\n properties: { ...properties, place: findFeatureByRefId(shownPlaces.features, properties.placeID)?.feature },\n };\n};\n","import type { KnobRange } from '../shared';\nimport {\n everyOf,\n type KnobDefinition,\n LABEL_HALO_WIDTH_RANGE,\n LABEL_SIZE_RANGE,\n OPACITY_RANGE,\n withIds,\n ZOOM_RANGE,\n} from '../shared/knobUtils';\nimport { mapFonts } from '../shared/layers/commonLayerProps';\nimport {\n CLUSTER_MAX_ZOOM,\n DEFAULT_BADGE_COLOR,\n DEFAULT_BADGE_STROKE_COLOR,\n DEFAULT_BADGE_STROKE_WIDTH,\n DEFAULT_BADGE_TEXT_COLOR,\n DEFAULT_BADGE_TEXT_SIZE,\n DEFAULT_CLUSTER_RADIUS_PX,\n DEFAULT_MIXED_BADGE_RADIUS,\n DEFAULT_SINGLE_BADGE_OFFSET,\n DEFAULT_SINGLE_BADGE_RADIUS,\n} from './layers/clusterLayers';\nimport { ENTRY_POINTS_DEFAULT_MIN_ZOOM } from './layers/entryPointsLayers';\nimport type { PlacesKnob, PlacesKnobId, PlacesKnobValueOf } from './types/placesKnobs';\nimport { type PlaceEntryPointsShowFor, placesMarkerTypes } from './types/placesModuleConfig';\nimport { DEFAULT_EV_AVAILABILITY_THRESHOLD } from './utils/evAvailabilityHelpers';\nimport { ENTRY_POINTS_DEFAULT_SHOW_FOR } from './utils/prepareEntryPointsForDisplay';\n\nconst STROKE_WIDTH_RANGE: KnobRange = { min: 0, max: 5, step: 0.5, bounds: 'hard-min' };\nconst BADGE_RADIUS_RANGE: KnobRange = { min: 4, max: 32, step: 1, bounds: 'hard-min' };\n\nconst placesKnobDefinitions = {\n markerType: {\n kind: 'enum',\n description: 'The kind of marker every place is drawn with; `icon.categoryIcons` replace its icon under each.',\n options: placesMarkerTypes,\n default: 'pin',\n },\n color: {\n kind: 'color',\n description:\n \"Colour of the default pin, the hovered or clicked label, connection lines and entry points. Unset: the map's accent if a theme sets one, else the SDK's own colours.\",\n },\n visible: {\n kind: 'toggle',\n description:\n 'Everything the module draws: places, connections and entry points. `show()` and `clear()` leave it.',\n default: true,\n },\n 'icon.default.style.fillColor': {\n kind: 'color',\n description: 'Fill colour of the default pin, in place of `color`.',\n },\n 'icon.default.style.outlineColor': {\n kind: 'color',\n description: 'Outline colour of the default pin.',\n },\n 'icon.default.style.outlineOpacity': {\n kind: 'number',\n description: 'Outline opacity of the default pin.',\n range: OPACITY_RANGE,\n },\n 'label.size': {\n kind: 'number',\n description:\n \"Label size, in pixels. Unset: 14 to 16 as the zoom grows, or the style's POI labels under `base-map`.\",\n range: LABEL_SIZE_RANGE,\n },\n 'label.color': {\n kind: 'color',\n description:\n \"Label colour. Unset: `#333333`, `#FFFFFF` on a dark map, or the style's POI labels under `base-map`.\",\n },\n 'label.haloColor': {\n kind: 'color',\n description: 'Colour of the halo around the labels. Unset: `#FFFFFF`, `#333333` on a dark map.',\n },\n 'label.haloWidth': {\n kind: 'number',\n description: 'Width of the label halo, in pixels. Unset: 1 to 1.5 as the zoom grows.',\n range: LABEL_HALO_WIDTH_RANGE,\n },\n 'label.opacity': {\n kind: 'number',\n description: 'Label opacity.',\n range: OPACITY_RANGE,\n },\n 'label.offset': {\n kind: 'number',\n description: \"Distance of the label from its icon, in ems. Unset: worked out from the icon's size.\",\n range: { min: 0, max: 5, step: 0.1, bounds: 'hard-min' },\n },\n 'label.font': {\n kind: 'enums',\n description:\n \"Label font faces, in order: the first one available wins. Unset: `Noto-Bold`, or the style's POI labels under `base-map`.\",\n options: mapFonts,\n },\n 'cluster.badge.color': {\n kind: 'color',\n description: 'Fill colour of the cluster count badges; `color` does not reach them.',\n default: DEFAULT_BADGE_COLOR,\n },\n 'cluster.badge.strokeColor': {\n kind: 'color',\n description: 'Outline colour of the cluster count badges.',\n default: DEFAULT_BADGE_STROKE_COLOR,\n },\n 'cluster.badge.strokeWidth': {\n kind: 'number',\n description: 'Outline width of the cluster count badges, in pixels.',\n range: STROKE_WIDTH_RANGE,\n default: DEFAULT_BADGE_STROKE_WIDTH,\n },\n 'cluster.badge.label.color': {\n kind: 'color',\n description: 'Colour of the count on the cluster badges.',\n default: DEFAULT_BADGE_TEXT_COLOR,\n },\n 'cluster.badge.label.size': {\n kind: 'number',\n description: 'Size of the count on the cluster badges, in pixels.',\n range: LABEL_SIZE_RANGE,\n default: DEFAULT_BADGE_TEXT_SIZE,\n },\n 'cluster.badge.radius.single': {\n kind: 'number',\n description: \"Radius of the badge in a single-category cluster's corner, in pixels.\",\n range: BADGE_RADIUS_RANGE,\n default: DEFAULT_SINGLE_BADGE_RADIUS,\n },\n 'cluster.badge.radius.mixed': {\n kind: 'number',\n description: 'Radius of the centred badge a mixed-category cluster is drawn as, in pixels.',\n range: BADGE_RADIUS_RANGE,\n default: DEFAULT_MIXED_BADGE_RADIUS,\n },\n 'cluster.badge.offset': {\n kind: 'offset',\n description:\n \"Shift of a single-category cluster's badge from its icon, in pixels; mixed-category badges stay centred.\",\n range: { min: -32, max: 32, step: 1, bounds: 'soft' },\n default: DEFAULT_SINGLE_BADGE_OFFSET,\n },\n 'cluster.source.clusterRadius': {\n kind: 'number',\n description: 'How near places merge into one cluster, in pixels. Changing it recreates the places source.',\n range: { min: 10, max: 200, step: 10, bounds: 'hard-min' },\n default: DEFAULT_CLUSTER_RADIUS_PX,\n },\n 'cluster.source.clusterMaxZoom': {\n kind: 'number',\n description: 'Last zoom places cluster at; entry points start one zoom above it.',\n range: { min: 0, max: CLUSTER_MAX_ZOOM, step: 1, bounds: 'hard' },\n default: CLUSTER_MAX_ZOOM,\n },\n 'cluster.source.clusterMinPoints': {\n kind: 'number',\n description: \"Fewest places a cluster holds. Unset: MapLibre's, 2.\",\n range: { min: 2, max: 20, step: 1, bounds: 'hard-min' },\n },\n 'evAvailability.visible': {\n kind: 'toggle',\n description:\n \"Free and total charging points under each EV station's name, from `getPlacesWithEVAvailability` data.\",\n default: false,\n },\n 'evAvailability.threshold': {\n kind: 'number',\n description: 'Share of free charging points at or above which a station reads as available.',\n range: { min: 0, max: 1, step: 0.05, bounds: 'hard' },\n default: DEFAULT_EV_AVAILABILITY_THRESHOLD,\n },\n 'entryPoints.showFor': {\n kind: 'enum',\n description: 'Which places have their entry points drawn: all, the hovered or clicked, or the clicked.',\n options: everyOf<PlaceEntryPointsShowFor>()(['all', 'selected', 'clicked']),\n default: ENTRY_POINTS_DEFAULT_SHOW_FOR,\n },\n 'entryPoints.minZoom': {\n kind: 'number',\n description:\n 'Lowest zoom entry points are drawn at; under `pin-clustered`, never below the first without clusters.',\n range: ZOOM_RANGE,\n default: ENTRY_POINTS_DEFAULT_MIN_ZOOM,\n },\n 'connections.color': {\n kind: 'color',\n description:\n \"Colour of the connection lines, over `color`. Unset: `color`, else the map's accent, else a blue that follows the light/dark theme.\",\n },\n 'connections.label.color': {\n kind: 'color',\n description:\n 'Colour of the connection labels. Unset: a darker shade of the line colour on a light map, a lighter one on a dark map.',\n },\n 'connections.label.haloColor': {\n kind: 'color',\n description: 'Colour of the halo around the connection labels. Unset: `#FFFFFF`, `#333333` on a dark map.',\n },\n} as const satisfies { [ID in PlacesKnobId]: KnobDefinition<PlacesKnobValueOf<ID>> };\n\n/**\n * Every places knob with its kind, description, range or options and, where one value holds\n * everywhere, its default. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link PlacesModuleConfig}, at the path its id names.\n * - Not knobs: the label `title`, `clusterProperties`, `evAvailability.formatText`,\n * `icon.default.image`, `icon.categoryIcons`, `icon.mapping`, `connections.label.title`, `layers`,\n * `beforeLayerConfig`, `extraFeatureProps` and `events`, which take callbacks, images, structured\n * or MapLibre values.\n * - `entryPoints` draws nothing until it is set, and `evAvailability` until `visible` is `true`.\n * `cluster` applies under the `pin-clustered` marker type only.\n *\n * @group Places\n */\nexport const placesKnobCatalogue: readonly PlacesKnob[] = /* @__PURE__ */ withIds(placesKnobDefinitions);\n\n/**\n * Every places knob id, in catalogue order.\n *\n * @group Places\n */\nexport const placesKnobIds: readonly PlacesKnobId[] = /* @__PURE__ */ placesKnobCatalogue.map(({ id }) => id);\n","import { isEqual } from 'lodash-es';\nimport type { Map } from 'maplibre-gl';\nimport { ICON_PIXEL_RATIO } from './imageUtils';\nimport { type AddedImage, addOrUpdateImage } from './mapUtils';\nimport type { CustomImage, PinIconConfig, SVGIconStyleOptions } from './types/image';\n\n/**\n * What a pin image was last drawn from: an image of the caller's, or the SDK's pin in a style.\n * @ignore\n */\nexport type PaintedPin = AddedImage | { style: SVGIconStyleOptions };\n\nconst toPaintedPin = (pin: PinIconConfig | undefined, baseStyle: SVGIconStyleOptions): PaintedPin =>\n pin?.image?.image\n ? { image: pin.image.image, pixelRatio: pin.image.pixelRatio ?? ICON_PIXEL_RATIO }\n : { style: { ...baseStyle, ...pin?.style } };\n\n// An image is compared by reference: two `img` elements are different images to MapLibre.\nconst isSamePaint = (next: PaintedPin, painted: PaintedPin): boolean =>\n 'image' in next\n ? 'image' in painted && next.image === painted.image && next.pixelRatio === painted.pixelRatio\n : 'style' in painted && isEqual(next.style, painted.style);\n\n/**\n * Draws the pin `pin` configures under `imageID`: the caller's image, else the SDK's pin, drawn by\n * `drawPin`, in `baseStyle` with the pin's own `style` over it.\n *\n * @remarks\n * A repaint reloads every tile drawing the image, so one that would change nothing is skipped.\n *\n * @param painted What the previous call returned, for this one to compare against.\n * @returns What the pin is drawn from now.\n * @ignore\n */\nexport const paintPin = (\n map: Map,\n imageID: string,\n pin: PinIconConfig | undefined,\n baseStyle: SVGIconStyleOptions,\n drawPin: (style: SVGIconStyleOptions) => HTMLImageElement,\n painted: PaintedPin | undefined,\n): PaintedPin => {\n const next = toPaintedPin(pin, baseStyle);\n if (painted && map.hasImage(imageID) && isSamePaint(next, painted)) return painted;\n\n const image = 'image' in next ? next.image : drawPin(next.style);\n const pixelRatio = 'image' in next ? next.pixelRatio : ICON_PIXEL_RATIO;\n addOrUpdateImage('add-or-update', imageID, image, map, { pixelRatio });\n return next;\n};\n\n/**\n * The image of each custom icon, under `imageIDOf(icon)`. An icon given no image names one the\n * sprite already holds, and is left out.\n * @ignore\n */\nexport const customImagesByID = <I extends string>(\n icons: readonly CustomImage<I>[] | undefined,\n imageIDOf: (icon: CustomImage<I>) => string = (icon) => icon.id,\n): Record<string, AddedImage> => {\n const images: Record<string, AddedImage> = {};\n for (const icon of icons ?? []) {\n if (icon.image)\n images[imageIDOf(icon)] = { image: icon.image, pixelRatio: icon.pixelRatio ?? ICON_PIXEL_RATIO };\n }\n return images;\n};\n\n/**\n * Adds the image of each custom icon the map does not have yet, under the icon's own id.\n *\n * @remarks\n * Never replaces nor removes one, unlike `syncImages`: the ids are the caller's, not suffixed\n * per module, so another module or the style may draw the same image.\n * @ignore\n */\nexport const addCustomImages = (map: Map, icons: readonly CustomImage[] | undefined): void => {\n for (const [imageID, { image, pixelRatio }] of Object.entries(customImagesByID(icons))) {\n addOrUpdateImage('if-not-in-sprite', imageID, image, map, { pixelRatio });\n }\n};\n","import type { DelayMagnitude } from '@tomtom-org/maps-sdk/core';\nimport type { StyleKnobId } from '../styling';\nimport type { TomTomMap } from '../TomTomMap';\n\n/**\n * The style knobs that recolour the style's incident layers, by the delay magnitude each one colours.\n * `trafficIncidentsStyleKnobs` uses them as its keys, which checks each is a traffic incidents knob.\n * @ignore\n */\nexport const incidentColorKnobIds = {\n minor: 'traffic.incidents.colors.minor',\n moderate: 'traffic.incidents.colors.moderate',\n major: 'traffic.incidents.colors.major',\n indefinite: 'traffic.incidents.colors.closed',\n} as const satisfies Partial<Record<DelayMagnitude, `traffic.incidents.${string}`>>;\n\n/**\n * Runs `handler` once any of `knobIds` has been applied to the loaded style, whichever module set it,\n * and only then — for a module whose layers borrow a look from the style layers those knobs restyle.\n * Skipped while a style loads: the knobs are applied again once it has, which runs `handler` then.\n * @ignore\n */\nexport const onStyleKnobsApplied = (\n tomtomMap: TomTomMap,\n knobIds: readonly StyleKnobId[],\n handler: () => void,\n): void => {\n tomtomMap._onStyleKnobsApplied((applied) => {\n if (tomtomMap.mapReady && knobIds.some((id) => applied.has(id))) handler();\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\n return shownPlaces.features.find((place) => place.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?.title;\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","import type { CustomImage } from '../../shared';\nimport { type MapStylePOICategory, toBaseMapPOICategory } from './poiCategoryMapping';\n\n/**\n * A `categoryIcons` entry able to draw a whole map category: one with an image, and no availability\n * level, which the map's POIs do not carry.\n */\ntype MapCategoryIcon = CustomImage<MapStylePOICategory> & Required<Pick<CustomImage, 'image'>>;\n\nconst isMapCategoryIcon = (icon: CustomImage<MapStylePOICategory>): icon is MapCategoryIcon =>\n !!icon.image && !icon.availabilityLevel;\n\n/**\n * The entry each map category is drawn with, keyed by that category: the first listed that can draw it.\n * `POIsModule` draws the map's POIs with these, and `PlacesModule` the places no entry names exactly.\n */\nexport const iconsByMapCategory = (\n categoryIcons: CustomImage<MapStylePOICategory>[] = [],\n): Map<string, MapCategoryIcon> => {\n const icons = new Map<string, MapCategoryIcon>();\n for (const icon of categoryIcons) {\n const mapCategory = toBaseMapPOICategory(icon.id);\n if (mapCategory && !icons.has(mapCategory) && isMapCategoryIcon(icon)) icons.set(mapCategory, icon);\n }\n return icons;\n};\n","import type { MapStylePOICategory } from '../../pois/util/poiCategoryMapping';\nimport type { IconMapping } from '../types/placesModuleConfig';\n\n/**\n * The image id `mapping` picks for `feature`, a POI category resolved through `categoryImageID`.\n * Nothing when there is no mapping or it returns nothing, for the feature to keep its own icon.\n * @ignore\n */\nexport const mappedImageID = <F>(\n feature: F,\n mapping: IconMapping<F> | undefined,\n categoryImageID: (category: MapStylePOICategory) => string | undefined,\n): string | undefined => {\n if (mapping?.to === 'imageID') return mapping.fn(feature);\n\n if (mapping?.to !== 'poiCategory') return undefined;\n\n const category = mapping.fn(feature);\n return category && categoryImageID(category);\n};\n","import { type POICategory, poiCategoriesToID } from '@tomtom-org/maps-sdk/core';\nimport { toPinSpriteImageID } from '../../shared/imageUtils';\n\n// Supported sub-categories for map display pins\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// Workaround: main categories the pin sprite has no image for, each drawn with the image of the\n// category its base-map icon is shared with (see `poiCategoryMapping.ts`):\nconst pinImageSubstitutes: Map<number, number> = new Map(\n (\n [\n ['ADVENTURE_SPORTS_FACILITY', 'SPORTS_CENTER'],\n ['COMMUNITY_CENTER', 'CULTURAL_CENTER'],\n ['EMBASSY', 'GOVERNMENT_OFFICE'],\n ['EMERGENCY_MEDICAL_SERVICE', 'EMERGENCY_ROOM'],\n ['ENTERTAINMENT', 'NIGHTLIFE'],\n ['RESIDENTIAL_ENTRANCE', 'RESIDENTIAL_ACCOMMODATION'],\n ] satisfies [POICategory, POICategory][]\n ).map(([missing, substitute]) => [poiCategoriesToID[missing], poiCategoriesToID[substitute]]),\n);\n\n/**\n * @ignore\n */\nexport const toPinImageID = (categoryID: number | undefined): string | undefined => {\n if (!categoryID) {\n return undefined;\n }\n\n // A supported subcategory has an image of its own; the rest fall back to their main category\n // (the first 4 digits of the category ID):\n const imageCategoryID = supportedPinSubcategories.has(categoryID)\n ? categoryID\n : Number(categoryID.toString().substring(0, 4));\n return toPinSpriteImageID(String(pinImageSubstitutes.get(imageCategoryID) ?? imageCategoryID));\n};\n","import {\n type CommonPlaceProps,\n generateId,\n Place,\n Places,\n POICategory,\n poiCategoriesToID,\n} from '@tomtom-org/maps-sdk/core';\nimport { iconsByMapCategory } from '../../pois/util/categoryIcons';\nimport { type MapStylePOICategory, toBaseMapPOICategory, toBaseMapPOIGroup } from '../../pois/util/poiCategoryMapping';\nimport type { CustomImage } from '../../shared';\nimport { featureTitle } from '../../shared/layers/labelProperties';\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 { ShownPlaces } from '../PlacesModule';\nimport type { DisplayPlaceProps } from '../types/placeDisplayProps';\nimport type { PlaceIconConfig, PlacesMarkerType, PlacesModuleConfig } from '../types/placesModuleConfig';\nimport {\n buildAvailabilityText,\n getAvailabilityRatio,\n getEVAvailabilityIconID,\n isEVStationWithAvailability,\n} from './evAvailabilityHelpers';\nimport { mappedImageID } from './iconMapping';\nimport { toPinImageID } from './toPinImageID';\n\n/**\n * The label {@link PlacesModule} gives a place when {@link PlaceLabelConfig.title} is not a function:\n * the POI name, else the free-form address.\n *\n * @example\n * ```typescript\n * // Brand first, the default label otherwise\n * const places = await PlacesModule.create(map, {\n * label: { title: (place) => place.properties.poi?.brands?.[0] ?? buildPlaceTitle(place) },\n * });\n * ```\n *\n * @group Places\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 marker type.\n */\nconst toImageID = (poiCategory: POICategory, markerType: PlacesMarkerType, 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 (markerType === 'pin' || markerType === '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` marker type 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` marker type.\nconst resolveDefaultPlaceIconID = (config: PlacesModuleConfig, instanceIndex: number): string => {\n const hasCustomDefault = !!config.icon?.default;\n if (!hasCustomDefault && (config.markerType ?? 'pin') === 'base-map') {\n return DEFAULT_BASE_MAP_PLACE_ICON_ID;\n }\n return suffixNumber(DEFAULT_PLACE_ICON_ID, instanceIndex);\n};\n\n// The entry for the place's own category, else the one `POIsModule` draws its map category with.\nconst findCategoryIcon = (\n poiCategory: POICategory | undefined,\n categoryIcons: PlaceIconConfig['categoryIcons'] = [],\n): CustomImage<MapStylePOICategory> | undefined => {\n const mapCategory = poiCategory && toBaseMapPOICategory(poiCategory);\n return (\n categoryIcons.find((customIcon) => customIcon.id === poiCategory) ??\n (mapCategory ? iconsByMapCategory(categoryIcons).get(mapCategory) : undefined)\n );\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 markerType = config.markerType ?? 'pin';\n const defaultPlaceIconID = resolveDefaultPlaceIconID(config, instanceIndex);\n\n const mappedID = mappedImageID(place, config.icon?.mapping, (category) =>\n toImageID(category, markerType, defaultPlaceIconID),\n );\n if (mappedID) return mappedID;\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, markerType);\n if (evAvailabilityIconID) {\n return evAvailabilityIconID;\n }\n\n // Regular custom icon matching (no availability)\n const matchingCustomIcon = findCategoryIcon(poiCategory, config.icon?.categoryIcons);\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, markerType, 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// marker types 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 const mappedCategory = mapping?.to === 'poiCategory' ? mapping.fn(place) : undefined;\n return mappedCategory ?? 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 * Turns any of the shapes {@link PlacesModule.show} accepts into one {@link Places} collection.\n *\n * @remarks\n * A `shown-features` handler receives the input of `show()` as it was given, in any of those shapes.\n *\n * @example\n * ```typescript\n * places.events.on('shown-features', (shown) => {\n * if ('places' in shown) renderResultList(toPlaces(shown.places).features);\n * });\n * ```\n *\n * @group Places\n */\nexport const toPlaces = (places: ShownPlaces): 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 visible.\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?.visible !== 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] === 'CHARGING_LOCATION';\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 visible 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// cluster pin (single-category case). It's the same\n// `poi-<category>` sprite the `base-map` marker type 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` marker type 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 * {@link preparePlacesForDisplay} for one {@link PlacesModule} instance, whose index suffixes the\n * IDs of the images it adds.\n * @ignore\n */\nexport const preparePlacesForInstance = (\n shownPlaces: ShownPlaces,\n instanceIndex: number,\n config: PlacesModuleConfig = {},\n): Places<DisplayPlaceProps> => {\n const places = toPlaces(shownPlaces);\n\n // Only merge EV availability props when explicitly visible\n const mergedExtraFeatureProps =\n config.evAvailability?.visible === 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 = featureTitle(place, config.label?.title, buildPlaceTitle);\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` marker type and the `pin-clustered` micro layer\n // (registered for un-clustered singletons) reuse those expressions,\n // so the leaf needs `category`/`group` populated under either marker type.\n ...((config?.markerType === 'base-map' || config?.markerType === 'pin-clustered') && {\n category: getPOILayerCategoryForPlace(place, config),\n group: getPOIGroupForPlace(place, config),\n }),\n // For the `pin-clustered` marker type 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?.markerType === 'pin-clustered' && {\n [FEATURE_PROPERTY_BASE_MAP_ICON_ID]: getBaseMapIconIDForPlace(place, config),\n }),\n ...extraFeatureProps,\n },\n };\n }),\n };\n};\n\n// A caller names the place shape their `extraFeatureProps`, `label.title` and `icon.mapping` callbacks\n// expect, which is narrower than the `Place` the module hands them — so every config a caller passes\n// crosses into the module here. Narrowing a callback's parameter is the caller asserting what they\n// will be shown; the module reads nothing off these values, it only invokes them and writes the\n// result onto the feature.\n// `Place<never>` accepts a callback declared for any narrower place shape, which is what lets a\n// caller's config in; the cast then hands the module the shape it works in.\nexport const widenConfig = <C extends PlacesModuleConfig<never> | undefined>(config: C) =>\n config as C extends undefined ? undefined : PlacesModuleConfig;\n\n/**\n * What {@link PlacesModule.show} does to the places it is given before drawing them.\n *\n * @remarks\n * `show()` calls it itself; call it to see what the layers, `layers.*` overrides and click handlers\n * read. Each place keeps its properties and gains the {@link PlaceDisplayProps}:\n *\n * - `id`: the place's, or a generated one, copied into `properties` for MapLibre's `promoteId`\n * - `title`: {@link PlaceLabelConfig.title} when it is a function, else {@link buildPlaceTitle}\n * - `iconID`: the image the marker type and {@link PlaceIconConfig} pick\n * - `category` and `group` under the `base-map` and `pin-clustered` marker types\n * - every {@link PlacesModuleConfig.extraFeatureProps} entry, and the EV availability ones when\n * {@link EVAvailabilityConfig.visible} is set\n *\n * An `iconID` naming an image the module adds itself, the default pin or a\n * {@link PlaceIconConfig.categoryIcons} entry, is the first module's name for it.\n * {@link PlacesModule.getShown} returns a given module's places as it drew them.\n *\n * @example\n * ```typescript\n * const [first] = preparePlacesForDisplay(await discoverPlaces({ query: 'coffee' }), { markerType: 'base-map' }).features;\n * first.properties.title; // what the label shows\n * first.properties.category; // what the base-map look keys its colour on\n * ```\n *\n * @group Places\n */\nexport const preparePlacesForDisplay = <P extends CommonPlaceProps = CommonPlaceProps>(\n shownPlaces: ShownPlaces,\n config: PlacesModuleConfig<P> = {},\n): Places<DisplayPlaceProps> => preparePlacesForInstance(shownPlaces, 0, widenConfig(config));\n","import type { CommonPlaceProps, Place, Places } from '@tomtom-org/maps-sdk/core';\nimport type { FeatureCollection } from 'geojson';\nimport { isEqual } from 'lodash-es';\nimport type {\n CircleLayerSpecification,\n LayerSpecification,\n LineLayerSpecification,\n MapGeoJSONFeature,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport {\n AbstractDataOwnedMapModule,\n type CombinedEvents,\n type GeoJSONSourceClusterOptions,\n GeoJSONSourceWithLayers,\n mapStyleLayerIDs,\n type ToBeAddedLayerSpecWithoutSource,\n type UserEventsWithStates,\n} from '../shared';\nimport { mapAccentColor, onAccentColorChange } from '../shared/accentColor';\nimport { partBeforeLayerIDOf } from '../shared/layers/beforeLayer';\nimport { DEFAULT_PLACE_ICON_ID } from '../shared/layers/symbolLayers';\nimport { suffixNumber } from '../shared/layers/utils';\nimport {\n type AddedImage,\n addLayers,\n applyLayersVisibility,\n syncImages,\n updateLayersAndSource,\n waitUntilMapIsReady,\n} from '../shared/mapUtils';\nimport { customImagesByID, type PaintedPin, paintPin } from '../shared/pinImages';\nimport { onStyleKnobsApplied } from '../shared/styleKnobsApplied';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n CLUSTER_MAX_ZOOM,\n clusteredPlacesAggregateProperties,\n DEFAULT_CLUSTER_RADIUS_PX,\n} from './layers/clusterLayers';\nimport { buildConnectionLayerSpecs } from './layers/connectionsLayers';\nimport { buildEntryPointLayerSpecs } from './layers/entryPointsLayers';\nimport { buildPlacesLayerSpecs, poiLookMarkerTypes } from './layers/placesLayers';\nimport { defaultPin } from './resources';\nimport type { PlaceEntryPointFeature } from './types/entryPointDisplay';\nimport type { DisplayPlaceProps } from './types/placeDisplayProps';\nimport type { PlaceConnectionDisplay, PlaceLayerName, PlacesModuleConfig } from './types/placesModuleConfig';\nimport { poiLookKnobIds } from './utils/layerSpecBuilders';\nimport type { ConnectionFeatureCollection } from './utils/prepareConnectionsForDisplay';\nimport { prepareConnectionsForDisplay } from './utils/prepareConnectionsForDisplay';\nimport type { EntryPointFeatureCollection } from './utils/prepareEntryPointsForDisplay';\nimport { prepareEntryPointsForDisplay, toEntryPointEventFeature } from './utils/prepareEntryPointsForDisplay';\nimport { preparePlacesForInstance, widenConfig } 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 marker types; 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 * Entry points source id with corresponding layers ids (line + point).\n */\n entryPoints: GeoJSONSourceWithLayers<EntryPointFeatureCollection>;\n};\n\n// `placesUnclustered` only exists under the clustered marker type, and is the same data seen another\n// way, so it goes with `places` wherever the places do: their events scope and their event states.\nconst placeSources: (keyof PlacesSourcesAndLayers)[] = ['places', 'placesUnclustered'];\n\ntype ConnectionLayerSpecMap = {\n line: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>;\n label: ToBeAddedLayerSpecWithoutSource<SymbolLayerSpecification>;\n};\n\ntype EntryPointLayerSpecMap = {\n line: ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>;\n point: ToBeAddedLayerSpecWithoutSource<CircleLayerSpecification>;\n};\n\n/**\n * What {@link PlacesModule.show} accepts, and what `shown-features` handlers receive back.\n *\n * @remarks\n * Search, geocoding, reverse geocoding and place details return places, which `show()` takes as\n * they are; {@link preparePlacesForDisplay} is what it does to them. Three places services return\n * something else, and no converter turns it into places:\n *\n * | Result | Not showable because | Show instead |\n * | --- | --- | --- |\n * | {@link getSearchSuggestions} | Suggestions to choose from, without geometry; the autocomplete arm inside returns `{ context, results }` segments | A place suggestion's `resolve()`, or a brand or category's `refine()` — the follow-up search |\n * | {@link geometryData} | Polygons | With {@link GeometriesModule} |\n * | {@link evChargingStationsAvailability} | Availability alone | {@link getPlacesWithEVAvailability} |\n *\n * @group Places\n */\nexport type ShownPlaces = Place | Place[] | Places;\n\n/**\n * What `places.events.on('shown-features', ...)` receives: whichever of places or connections was\n * just rendered.\n *\n * @group Places\n */\nexport type PlacesShownFeatures = { places: ShownPlaces } | { connections: PlaceConnectionDisplay[] };\n\n/**\n * Event surface of {@link PlacesModule}: the module's own events, plus one named scope per\n * surface it manages.\n *\n * @group Places\n */\nexport type PlacesEvents = CombinedEvents<MapGeoJSONFeature, PlacesModuleConfig, PlacesShownFeatures> & {\n /** The place pins themselves, clustered or not. */\n places: UserEventsWithStates<Place<DisplayPlaceProps>>;\n /** The lines and labels connecting places to their parent, when connections are shown. */\n connections: UserEventsWithStates<MapGeoJSONFeature>;\n /** The entry points of places, when {@link PlacesModuleConfig.entryPoints} draws them. */\n entryPoints: UserEventsWithStates<PlaceEntryPointFeature>;\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 * - Marker types ({@link PlacesMarkerType}): pins, circles, base-map POI look-alikes and clustered pins\n * - One colour for the module's own drawing ({@link PlacesModuleConfig.color})\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 * - Entry points of places, for all of them or only hovered or clicked ones (opt-in)\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.visible` 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.create(map, {\n * icon: {\n * categoryIcons: []\n * },\n * label: {\n * title: (place) => place.properties.poi?.name || 'Unknown'\n * },\n * markerType: '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.create(map, {\n * evAvailability: { visible: true }\n * });\n * const results = await discoverPlaces({ filters: { poiCategories: ['CHARGING_LOCATION'] } });\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.create(map); // EV availability disabled\n * const searched = await PlacesModule.create(map, {\n * evAvailability: { visible: 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 */\nexport class PlacesModule extends AbstractDataOwnedMapModule<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 private entryPointLayerSpecs!: EntryPointLayerSpecMap;\n // Refreshing entry points shows their source, which calls the refresh again.\n private refreshingEntryPoints = false;\n // The cluster options the mounted source was created with, `undefined` when it does not cluster.\n // MapLibre fixes them at source creation, so `updateLayersAndData` rebuilds the source when they change.\n private currentClusterOptions?: GeoJSONSourceClusterOptions;\n private sourceID!: string;\n // Companion source id used by the `pin-clustered` marker type 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 entryPointSourceID!: string;\n private layerIDPrefix!: string;\n private defaultPlaceIconID!: string;\n private paintedPin?: PaintedPin;\n // Assigned in `_initSourcesWithLayers`, for the reason `shownConnections` is.\n private addedCategoryIcons!: Record<string, AddedImage>;\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: PlacesShownFeatures) => void)[] = [];\n\n /**\n * Creates a new module instance on the given map, waiting until the map is ready first.\n *\n * `PlacesModule` owns the sources, layers and images it adds, all suffixed per instance. Every\n * call therefore returns a **new, independent** instance, and stacking several of them on one\n * map is a supported thing to do.\n *\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 create<P extends CommonPlaceProps = CommonPlaceProps>(\n tomtomMap: TomTomMap,\n config?: PlacesModuleConfig<P>,\n ): Promise<PlacesModule> {\n await waitUntilMapIsReady(tomtomMap);\n\n return new PlacesModule(tomtomMap, widenConfig(config));\n }\n\n private constructor(map: TomTomMap, config?: PlacesModuleConfig) {\n super(map, config);\n // A `color` the caller chose covers everything the accent (`TomTomMap.mapColors`) would reach.\n onAccentColorChange(map, () => {\n if (this.config?.color === undefined) this.updateLayersAndData(this.config);\n });\n onStyleKnobsApplied(map, poiLookKnobIds, () => {\n if (this.config?.markerType && poiLookMarkerTypes.has(this.config.markerType)) {\n this.updatePlacesLayers(this.config);\n }\n });\n }\n\n // `undefined` leaves each layer builder its own default colour.\n private colorOf(config: PlacesModuleConfig | undefined): string | undefined {\n return config?.color ?? mapAccentColor(this.tomtomMap);\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.entryPointSourceID = `places-entry-points-${this.instanceIndex}`;\n this.layerIDPrefix = this.sourceID;\n this.defaultPlaceIconID = suffixNumber(DEFAULT_PLACE_ICON_ID, this.instanceIndex);\n this.shownConnections = [];\n this.addedCategoryIcons = {};\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.currentClusterOptions = clusterOptions;\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 marker types 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 // Added first, so that sharing an anchor with the places puts the connections under them.\n this.connectionLayerSpecs = this.buildConnectionLayerSpecs(config);\n const connections = this.addOverlaySource<ConnectionFeatureCollection>(\n this.connectionSourceID,\n this.connectionLayerSpecs,\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 this.entryPointLayerSpecs = this.buildEntryPointLayerSpecs(config);\n const entryPoints = this.addOverlaySource<EntryPointFeatureCollection>(\n this.entryPointSourceID,\n this.entryPointLayerSpecs,\n );\n // Event states land on either source without the module being told, so both refresh — on\n // every hover, hence the skip when there are no entry points to draw or clear.\n const refreshEntryPoints = () => {\n if (this.config?.entryPoints || entryPoints.shownFeatures.features.length) {\n this.updateEntryPointsData(this.config);\n }\n };\n places.onShow(refreshEntryPoints);\n entryPoints.onShow(refreshEntryPoints);\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 marker types that don't use the companion.\n return placesUnclustered\n ? { places, placesUnclustered, connections, entryPoints }\n : { places, connections, entryPoints };\n }\n\n // For the sources drawn next to the pins. One whose layers anchor on the places layers is added\n // after them, so that `addLayers` resolves those `beforeID`s.\n private addOverlaySource<T extends FeatureCollection>(\n sourceID: string,\n layerSpecs: Record<string, ToBeAddedLayerSpecWithoutSource>,\n ): GeoJSONSourceWithLayers<T> {\n const source = new GeoJSONSourceWithLayers<T>(this.mapLibreMap, sourceID, Object.values(layerSpecs), false);\n addLayers(source._layerSpecs, this.mapLibreMap);\n return source;\n }\n\n // Swaps the layers of `source` for `newSpecs`, which the caller keeps as the current ones.\n private replaceLayerSpecs<S extends Record<string, ToBeAddedLayerSpecWithoutSource>>(\n newSpecs: S,\n oldSpecs: S,\n source: GeoJSONSourceWithLayers,\n ): S {\n updateLayersAndSource(Object.values(newSpecs), Object.values(oldSpecs), source, this.mapLibreMap);\n return newSpecs;\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 // marker type 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.currentClusterOptions) {\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 this.colorOf(config),\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 // marker type, or `undefined` to leave clustering off entirely. Only the\n // `pin-clustered` marker type opts in.\n private buildClusterOptions(config?: PlacesModuleConfig): GeoJSONSourceClusterOptions | undefined {\n if (config?.markerType !== 'pin-clustered') return undefined;\n\n const source = config.cluster?.source;\n return {\n clusterRadius: DEFAULT_CLUSTER_RADIUS_PX,\n clusterMaxZoom: CLUSTER_MAX_ZOOM,\n ...source,\n cluster: true,\n // The built-in aggregator is spread last so a user-supplied key of the\n // same name can't displace it — it feeds the single-vs-mixed cluster\n // filters and the cluster pin's `match`.\n clusterProperties: { ...source?.clusterProperties, ...clusteredPlacesAggregateProperties },\n };\n }\n\n private buildConnectionLayerSpecs(config?: PlacesModuleConfig): ConnectionLayerSpecMap {\n const specs = buildConnectionLayerSpecs(\n config?.connections,\n this.colorOf(config),\n this.tomtomMap.styleLightDarkTheme,\n );\n const lineID = `${this.layerIDPrefix}-connection-line`;\n const labelID = `${this.layerIDPrefix}-connection-label`;\n // Stack connections under the base-map label layer (`lowestLabel`) by default 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 = partBeforeLayerIDOf(config?.beforeLayerConfig, 'connections', mapStyleLayerIDs.lowestLabel);\n return {\n line: { ...specs.line, id: lineID, beforeID: anchor },\n label: { ...specs.label, id: labelID, beforeID: anchor },\n };\n }\n\n private buildEntryPointLayerSpecs(config?: PlacesModuleConfig): EntryPointLayerSpecMap {\n // A clustered place has no pin of its own, so its entry points wait for the clusters to go.\n const clusterOptions = this.buildClusterOptions(config);\n const lowestUnclusteredZoom = clusterOptions ? (clusterOptions.clusterMaxZoom ?? CLUSTER_MAX_ZOOM) + 1 : 0;\n const specs = buildEntryPointLayerSpecs(\n config?.entryPoints,\n this.tomtomMap.styleLightDarkTheme,\n lowestUnclusteredZoom,\n this.colorOf(config),\n );\n return {\n // Under the pins, so a line leaves from the pin's tip.\n line: {\n ...specs.line,\n id: `${this.layerIDPrefix}-entry-point-line`,\n beforeID: this.layerSpecs.main.id,\n },\n // Over the pins, including a selected one, whose larger icon would hide a nearby entrance;\n // under the cluster layers.\n point: {\n ...specs.point,\n id: `${this.layerIDPrefix}-entry-point`,\n beforeID: this.layerSpecs.cluster.id,\n },\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 discardShownData() {\n this.shownConnections = [];\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 * Applies a configuration to this module, replacing the current one.\n *\n * @typeParam P - The place shape the `label.title`, `icon.mapping` and `extraFeatureProps`\n * callbacks expect, as with {@link PlacesModule.create}.\n *\n * @example\n * ```typescript\n * places.applyConfig<MyPlaceProps>({ label: { title: (place) => place.properties.storeName } });\n * ```\n */\n applyConfig<P extends CommonPlaceProps = CommonPlaceProps>(config: PlacesModuleConfig<P> | undefined): void {\n super.applyConfig(widenConfig(config));\n }\n\n /**\n * Changes part of the configuration and keeps the rest, redrawing the places already shown.\n *\n * @typeParam P - The place shape the `label.title`, `icon.mapping` and `extraFeatureProps`\n * callbacks expect, as with {@link PlacesModule.create}.\n *\n * @example\n * ```typescript\n * places.updateConfig({ markerType: 'base-map' });\n * places.updateConfig({ icon: { categoryIcons: [{ id: 'RESTAURANT', image: '/icons/food.png' }] } });\n * places.updateConfig<MyPlaceProps>({ extraFeatureProps: { brand: (place) => place.properties.brand } });\n * ```\n */\n updateConfig<P extends CommonPlaceProps = CommonPlaceProps>(partial: PlacesModuleConfig<P>): void {\n super.updateConfig(widenConfig(partial));\n }\n\n /** @ignore */\n protected applyVisibility(config: PlacesModuleConfig | undefined): void {\n this.applyPlacesVisibility(config);\n this.applyVisibilityTo(\n Object.values(this.connectionLayerSpecs),\n !!this.sourcesWithLayers.connections.shownFeatures.features.length,\n config,\n );\n this.applyVisibilityTo(\n Object.values(this.entryPointLayerSpecs),\n !!this.sourcesWithLayers.entryPoints.shownFeatures.features.length,\n config,\n );\n }\n\n private updateLayersAndData(config: PlacesModuleConfig | undefined): void {\n this.setupImages(config);\n\n // Toggling clustering, or changing its options, requires recreating the GeoJSON source —\n // MapLibre fixes them 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 if (!isEqual(this.buildClusterOptions(config), this.currentClusterOptions)) {\n this.rebuildSourcesAndLayers(config);\n return;\n }\n\n this.updatePlacesLayers(config);\n this.applyPlacesVisibility(config);\n this.updateData(config);\n this.connectionLayerSpecs = this.replaceLayerSpecs(\n this.buildConnectionLayerSpecs(config),\n this.connectionLayerSpecs,\n this.sourcesWithLayers.connections,\n );\n this.updateConnectionsData(config);\n this.entryPointLayerSpecs = this.replaceLayerSpecs(\n this.buildEntryPointLayerSpecs(config),\n this.entryPointLayerSpecs,\n this.sourcesWithLayers.entryPoints,\n );\n this.updateEntryPointsData(config);\n if (!this._initializing && !isEqual(this.config?.beforeLayerConfig, config?.beforeLayerConfig)) {\n this.restackLayers();\n }\n }\n\n // The layer chains cross sources (the entry points pin to the markers, the `micro` dots of\n // `pin-clustered` too), so moving a part restacks them all, in the order they were added.\n private restackLayers(): void {\n const { connections, places, placesUnclustered, entryPoints } = this.sourcesWithLayers;\n addLayers(\n [connections, places, placesUnclustered, entryPoints].flatMap((source) => source?._layerSpecs ?? []),\n this.mapLibreMap,\n true,\n );\n }\n\n // Rewrites the places layers for `config` in place, within the cluster mode already on the map.\n private updatePlacesLayers(config: PlacesModuleConfig | undefined): void {\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.currentClusterOptions\n ? this.partitionSpecsForSources(newLayerSpecs)\n : { clusteredSpecs: Object.values(newLayerSpecs), unclusteredSpecs: [] };\n const oldSplit = this.currentClusterOptions\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 }\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 const map = this.mapLibreMap;\n for (const spec of [\n ...Object.values(this.layerSpecs),\n ...Object.values(this.connectionLayerSpecs),\n ...Object.values(this.entryPointLayerSpecs),\n ]) {\n if (map.getLayer(spec.id)) map.removeLayer(spec.id);\n }\n // The unclustered companion only exists under `pin-clustered`, hence the check; trying it\n // every time keeps leaving and re-entering the marker type from leaving a stale source behind.\n for (const sourceID of [\n this.sourceID,\n this.unclusteredSourceID,\n this.connectionSourceID,\n this.entryPointSourceID,\n ]) {\n if (map.getSource(sourceID)) map.removeSource(sourceID);\n }\n\n this.initSourcesWithLayers(config, true);\n this.setupImages(config);\n const prepared = preparePlacesForInstance(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 hidden cluster slots\n // (badge / count / mixed badge / mixed count) from leaking onto\n // marker types 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(config);\n if (previousConnections.length) {\n this.shownConnections = [...previousConnections];\n this.updateConnectionsData(config);\n }\n this.updateEntryPointsData(config);\n }\n\n private updateConnectionsData(config: PlacesModuleConfig | undefined): void {\n const shownPlaces = this.sourcesWithLayers.places.shownFeatures;\n const prepared = prepareConnectionsForDisplay(this.shownConnections, shownPlaces, config);\n this.sourcesWithLayers.connections.show(prepared, { automaticVisibility: false });\n this.applyVisibilityTo(Object.values(this.connectionLayerSpecs), !!prepared.features.length, config);\n }\n\n private updateEntryPointsData(config: PlacesModuleConfig | undefined): void {\n if (this.refreshingEntryPoints) return;\n\n this.refreshingEntryPoints = true;\n try {\n const entryPoints = this.sourcesWithLayers.entryPoints;\n const prepared = prepareEntryPointsForDisplay(\n this.sourcesWithLayers.places.shownFeatures,\n entryPoints.shownFeatures,\n config?.entryPoints,\n );\n entryPoints.show(prepared, { automaticVisibility: false });\n this.applyVisibilityTo(Object.values(this.entryPointLayerSpecs), !!prepared.features.length, config);\n } finally {\n this.refreshingEntryPoints = false;\n }\n }\n\n // A layer draws while it has features and the module is not hidden. `config` is passed rather\n // than read, because `_applyConfig` runs before `this.config` holds the new configuration.\n private applyVisibilityTo(\n layerSpecs: ToBeAddedLayerSpecWithoutSource[],\n hasData: boolean,\n config: PlacesModuleConfig | undefined,\n ): void {\n applyLayersVisibility(layerSpecs, hasData && config?.visible !== false, this.mapLibreMap);\n }\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(config: PlacesModuleConfig | undefined): void {\n this.applyVisibilityTo(\n Object.values(this.layerSpecs),\n !!this.sourcesWithLayers.places.shownFeatures.features.length,\n config,\n );\n }\n\n // The default pin takes the module's colour under the caller's `style`, and is repainted when\n // either changes, so a new accent — or its reset — reaches it.\n private setupImages(config: PlacesModuleConfig | undefined): void {\n this.addedCategoryIcons = syncImages(\n this.mapLibreMap,\n this.addedCategoryIcons,\n customImagesByID(config?.icon?.categoryIcons, ({ id, availabilityLevel }) =>\n suffixNumber(availabilityLevel ? `${id}-${availabilityLevel}` : id, this.instanceIndex),\n ),\n );\n this.paintedPin = paintPin(\n this.mapLibreMap,\n this.defaultPlaceIconID,\n config?.icon?.default,\n { fillColor: this.colorOf(config) },\n defaultPin,\n this.paintedPin,\n );\n }\n\n private updateData(config: PlacesModuleConfig | undefined): void {\n const prepared = preparePlacesForInstance(\n this.sourcesWithLayers.places.shownFeatures,\n this.instanceIndex,\n config,\n );\n // Through `show`, so the next event-state change redraws these features and not the ones\n // they replace.\n this.sourcesWithLayers.places.show(prepared, { automaticVisibility: false });\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?.show(prepared, { automaticVisibility: false });\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, and never merges with them\n * - Drops the event states of the places it replaces, even of a place shown again under the same\n * id: an event state lives on the shown feature, so re-select with {@link putEventState} by id.\n * Places that carry `properties.eventState`, such as `getShown().places`, keep theirs\n * - Drops the shown connections (see {@link PlaceConnectionDisplay})\n * - Applies current module styling configuration\n * - Automatically generates labels if label 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 * {@link ShownPlaces} names the places services whose results are not places, and what shows them.\n *\n * @example\n * Display search results:\n * ```typescript\n * import { discoverPlaces } from '@tomtom-org/maps-sdk/services';\n *\n * const results = await discoverPlaces({ 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: ShownPlaces) {\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 // The event states on entry points belong to the places being replaced, even under the same ids.\n this.sourcesWithLayers.entryPoints.clear();\n const prepared = preparePlacesForInstance(places, this.instanceIndex, this.config);\n // Opt out of the blanket visibility flip — `applyPlacesVisibility` honors\n // spec-level `visibility: 'none'` so layers the marker type hides 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(this.config);\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, their event states and the shown connections\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.create(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 for (const handler of this.shownFeaturesHandlers) {\n handler({ connections });\n }\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 * `places` are the places given to `show()` as drawn: each carries the {@link PlaceDisplayProps}\n * and its current `eventState`. `connections` are the ones given to `showConnections()`, including\n * any skipped for an id no shown place has.\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 /** @ignore */\n protected eventStateSources(): (keyof PlacesSourcesAndLayers)[] {\n return placeSources;\n }\n\n get events(): PlacesEvents {\n return this.buildEvents(\n // Module-wide events cover every surface this module draws, so they carry the raw\n // MapLibre feature — a connection line is not a place. Use the `places` scope for the\n // pins alone and their `Place` shape.\n this.moduleEventsWithShown<MapGeoJSONFeature, PlacesShownFeatures>(\n [...placeSources, 'connections', 'entryPoints'],\n this.shownFeaturesHandlers,\n ),\n {\n places: this.userEventsWithStates<Place<DisplayPlaceProps>>(placeSources),\n connections: this.userEventsWithStates<MapGeoJSONFeature>(['connections']),\n entryPoints: this.userEventsWithStates<PlaceEntryPointFeature>(['entryPoints'], {\n mapping: (feature) =>\n toEntryPointEventFeature(feature, this.sourcesWithLayers.places.shownFeatures),\n }),\n },\n );\n }\n}\n","import type { MapStylePOICategory } from '../util/poiCategoryMapping';\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-org/maps-sdk/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.updateConfig({ filters: { categories: { show: 'only', values: ['FOOD_DRINKS_GROUP', 'ENTERTAINMENT_GROUP'] } } });\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 = {\n FOOD_DRINKS_GROUP: ['RESTAURANT', 'FAST_FOOD', 'CAFE_PUB', 'PUB', 'WINERY', 'SOUL_FOOD', 'DELICATESSEN'],\n SHOPPING_GROUP: [\n 'SHOP_OR_STORE',\n 'MALL',\n 'CLOTHING_SHOP',\n 'MARKETPLACE',\n 'FOOD_MARKET',\n 'SUPERMARKET',\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 'HELIPORT_OR_HELIPAD',\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 'HEALTHCARE_SERVICE',\n 'HOSPITAL',\n 'PHARMACY',\n 'DENTIST',\n 'WELFARE_ORGANIZATION',\n ],\n PARKING_GROUP: ['PARKING_GARAGE', 'OPEN_CAR_PARKING_AREA'],\n HOLIDAY_TOURISM_GROUP: [\n 'AMUSEMENT_PARK',\n 'BEACH',\n 'HOLIDAY_RENTAL',\n 'NATURAL_FEATURE',\n 'TOURIST_ATTRACTION',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n 'MUSEUM',\n 'VIEWPOINT',\n 'TOURIST_INFORMATION',\n ],\n EV_CHARGING_STATIONS_GROUP: ['CHARGING_LOCATION'],\n GAS_STATIONS_GROUP: ['FUEL_STATION'],\n ACCOMMODATION_GROUP: ['CAMPING_GROUND', 'HOTEL_OR_MOTEL', 'HOLIDAY_RENTAL'],\n ENTERTAINMENT_GROUP: [\n 'CINEMA',\n 'THEATER',\n 'MOVIE_THEATER',\n 'NIGHTLIFE',\n 'CONCERT_HALL',\n 'ENTERTAINMENT',\n 'CLUB_AND_ASSOCIATION',\n 'CASINO_AND_GAMBLING',\n ],\n SPORTS_LEISURE_GROUP: [\n 'SPORTS_CENTER',\n 'WATER_SPORT',\n 'SWIMMING_POOL',\n 'GOLF_COURSE',\n 'STADIUM',\n 'BEACH',\n 'ICE_RINK',\n 'LEISURE_CENTER',\n 'MOUNTAIN_PASS',\n 'MOUNTAIN_PEAK',\n ],\n EDUCATION_GROUP: ['SCHOOL', 'COLLEGE_OR_UNIVERSITY', 'PUBLIC_LIBRARY', 'CULTURAL_CENTER'],\n GOVERNMENT_GROUP: ['GOVERNMENT_OFFICE', 'COURTHOUSE', 'EMBASSY', 'FIRE_STATION', 'POLICE_STATION'],\n} satisfies Record<string, MapStylePOICategory[]>;\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 { poiCategories } from '@tomtom-org/maps-sdk/core';\nimport { FACTOR_RANGE, type KnobDefinition, valuesFilterKnobs, withIds } from '../shared/knobUtils';\nimport { type POICategoryGroup, poiCategoryGroups } from './types/poiCategoryGroups';\nimport type { POIsKnob, POIsKnobId, POIsKnobValueOf } from './types/poisKnobs';\nimport type { FilterablePOICategory } from './types/poisModuleConfig';\n\n// A pure IIFE, so a bundle that never reads the catalogue drops the category lists with it.\nconst poisKnobDefinitions = /* @__PURE__ */ (() => {\n const categoriesFilter = valuesFilterKnobs(\n 'filters.categories',\n 'categories and category groups',\n [...(Object.keys(poiCategoryGroups) as POICategoryGroup[]), ...poiCategories] satisfies FilterablePOICategory[],\n ' A group stands for each of its categories; groups come first in the options.',\n );\n return {\n visible: { kind: 'toggle', description: 'The POIs the map style draws.', default: true },\n 'filters.categories.show': categoriesFilter.show,\n 'filters.categories.values': categoriesFilter.values,\n sizeFactor: {\n kind: 'factor',\n description: 'Size of the POI icons and their labels.',\n range: FACTOR_RANGE,\n default: 1,\n },\n minZoom: {\n kind: 'number',\n description: 'Zoom level from which POIs appear (the style default is 6).',\n range: { min: 3, max: 18, step: 1, bounds: 'hard' },\n },\n zoomShift: {\n kind: 'number',\n description:\n 'Shifts the zoom at which each POI appears: positive = later and sparser, negative = earlier and denser.',\n range: { min: -3, max: 3, step: 1, bounds: 'hard' },\n default: 0,\n },\n 'label.color': {\n kind: 'color',\n description: 'Text colour of the POI labels (replaces the per-category colours).',\n },\n 'label.haloColor': { kind: 'color', description: 'Halo colour of the POI labels.' },\n 'microMarkers.visible': {\n kind: 'toggle',\n description:\n 'The small dot markers for low-priority POIs at high zoom; never shown while the POIs are hidden.',\n },\n } as const satisfies { [ID in POIsKnobId]: KnobDefinition<POIsKnobValueOf<ID>> };\n})();\n\n/**\n * Every POIs knob with its kind, description, options and, where one value holds everywhere, its\n * default. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link POIsModuleConfig}, at the path its id names: whether\n * POIs show, which categories, and how they look — size, zoom, label colours, micro markers. Those\n * restyle the style's own POI layers, and read its own value while unset. Not knobs:\n * `icon.categoryIcons`, which takes images, and `events`.\n *\n * @group POIs\n */\nexport const poisKnobCatalogue: readonly POIsKnob[] = /* @__PURE__ */ withIds(poisKnobDefinitions);\n\n/**\n * Every POIs knob id, in catalogue order.\n *\n * @group POIs\n */\nexport const poisKnobIds: readonly POIsKnobId[] = /* @__PURE__ */ poisKnobCatalogue.map(({ id }) => id);\n","import { get } from 'lodash-es';\nimport type { LayerSpecification, SourceSpecification, StyleSpecification } from 'maplibre-gl';\nimport { STYLE_VERSION } from '../init/styleInputBuilder';\nimport { scaleNumericValue, shiftZoomOffset } from '../shared/layers/expressionTransforms';\nimport { LayerFilterComposer } from '../shared/layers/layerFilterComposer';\nimport { matchesAnyLayerSelector } from '../shared/layers/layerSelector';\nimport { ofMap } from '../shared/ofMap';\nimport type { TomTomMap } from '../TomTomMap';\nimport { hslShiftBetween, isColorLiteral, shiftHsl } from '../utils/colorUtils';\nimport type { RuntimeLayerProperties } from './layerQuery';\nimport type { PaintColorProperty } from './mapColorCatalogue';\nimport type {\n ColorProperty,\n LayerStyleKnobMechanism,\n LiteralProperty,\n MapLookMechanism,\n ScalableProperty,\n StyleColorKnobId,\n StyleKnobDefinition,\n StyleKnobId,\n StyleKnobMechanism,\n StyleKnobValueOf,\n} from './styleKnobDefinitions';\n\n/**\n * The style knobs in force on a map, by map-wide id.\n * @ignore\n */\nexport type StyleKnobSettings = { [ID in StyleKnobId]?: StyleKnobValueOf<ID> };\n\n/** @ignore */\nexport type StyleKnobValue = StyleKnobValueOf<StyleKnobId>;\n\n// A layer's zoom range is always a number in the style spec, while every other property can hold an\n// expression the knobs pass through untouched — so those stay `unknown` and only the zoom keys carry\n// a value type worth promising.\ntype ZoomProperty = 'minzoom' | 'maxzoom';\n\n/**\n * Layer properties the knobs read at style load and rewrite afterwards. A layer's filter is not\n * among them: the knob that rewrites it goes through the shared `LayerFilterComposer`, which holds\n * the style's own filter for every contributor that narrows the same layer.\n * @ignore\n */\nexport type LayerProperty = ScalableProperty | PaintColorProperty | LiteralProperty | ZoomProperty;\ntype LayerPropertyValue<PROPERTY extends LayerProperty> = PROPERTY extends ZoomProperty ? number : unknown;\n\n// The end of a layer's zoom range each zoom mechanism sets.\nconst ZOOM_BOUNDS = { minZoom: 'minzoom', maxZoom: 'maxzoom' } as const satisfies Record<\n Extract<StyleKnobMechanism['type'], 'minZoom' | 'maxZoom'>,\n ZoomProperty\n>;\n\n// The key with the filter composer — one per mechanism that writes a filter.\nconst ZOOM_SHIFT_CONTRIBUTOR = 'styleKnobs.zoomShift';\n\n// The properties MapLibre sets as layout; every other one a knob writes is paint. The zoom range\n// goes through its own setter.\nconst LAYOUT_PROPERTIES: ReadonlySet<LayerProperty> = new Set(['text-size', 'icon-size']);\n\n// The style spec's own defaults, used when a layer relies on them instead of spelling the value out.\n// `satisfies` rather than an annotation, so the zoom defaults read back as numbers and the callers\n// that fall back to them need no cast.\nconst SPEC_DEFAULTS = {\n 'text-size': 16,\n 'icon-size': 1,\n 'line-width': 1,\n minzoom: 0,\n maxzoom: 24,\n 'hillshade-method': 'standard',\n 'hillshade-illumination-direction': 335,\n 'hillshade-exaggeration': 0.5,\n 'hillshade-shadow-color': '#000000',\n 'hillshade-highlight-color': '#FFFFFF',\n 'hillshade-accent-color': '#000000',\n} satisfies { [PROPERTY in LayerProperty]?: LayerPropertyValue<PROPERTY> };\n\nconst hasSpecDefault = (property: LayerProperty): property is keyof typeof SPEC_DEFAULTS => property in SPEC_DEFAULTS;\n\nconst isLayerMechanism = (mechanism: StyleKnobMechanism): mechanism is LayerStyleKnobMechanism => 'layers' in mechanism;\n\n/**\n * The part of the engine that reaches the map beyond its layers one by one — the map colours, the\n * projection, the sky — added by the module whose knobs need it, so a bundle without that module\n * carries none of it.\n * @ignore\n */\nexport type StyleKnobExtension = {\n /** Re-reads what it needs off a freshly indexed style, before any of its knobs is resolved. */\n index(): void;\n /** The layers one of its mechanisms reaches in the loaded style, their original values captured. */\n resolve(id: StyleKnobId, mechanism: MapLookMechanism): string[];\n /** Brings what one of its mechanisms reaches in line with the values set. */\n refresh(id: StyleKnobId, mechanism: MapLookMechanism): void;\n /** What the loaded style ships for one of its knobs. */\n defaultOf(id: StyleKnobId, mechanism: MapLookMechanism): StyleKnobValue | undefined;\n /** A value the loaded style derives for one of its knobs from the others set, where it derives one. */\n derivedValueOf(id: StyleKnobId, mechanism: MapLookMechanism): StyleKnobValue | undefined;\n};\n\n// Which layers a mechanism reaches in the loaded style.\ntype ResolvedMechanism = { mechanism: StyleKnobMechanism; layerIds: string[]; shadeLayerIds: string[] };\ntype ResolvedKnob = { definition: StyleKnobDefinition; mechanisms: ResolvedMechanism[]; targets: number };\ntype RegisteredKnob = { owner: string; definition: StyleKnobDefinition };\n\n/** The loaded style as the engine indexed it, before any knob touched it. @ignore */\nexport type IndexedStyle = Pick<StyleSpecification, 'projection' | 'sky'> & {\n layers: LayerSpecification[];\n sources: Record<string, SourceSpecification>;\n};\n\n/**\n * The key of one (layer, property) a knob reaches.\n * @ignore\n */\nexport const targetKey = (layerId: string, property: LayerProperty) => `${layerId}|${property}`;\n\n// The style transition held until a freshly loaded style's first frame. Compared by identity, so a\n// hold already in place is never mistaken for the transition the style shipped.\nconst NO_TRANSITION: NonNullable<StyleSpecification['transition']> = { duration: 0, delay: 0 };\n\nconst enginesByMap = new WeakMap<TomTomMap, StyleKnobs>();\n\n/**\n * The single owner of the style properties the style knobs rewrite, on one map. The modules whose\n * knobs restyle the loaded style — styling, POIs, traffic, terrain — each register their knobs here\n * and put their values, so the knobs that reach one property compose: the label, icon and POI size\n * factors multiply on the POI labels, whichever module set them.\n *\n * It indexes the loaded style, captures the original value of every property a registered knob may\n * touch, applies and resets, and re-applies every value after a style change that keeps state —\n * after the modules have restored themselves, so the knobs land on top.\n *\n * @ignore\n */\nexport class StyleKnobs {\n readonly tomtomMap: TomTomMap;\n private readonly filterComposer: LayerFilterComposer;\n private settings: StyleKnobSettings = {};\n // Set between a style starting to change and this engine re-indexing the new one: values put\n // meanwhile are kept, and land once the new style is indexed.\n private stale = false;\n private registered = new Map<StyleKnobId, RegisteredKnob>();\n private extension?: StyleKnobExtension;\n private style: IndexedStyle = { layers: [], sources: {} };\n // The style's own values for every (layer, property) a knob may touch, captured at style load.\n private originalValues = new Map<string, unknown>();\n private resolved = new Map<StyleKnobId, ResolvedKnob>();\n // Scale knobs compose: a (layer, property) reached by several of them gets their product.\n private scaleKnobsByTarget = new Map<string, StyleKnobId[]>();\n private warned = new Set<string>();\n\n private constructor(tomtomMap: TomTomMap) {\n this.tomtomMap = tomtomMap;\n this.filterComposer = LayerFilterComposer.for(tomtomMap);\n this.indexStyle();\n tomtomMap.addStyleChangeHandler({\n onStyleAboutToChange: () => {\n this.stale = true;\n },\n onStyleChanged: ({ resetState }) => this.restore(resetState),\n // After the data and visibility modules have restored themselves, so knobs land on top;\n // before the styling foundations module, which lays its raw layer edits over the knobs.\n priority: 90,\n });\n }\n\n /** The engine of this map, created on first use. */\n static for(tomtomMap: TomTomMap): StyleKnobs {\n return ofMap(enginesByMap, tomtomMap, () => new StyleKnobs(tomtomMap));\n }\n\n /**\n * Makes one module's knobs known, along with the extension its mechanisms need; a knob already\n * known stays as it was. Resolved on the loaded style right away.\n */\n register(\n owner: string,\n definitions: Readonly<Partial<Record<StyleKnobId, StyleKnobDefinition>>>,\n extension?: StyleKnobExtension,\n ): void {\n if (extension && !this.extension) {\n this.extension = extension;\n if (!this.stale) extension.index();\n }\n for (const [id, definition] of Object.entries(definitions) as [StyleKnobId, StyleKnobDefinition][]) {\n if (this.registered.has(id)) continue;\n\n this.registered.set(id, { owner, definition });\n if (!this.stale) this.resolveKnob(id);\n }\n }\n\n /** The values set, by map-wide id. */\n get values(): Readonly<StyleKnobSettings> {\n return this.settings;\n }\n\n /** The loaded style's own layers, sources, projection and sky, as indexed. */\n get indexedStyle(): Readonly<IndexedStyle> {\n return this.style;\n }\n\n /**\n * Sets or unsets the given knobs and brings the style in line with them. `undefined` unsets one,\n * which puts the style's own value back. Only the knobs whose value moved are rewritten, or\n * dragging one colour picker with a palette set would re-derive all ten per frame.\n */\n update(values: StyleKnobSettings): void {\n const next: StyleKnobSettings = { ...this.settings };\n const touched: StyleKnobId[] = [];\n for (const id of Object.keys(values) as StyleKnobId[]) {\n const value = values[id];\n if (next[id] === value) continue;\n\n if (value === undefined) delete next[id];\n else (next as Record<StyleKnobId, StyleKnobValue>)[id] = value;\n touched.push(id);\n }\n this.settings = next;\n if (this.stale || !touched.length) return;\n\n for (const id of touched) this.refreshKnob(id);\n this.publish(touched);\n }\n\n /** The value a knob is at: the one set, one the loaded style derives from the others set, or the style's own. */\n valueOf(id: StyleKnobId): StyleKnobValue | undefined {\n return this.settings[id] ?? this.derivedValueOf(id) ?? this.defaultOf(id);\n }\n\n /**\n * Records the style's own value of a layer property; false when the layer has no such property\n * (a symbol layer without text has no text size to scale).\n */\n capture(layer: LayerSpecification, property: LayerProperty): boolean {\n const value = styleValueOf(layer, property);\n if (value === undefined) return false;\n\n this.originalValues.set(targetKey(layer.id, property), value);\n return true;\n }\n\n /**\n * The captured value, in the type its property promises: `capture` only ever stores what\n * `styleValueOf` read off the layer.\n */\n originalValueOf<PROPERTY extends LayerProperty>(\n layerId: string,\n property: PROPERTY,\n ): LayerPropertyValue<PROPERTY> | undefined {\n return this.originalValues.get(targetKey(layerId, property)) as LayerPropertyValue<PROPERTY> | undefined;\n }\n\n /** Writes one layer property, through MapLibre's layout or paint setter as the property takes. */\n setProperty(layerId: string, property: Exclude<LayerProperty, ZoomProperty>, value: unknown): void {\n const mapLibreMap = this.tomtomMap.mapLibreMap;\n if (!mapLibreMap.getLayer(layerId)) return;\n\n const writer: RuntimeLayerProperties = mapLibreMap;\n if (LAYOUT_PROPERTIES.has(property)) {\n writer.setLayoutProperty(layerId, property, value, { validate: false });\n } else {\n writer.setPaintProperty(layerId, property, value, { validate: false });\n }\n }\n\n // A clean switch (`resetState`) drops every value with the modules that set them; any other\n // brings every value back on the new style.\n private restore(resetState: boolean): void {\n if (resetState) this.settings = {};\n this.stale = false;\n this.indexStyle();\n const ids = Object.keys(this.settings) as StyleKnobId[];\n for (const id of ids) this.refreshKnob(id);\n this.publish(ids);\n }\n\n // Puts the knobs in force on the map, for the modules that draw their own layers (see\n // `TomTomMap._onStyleKnobsApplied`). A clean style switch re-applies no knob, so one whose value\n // moved since the last publish counts as applied too.\n private publish(applied: Iterable<StyleKnobId>): void {\n const previous = this.tomtomMap._styleKnobSettings;\n const knobIds = new Set(applied);\n for (const id of Object.keys(previous) as StyleKnobId[]) {\n if (previous[id] !== this.settings[id]) knobIds.add(id);\n }\n this.tomtomMap._setStyleKnobSettings(this.settings, knobIds);\n }\n\n // ── Indexing the loaded style ─────────────────────────────────────────────────────────────\n\n // Only the style's own layers: the ones the data modules and the caller add are theirs to style.\n private indexStyle(): void {\n const style = this.tomtomMap.mapLibreMap.getStyle();\n this.style = {\n layers: (style?.layers ?? []).filter((layer) => this.tomtomMap._isStyleLayer(layer.id)),\n sources: style?.sources ?? {},\n projection: style?.projection,\n sky: style?.sky,\n };\n this.originalValues = new Map();\n this.resolved = new Map();\n this.scaleKnobsByTarget = new Map();\n this.warned = new Set();\n this.holdTransitionsUntilFirstFrame();\n this.extension?.index();\n for (const id of this.registered.keys()) this.resolveKnob(id);\n }\n\n // MapLibre fades a rewritten paint property into its new value over the style's `transition`, and\n // a style switch rewrites the paint of every layer it updates in place. So knobs re-applied on top\n // would fade in from the new style's own look: until its first frame, nothing transitions.\n private holdTransitionsUntilFirstFrame(): void {\n const mapLibreMap = this.tomtomMap.mapLibreMap;\n const stylesheet = mapLibreMap.style?.stylesheet;\n if (!stylesheet || stylesheet.transition === NO_TRANSITION) return;\n\n const shipped = stylesheet.transition;\n stylesheet.transition = NO_TRANSITION;\n mapLibreMap.once('render', () => {\n stylesheet.transition = shipped;\n });\n }\n\n private resolveKnob(id: StyleKnobId): void {\n const registered = this.registered.get(id);\n if (!registered) return;\n\n const { definition } = registered;\n const mechanisms = definition.mechanisms.map((mechanism) => this.resolveMechanism(id, mechanism));\n const targets = new Set(mechanisms.flatMap((resolvedMechanism) => resolvedMechanism.layerIds)).size;\n this.resolved.set(id, { definition, mechanisms, targets });\n }\n\n private resolveMechanism(id: StyleKnobId, mechanism: StyleKnobMechanism): ResolvedMechanism {\n if (!isLayerMechanism(mechanism)) {\n return { mechanism, layerIds: this.extension?.resolve(id, mechanism) ?? [], shadeLayerIds: [] };\n }\n\n const { layers, sources } = this.style;\n const matched = layers.filter((layer) => matchesAnyLayerSelector(mechanism.layers, layer, sources));\n let layerIds: string[];\n let shadeLayerIds: string[] = [];\n switch (mechanism.type) {\n case 'scale':\n layerIds = this.captureAll(matched, mechanism.property);\n for (const layerId of layerIds) {\n const key = targetKey(layerId, mechanism.property);\n this.scaleKnobsByTarget.set(key, [...(this.scaleKnobsByTarget.get(key) ?? []), id]);\n }\n break;\n case 'color': {\n layerIds = this.captureAll(matched, mechanism.property);\n const shadeSelectors = mechanism.shades ?? [];\n const shades = layers.filter((layer) => matchesAnyLayerSelector(shadeSelectors, layer, sources));\n shadeLayerIds = this.captureAll(shades, mechanism.property);\n break;\n }\n case 'minZoom':\n case 'maxZoom':\n layerIds = matched.map((layer) => layer.id);\n for (const layer of matched) {\n this.capture(layer, 'minzoom');\n this.capture(layer, 'maxzoom');\n }\n break;\n case 'paintLiteral':\n // A layer without the property still takes it (the style relies on the spec default).\n layerIds = matched.map((layer) => layer.id);\n for (const layer of matched) this.capture(layer, mechanism.property);\n break;\n case 'zoomOffset':\n layerIds = matched\n .filter((layer) => shiftZoomOffset(layerFilterOf(layer), 0).patched)\n .map((layer) => layer.id);\n break;\n }\n return { mechanism, layerIds, shadeLayerIds };\n }\n\n // The ids of the layers that have the property, its value captured on each.\n private captureAll(layers: LayerSpecification[], property: LayerProperty): string[] {\n return layers.filter((layer) => this.capture(layer, property)).map((layer) => layer.id);\n }\n\n // ── Applying knobs ────────────────────────────────────────────────────────────────────────\n\n // Brings every layer the knob reaches in line with the settings: the knob's value when set, the\n // style's own value otherwise.\n private refreshKnob(id: StyleKnobId): void {\n const resolvedKnob = this.resolved.get(id);\n if (!resolvedKnob || !this.tomtomMap.mapReady) return;\n\n if (resolvedKnob.targets === 0 && this.settings[id] !== undefined) {\n this.warnOnce(\n id,\n id,\n `knob '${id}' matches no layer in the loaded style (curated for style version ${STYLE_VERSION}); it will have no effect.`,\n );\n }\n for (const resolvedMechanism of resolvedKnob.mechanisms) {\n this.refreshMechanism(id, resolvedMechanism);\n }\n }\n\n private refreshMechanism(id: StyleKnobId, { mechanism, layerIds, shadeLayerIds }: ResolvedMechanism): void {\n if (!isLayerMechanism(mechanism)) {\n this.extension?.refresh(id, mechanism);\n return;\n }\n if (mechanism.type === 'color') {\n this.refreshColor(mechanism.property, layerIds, shadeLayerIds, this.settings[id] as string | undefined);\n return;\n }\n for (const layerId of layerIds) this.refreshLayer(id, mechanism, layerId);\n }\n\n private refreshLayer(\n id: StyleKnobId,\n mechanism: Exclude<LayerStyleKnobMechanism, { type: 'color' }>,\n layerId: string,\n ): void {\n const value = this.settings[id];\n switch (mechanism.type) {\n case 'scale':\n this.refreshScaledProperty(id, layerId, mechanism.property);\n break;\n case 'minZoom':\n case 'maxZoom':\n this.refreshZoomBound(layerId, ZOOM_BOUNDS[mechanism.type], value as number | undefined);\n break;\n case 'paintLiteral':\n this.setProperty(\n layerId,\n mechanism.property,\n value ?? this.originalValueOf(layerId, mechanism.property),\n );\n break;\n case 'zoomOffset':\n this.refreshZoomOffset(layerId, value as number | undefined);\n break;\n }\n }\n\n // Sets one end of the layer's zoom range, or restores it; the other end stays as the style has it.\n private refreshZoomBound(layerId: string, bound: ZoomProperty, value: number | undefined): void {\n const mapLibreMap = this.tomtomMap.mapLibreMap;\n if (!mapLibreMap.getLayer(layerId)) return;\n\n const range = {\n minzoom: this.styleZoomBound(layerId, 'minzoom'),\n maxzoom: this.styleZoomBound(layerId, 'maxzoom'),\n };\n range[bound] = value ?? range[bound];\n mapLibreMap.setLayerZoomRange(layerId, range.minzoom, range.maxzoom);\n }\n\n private styleZoomBound(layerId: string, bound: ZoomProperty): number {\n return this.originalValueOf(layerId, bound) ?? SPEC_DEFAULTS[bound];\n }\n\n private refreshZoomOffset(layerId: string, shift: number | undefined): void {\n this.filterComposer.setTransform(\n layerId,\n ZOOM_SHIFT_CONTRIBUTOR,\n shift === undefined ? undefined : (filter) => shiftZoomOffset(filter, shift).filter,\n );\n }\n\n private refreshScaledProperty(id: StyleKnobId, layerId: string, property: ScalableProperty): void {\n const original = this.originalValueOf(layerId, property);\n const factor = (this.scaleKnobsByTarget.get(targetKey(layerId, property)) ?? []).reduce(\n (product, knobId) => product * ((this.settings[knobId] as number | undefined) ?? 1),\n 1,\n );\n if (factor === 1) {\n this.setProperty(layerId, property, original);\n return;\n }\n const scaled = scaleNumericValue(original, factor);\n if (scaled === undefined) {\n this.warnOnce(\n id,\n `scale:${layerId}:${property}`,\n `cannot scale '${property}' of layer '${layerId}': the style gives it a value this knob cannot scale (a non-numeric value, or an expression holding a nested zoom term); leaving it as the style defines it.`,\n );\n return;\n }\n this.setProperty(layerId, property, scaled);\n }\n\n private refreshColor(\n property: ColorProperty,\n layerIds: string[],\n shadeLayerIds: string[],\n value: string | undefined,\n ): void {\n for (const layerId of layerIds) {\n this.setProperty(layerId, property, value ?? this.originalValueOf(layerId, property));\n }\n // The style derived the shade layers' colours from the primary one; re-derive them from the\n // new colour with the same HSL offset, so an outline stays the darker rim it was designed as.\n const primaryOriginal = layerIds.map((layerId) => this.originalValueOf(layerId, property)).find(isColorLiteral);\n for (const layerId of shadeLayerIds) {\n const shadeOriginal = this.originalValueOf(layerId, property);\n let shaded: unknown = shadeOriginal;\n if (value !== undefined) {\n const shift =\n isColorLiteral(shadeOriginal) && primaryOriginal && hslShiftBetween(primaryOriginal, shadeOriginal);\n shaded = (shift && shiftHsl(value, shift)) ?? value;\n }\n this.setProperty(layerId, property, shaded);\n }\n }\n\n // ── Defaults ──────────────────────────────────────────────────────────────────────────────\n\n private derivedValueOf(id: StyleKnobId): StyleKnobValue | undefined {\n const [first] = this.resolved.get(id)?.definition.mechanisms ?? [];\n return first && !isLayerMechanism(first) ? this.extension?.derivedValueOf(id, first) : undefined;\n }\n\n // What the loaded style ships for a knob: 1 for factors, the first target's literal for colours\n // and zooms; undefined when no single value stands for a data-driven expression.\n private defaultOf(id: StyleKnobId): StyleKnobValue | undefined {\n const resolvedKnob = this.resolved.get(id);\n if (!resolvedKnob) return undefined;\n\n const { definition, mechanisms } = resolvedKnob;\n if (definition.kind === 'factor') return 1;\n\n const [first] = mechanisms;\n const [layerId] = first?.layerIds ?? [];\n if (layerId === undefined) return undefined;\n\n const { mechanism } = first;\n if (!isLayerMechanism(mechanism)) return this.extension?.defaultOf(id, mechanism);\n\n switch (mechanism.type) {\n case 'color': {\n const original = this.originalValueOf(layerId, mechanism.property);\n return isColorLiteral(original) ? original : undefined;\n }\n case 'minZoom':\n case 'maxZoom':\n return this.styleZoomBound(layerId, ZOOM_BOUNDS[mechanism.type]);\n case 'paintLiteral': {\n const original = this.originalValueOf(layerId, mechanism.property);\n return typeof original === 'string' || typeof original === 'number' ? original : undefined;\n }\n case 'zoomOffset':\n return 0;\n default:\n return undefined;\n }\n }\n\n private warnOnce(id: StyleKnobId, key: string, message: string): void {\n if (this.warned.has(key)) return;\n\n this.warned.add(key);\n console.warn(`[${this.registered.get(id)?.owner ?? StyleKnobs.name}] ${message}`);\n }\n}\n\n// The local id behind a prefix: what the module's catalogue calls a map-wide id.\ntype LocalIdOf<ID extends string, PREFIX extends string> = ID extends `${PREFIX}${infer LOCAL}` ? LOCAL : never;\n\n/**\n * The style knobs one module's configuration holds: each by its id in the module's catalogue, which\n * is its path into the configuration, and known to {@link StyleKnobs} under that id behind the\n * module's prefix. The module's knobs join the engine of a map the first time it reaches it.\n * @ignore\n */\nexport class StyleKnobScope<ID extends StyleKnobId, LOCAL extends string> {\n // From the id in the module's catalogue to the map-wide one. Built on first use: a scope is\n // created at module load, and one that builds nothing then is dropped from a bundle that never uses it.\n private knobIdsByLocalId?: ReadonlyMap<string, ID>;\n\n constructor(\n private readonly owner: string,\n private readonly prefix: string,\n private readonly definitions: Readonly<Record<ID, StyleKnobDefinition>>,\n private readonly extension?: (tomtomMap: TomTomMap) => StyleKnobExtension,\n ) {}\n\n private get knobIds(): ReadonlyMap<string, ID> {\n this.knobIdsByLocalId ??= new Map(\n (Object.keys(this.definitions) as ID[]).map((id): [string, ID] => [id.slice(this.prefix.length), id]),\n );\n return this.knobIdsByLocalId;\n }\n\n /** Whether a map-wide id is one of these knobs. */\n has(id: string): id is ID {\n return id in this.definitions;\n }\n\n /** The map-wide ids of these knobs that hold a colour. */\n get colorKnobIds(): Extract<ID, StyleColorKnobId>[] {\n return (Object.keys(this.definitions) as ID[]).filter(\n (id): id is Extract<ID, StyleColorKnobId> => this.definitions[id].kind === 'color',\n );\n }\n\n /** The engine of the map, with these knobs registered on it. */\n engineOf(tomtomMap: TomTomMap): StyleKnobs {\n const engine = StyleKnobs.for(tomtomMap);\n engine.register(this.owner, this.definitions, this.extension?.(tomtomMap));\n return engine;\n }\n\n /** Puts every knob of the configuration in force, unsetting the ones it leaves out. */\n apply(tomtomMap: TomTomMap, config: object | undefined): void {\n const values: Record<string, unknown> = {};\n for (const [local, id] of this.knobIds) values[id] = get(config, local);\n this.engineOf(tomtomMap).update(values as StyleKnobSettings);\n }\n\n /** The value one of these knobs is at on the loaded style, by its id in the module's catalogue; `undefined` for any other id. */\n valueOf(tomtomMap: TomTomMap, id: LOCAL | (string & {})): unknown {\n const knobId = this.knobIds.get(id);\n return knobId === undefined ? undefined : this.engineOf(tomtomMap).valueOf(knobId);\n }\n}\n\n/**\n * The style knobs of one module, from its table of map-wide ids behind `prefix`.\n * @ignore\n */\nexport const styleKnobScope = <PREFIX extends string, ID extends StyleKnobId>(\n owner: string,\n prefix: PREFIX,\n definitions: Readonly<Record<ID, StyleKnobDefinition>>,\n extension?: (tomtomMap: TomTomMap) => StyleKnobExtension,\n): StyleKnobScope<ID, LocalIdOf<ID, PREFIX>> => new StyleKnobScope(owner, prefix, definitions, extension);\n\nconst layerFilterOf = (layer: LayerSpecification): unknown => ('filter' in layer ? layer.filter : undefined);\n\n/**\n * The value a layer property has in the loaded style, falling back to the spec default where the\n * layer relies on it; undefined when the layer has no such property at all.\n * @ignore\n */\nexport const styleValueOf = (layer: LayerSpecification, property: LayerProperty): unknown => {\n const layout = ('layout' in layer ? layer.layout : undefined) as Record<string, unknown> | undefined;\n const paint = ('paint' in layer ? layer.paint : undefined) as Record<string, unknown> | undefined;\n switch (property) {\n case 'text-size':\n return layer.type === 'symbol' && layout?.['text-field'] !== undefined\n ? (layout['text-size'] ?? SPEC_DEFAULTS['text-size'])\n : undefined;\n case 'icon-size':\n return layer.type === 'symbol' && layout?.['icon-image'] !== undefined\n ? (layout['icon-size'] ?? SPEC_DEFAULTS['icon-size'])\n : undefined;\n case 'line-width':\n return layer.type === 'line' ? (paint?.['line-width'] ?? SPEC_DEFAULTS['line-width']) : undefined;\n case 'minzoom':\n return layer.minzoom;\n case 'maxzoom':\n return layer.maxzoom;\n default:\n // Hillshade layers lean on the spec defaults for whatever paint they leave unset.\n return (\n paint?.[property] ??\n (layer.type === 'hillshade' && hasSpecDefault(property) ? SPEC_DEFAULTS[property] : undefined)\n );\n }\n};\n","import type { poisStyleKnobs } from '../pois/poisStyleKnobs';\nimport type { KnobKind, KnobValueOf } from '../shared';\nimport type { LayerSelector } from '../shared/layers/layerSelector';\nimport type { hillshadeStyleKnobs } from '../terrain/hillshadeStyleKnobs';\nimport type { trafficFlowStyleKnobs, trafficIncidentsStyleKnobs } from '../traffic/trafficStyleKnobs';\nimport type { BaseMapColorName, PaintColorProperty } from './mapColorCatalogue';\nimport type { stylingFoundationsStyleKnobs } from './stylingFoundationsStyleKnobs';\n\n/** @ignore */\nexport type ScalableProperty = 'text-size' | 'icon-size' | 'line-width';\n/** @ignore */\nexport type ColorProperty = Extract<\n PaintColorProperty,\n | 'line-color'\n | 'text-color'\n | 'text-halo-color'\n | 'hillshade-shadow-color'\n | 'hillshade-highlight-color'\n | 'hillshade-accent-color'\n>;\n/**\n * Paint properties set as plain literals by the `paintLiteral` mechanism. Listed as values for the\n * same reason as `paintColorProperties`.\n * @ignore\n */\nexport const literalPaintProperties = [\n 'hillshade-method',\n 'hillshade-illumination-direction',\n 'hillshade-exaggeration',\n] as const;\n/** @ignore */\nexport type LiteralProperty = (typeof literalPaintProperties)[number];\n\n/**\n * How a style knob rewrites the layers it selects, each on its own.\n * @ignore\n */\nexport type LayerStyleKnobMechanism =\n /** Multiplies the property's own value on every matched layer (works on any style). */\n | { type: 'scale'; property: ScalableProperty; layers: LayerSelector[] }\n /**\n * Sets a colour literal on the matched layers; `shades` are layers whose colour the style\n * derived from it (an outline, a tunnel variant) and that are re-derived by the same HSL offset.\n */\n | { type: 'color'; property: ColorProperty; layers: LayerSelector[]; shades?: LayerSelector[] }\n /** Sets the minimum zoom of the matched layers. */\n | { type: 'minZoom'; layers: LayerSelector[] }\n /** Sets the maximum zoom of the matched layers. */\n | { type: 'maxZoom'; layers: LayerSelector[] }\n /** Sets a paint property to the knob's value as a plain literal on the matched layers. */\n | { type: 'paintLiteral'; property: LiteralProperty; layers: LayerSelector[] }\n /** Shifts the `[\"-\", [\"zoom\"], offset]` density term inside the matched layers' filters. */\n | { type: 'zoomOffset'; layers: LayerSelector[] };\n\n/**\n * How a {@link StylingFoundationsModule} knob reaches the look of the whole map, through the map-look extension\n * of the engine rather than layer by layer.\n * @ignore\n */\nexport type MapLookMechanism =\n /** The projection the whole map is drawn in. */\n | { type: 'projection' }\n /** The sky/atmosphere (its colours come from the sibling colour knobs). */\n | { type: 'sky' }\n /** The colour behind the map canvas, which a globe leaves visible around the planet. */\n | { type: 'space' }\n /**\n * One of the ten base-map colours: re-derives every literal it governs from the new colour,\n * keeping each one's HSL offset from the style's own base (the anchor).\n */\n | { type: 'mapColor'; color: BaseMapColorName }\n /**\n * Published, not painted: the value is put on the map for the modules that draw their own layers\n * to read as their default (see {@link TomTomMap.mapColors}); no style layer is touched.\n */\n | { type: 'published' };\n\n/**\n * How a style knob reaches the style. A knob may use several (e.g. text and icon size together).\n * @ignore\n */\nexport type StyleKnobMechanism = LayerStyleKnobMechanism | MapLookMechanism;\n\n/** @ignore */\nexport type StyleKnobDefinition<KIND extends KnobKind = KnobKind> = { kind: KIND; mechanisms: StyleKnobMechanism[] };\n\n/**\n * The style knobs of one module, by map-wide id: its prefix, then the id in its catalogue — which\n * must be one of `ID`, or the table does not compile.\n * @ignore\n */\nexport type StyleKnobTable<PREFIX extends string, ID extends string> = {\n [KNOB in ID as `${PREFIX}${KNOB}`]?: StyleKnobDefinition;\n};\n\n/** @ignore */\nexport const scaleKnob = (property: ScalableProperty, layers: LayerSelector): StyleKnobDefinition<'factor'> => ({\n kind: 'factor',\n mechanisms: [{ type: 'scale', property, layers: [layers] }],\n});\n\n/** @ignore */\nexport const colorKnob = (\n property: ColorProperty,\n layers: LayerSelector,\n shades?: LayerSelector,\n): StyleKnobDefinition<'color'> => ({\n kind: 'color',\n mechanisms: [{ type: 'color', property, layers: [layers], shades: shades && [shades] }],\n});\n\n// Every module's table at once, for the types that span them. Their keys never overlap: each\n// module's sit behind its own prefix.\ntype StyleKnobDefinitions = typeof stylingFoundationsStyleKnobs &\n typeof poisStyleKnobs &\n typeof trafficFlowStyleKnobs &\n typeof trafficIncidentsStyleKnobs &\n typeof hillshadeStyleKnobs;\n\n/**\n * The map-wide id of a style knob: a knob that restyles the loaded style, named by its module's\n * prefix and its id in that module's catalogue — `pois.label.color` is `label.color` of\n * {@link poisKnobCatalogue}, `traffic.flow.colors.slow` is `colors.slow` of\n * {@link trafficFlowKnobCatalogue}, `terrain.hillshade.method` is `hillshade.method` of\n * {@link terrainKnobCatalogue}. The {@link StylingFoundationsModule} knobs keep their own ids.\n *\n * @group Map Styling\n */\nexport type StyleKnobId = keyof StyleKnobDefinitions;\n\n/**\n * The value type a given style knob takes, by its map-wide id.\n *\n * @group Map Styling\n */\nexport type StyleKnobValueOf<ID extends StyleKnobId> = KnobValueOf<StyleKnobDefinitions[ID]['kind']>;\n\n/**\n * The map-wide id of a style knob that holds a colour: every colour of the loaded style a module's\n * knob sets — the ten map colours and the accent, the sky, the POI labels, the traffic levels and\n * the hillshade tones. {@link getStyleColor} reads the one in force.\n *\n * @group Map Styling\n */\nexport type StyleColorKnobId = {\n [ID in StyleKnobId]: StyleKnobDefinitions[ID]['kind'] extends 'color' ? ID : never;\n}[StyleKnobId];\n","import { baseMapLayerGroupSelector } from '../base/layerGroups';\nimport type { BaseMapLayerGroupName } from '../base/types/baseMapModuleConfig';\nimport type { LayerSelector } from '../shared/layers/layerSelector';\nimport { POI_SOURCE_ID, TRAFFIC_FLOW_SOURCE_ID, TRAFFIC_INCIDENTS_SOURCE_ID } from '../shared/layers/sourcesIDs';\n\n/**\n * The layer groups of the style-owned modules other than the base map, each the style layers one\n * module controls: {@link POIsModule}, {@link TrafficFlowModule}, {@link TrafficIncidentsModule} and\n * the hillshade {@link TerrainModule} shows.\n *\n * @group Map Styling\n */\nexport const styleModuleLayerGroupNames = ['pois', 'trafficFlow', 'trafficIncidents', 'hillshade'] as const;\n\n/**\n * One of {@link styleModuleLayerGroupNames}.\n *\n * @group Map Styling\n */\nexport type StyleModuleLayerGroupName = (typeof styleModuleLayerGroupNames)[number];\n\n/**\n * A named group of style layers: one of the base map's ({@link BaseMapLayerGroupName}) or the\n * layers of another style-owned module ({@link StyleModuleLayerGroupName}).\n *\n * @group Map Styling\n */\nexport type StyleLayerGroupName = BaseMapLayerGroupName | StyleModuleLayerGroupName;\n\nconst styleModuleLayerGroupSelectors: Record<StyleModuleLayerGroupName, LayerSelector> = {\n pois: { sources: [POI_SOURCE_ID], sourceLayers: ['poi'], layerTypes: ['symbol'] },\n trafficFlow: { sources: [TRAFFIC_FLOW_SOURCE_ID] },\n trafficIncidents: { sources: [TRAFFIC_INCIDENTS_SOURCE_ID] },\n // Any hillshade layer, so the group holds on a custom style too.\n hillshade: { layerTypes: ['hillshade'] },\n};\n\nconst isStyleModuleLayerGroup = (group: StyleLayerGroupName): group is StyleModuleLayerGroupName =>\n group in styleModuleLayerGroupSelectors;\n\n/**\n * The selector behind a style layer group, for `styling.layers` and the curated maps (the styling\n * knobs) that build on the same groups instead of restating them.\n * @ignore\n */\nexport const styleLayerGroupSelector = (group: StyleLayerGroupName): LayerSelector =>\n isStyleModuleLayerGroup(group) ? styleModuleLayerGroupSelectors[group] : baseMapLayerGroupSelector(group);\n","import { styleKnobScope } from '../styling/StyleKnobs';\nimport { colorKnob, type StyleKnobTable } from '../styling/styleKnobDefinitions';\nimport { styleLayerGroupSelector } from '../styling/styleLayerGroups';\nimport type { POIsKnobId } from './types/poisKnobs';\n\n/**\n * How the {@link POIsModule} knobs that restyle the POIs reach the loaded style.\n * @ignore\n */\nexport const poisStyleKnobs = /* @__PURE__ */ (() => {\n // The two POI layers the style ships (`POI`, `POI - Micro`).\n const poiLayers = styleLayerGroupSelector('pois');\n return {\n 'pois.sizeFactor': {\n kind: 'factor',\n mechanisms: [\n { type: 'scale', property: 'icon-size', layers: [poiLayers] },\n { type: 'scale', property: 'text-size', layers: [poiLayers] },\n ],\n },\n 'pois.minZoom': {\n kind: 'number',\n mechanisms: [{ type: 'minZoom', layers: [{ ...poiLayers, idExcludes: ['micro'] }] }],\n },\n 'pois.zoomShift': { kind: 'number', mechanisms: [{ type: 'zoomOffset', layers: [poiLayers] }] },\n 'pois.label.color': colorKnob('text-color', poiLayers),\n 'pois.label.haloColor': colorKnob('text-halo-color', poiLayers),\n } as const satisfies StyleKnobTable<'pois.', POIsKnobId>;\n})();\n\n/**\n * How the POIs look, which restyles the style's own POI layers.\n * @ignore\n */\nexport const poisLook = /* @__PURE__ */ styleKnobScope('POIsModule', 'pois.', poisStyleKnobs);\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 { isNil } from 'lodash-es';\nimport type { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { ValuesFilter } from '../shared';\nimport {\n AbstractStyleOwnedMapModule,\n type CombinedEvents,\n mapStyleLayerIDs,\n POI_SOURCE_ID,\n type StyleSourceWithLayers,\n} from '../shared';\nimport { ICON_PIXEL_RATIO } from '../shared/imageUtils';\nimport { buildIconOffsetExpression, type IconOffset } from '../shared/layers/iconOffset';\nimport { LayerPartToggles } from '../shared/layers/layerPartToggles';\nimport { buildMappedValuesFilter, isCompleteValuesFilter } from '../shared/mapLibreFilterUtils';\nimport { type AddedImage, syncImages } from '../shared/mapUtils';\nimport { getStyleOwnedModule, styleSourceWithLayers } from '../shared/styleOwnedModules';\nimport type { TomTomMap } from '../TomTomMap';\nimport { poiLayerIDs } from './layers/poisLayers';\nimport { poisLook } from './poisStyleKnobs';\nimport { type POICategoryGroup, poiCategoryGroups } from './types/poiCategoryGroups';\nimport type { FilterablePOICategory, POIIconConfig, POIsModuleConfig } from './types/poisModuleConfig';\nimport type { POIsModuleFeature } from './types/poisModuleFeature';\nimport { iconsByMapCategory } from './util/categoryIcons';\nimport { type MapStylePOICategory, toBaseMapPOICategory } from './util/poiCategoryMapping';\nimport { poisMapping } from './util/poisMapping';\n\nconst isCategoryGroup = (category: FilterablePOICategory): category is POICategoryGroup =>\n category in poiCategoryGroups;\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 = categories.flatMap((category: FilterablePOICategory) =>\n isCategoryGroup(category)\n ? poiCategoryGroups[category].map(toBaseMapPOICategory)\n : [toBaseMapPOICategory(category)],\n );\n // A category the base-map style shows no icon for maps to nothing, and MapLibre rejects a\n // filter that carries that `undefined` outright.\n return [...new Set(categoryIds.filter((categoryId): categoryId is string => !isNil(categoryId)))];\n};\n\n// This module's key with the shared filter composer — `zoomShift` rewrites the same layer's filter\n// under its own key.\nconst CATEGORIES_CONTRIBUTOR = 'pois.categories';\n// And with the visibility composer, next to the micro markers it toggles on their own.\nconst VISIBILITY_CONTRIBUTOR = 'pois.visible';\n\n// The `POI` layer properties a custom icon rewrites, over the style's own values.\ntype IconLayout = Pick<NonNullable<SymbolLayerSpecification['layout']>, 'icon-image' | 'icon-offset'>;\nconst iconLayoutProperties = ['icon-image', 'icon-offset'] as const satisfies (keyof IconLayout)[];\n\n// A legacy `{token}` string is no expression, so it cannot stand as the last arm of a `match` as it is.\n// Split on its braces, every odd part names a property.\nconst tokenStringToExpression = (iconImage: string): ExpressionSpecification => {\n const parts: (string | ExpressionSpecification)[] = [];\n for (const [index, part] of iconImage.split(/[{}]/).entries()) {\n if (index % 2) parts.push(['get', part]);\n else if (part) parts.push(part);\n }\n return ['concat', ...parts];\n};\n\n// The style's icon, except on the categories `imageByCategory` names.\nconst iconImageExpression = (\n imageByCategory: Map<string, string>,\n styleIconImage: IconLayout['icon-image'],\n): ExpressionSpecification => {\n const styleArm =\n typeof styleIconImage === 'string' && styleIconImage.includes('{')\n ? tokenStringToExpression(styleIconImage)\n : (styleIconImage ?? '');\n const arms: (string | ExpressionSpecification)[] = ['match', ['get', 'category'], ...[...imageByCategory].flat()];\n arms.push(styleArm as string | ExpressionSpecification);\n return arms as ExpressionSpecification;\n};\n\n// The image of the icon of one category: one per map, as the module is.\nconst toIconImageID = (category: MapStylePOICategory): string => `pois-${category}`;\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 pois: 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 * **What this module owns:** whether the style's POIs are shown, which categories, any custom icon\n * a category is drawn with, and how they look: size, label colours and zoom thresholds. The micro\n * markers show or hide on their own (`microMarkers.visible`), never while the POIs are hidden.\n * See [Who owns what](https://docs.tomtom.com/maps-sdk-js/guides/map/modules#who-owns-what).\n * {@link poisKnobCatalogue} lists the settings of its configuration a knob holds.\n *\n * **Features:**\n * - Toggle POI visibility on/off\n * - Filter by POI categories or category groups\n * - Custom icons per POI category ({@link POIIconConfig})\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_OR_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-org/maps-sdk/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.updateConfig({\n * filters: { categories: { show: 'only', values: ['RESTAURANT', 'HOTEL_OR_MOTEL'] } },\n * });\n *\n * // Hide parking garages\n * poisModule.updateConfig({\n * filters: { categories: { show: 'all-except', values: ['PARKING_GARAGE', 'OPEN_CAR_PARKING_AREA'] } },\n * });\n *\n * // Show every POI again\n * poisModule.updateConfig({ filters: undefined });\n * ```\n *\n * @example\n * Filter using category groups:\n * ```typescript\n * // Show only food and shopping POIs\n * poisModule.updateConfig({\n * filters: { categories: { show: 'only', values: ['FOOD_DRINKS_GROUP', 'SHOPPING_GROUP'] } },\n * });\n * ```\n *\n * @example\n * Custom icons per category, shared with the places a {@link PlacesModule} shows:\n * ```typescript\n * const categoryIcons = [\n * { id: 'RESTAURANT', image: restaurantSvg },\n * { id: 'HOTEL_OR_MOTEL', image: '/icons/hotel.png', pixelRatio: 2 },\n * ];\n * poisModule.updateConfig({ icon: { categoryIcons } });\n * searchResults.updateConfig({ icon: { categoryIcons } });\n *\n * // The style's own icons again\n * poisModule.updateConfig({ icon: undefined });\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 AbstractStyleOwnedMapModule<PoIsSourcesAndLayers, POIsModuleConfig> {\n private categoriesFilter?: ValuesFilter<FilterablePOICategory>;\n // The loaded style's own icon properties of the `POI` layer, read before this module writes over them.\n private styleIconLayout!: IconLayout;\n // Assigned in `_initSourcesWithLayers`: the base constructor applies the config before a field\n // initializer would run.\n private addedIcons!: Record<string, AddedImage>;\n // Assigned in `_initSourcesWithLayers` on every style load, for the same reason.\n private microMarkers!: LayerPartToggles<'microMarkers.visible'>;\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 * @remarks\n * **Instances:**\n * `POIsModule` controls the POI sources and layers the map style already provides, under fixed\n * global IDs. Every instance is another handle on that same shared state, so visibility and\n * filters applied through one are visible through all of them.\n */\n static get(map: TomTomMap, config?: POIsModuleConfig): Promise<POIsModule> {\n return getStyleOwnedModule(map, POIsModule, () => new POIsModule(map, config), config);\n }\n\n private constructor(map: TomTomMap, config?: POIsModuleConfig) {\n super(map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const pois = styleSourceWithLayers(this.mapLibreMap, POI_SOURCE_ID, POIsModule.name, (layer) =>\n poiLayerIDs.includes(layer.id),\n );\n this.addedIcons ??= {};\n this.microMarkers = new LayerPartToggles('pois', { 'microMarkers.visible': [{ idIncludes: ['micro'] }] });\n this.microMarkers.index(pois._layerSpecs);\n this.styleIconLayout = this.mapLibreMap.getLayer(mapStyleLayerIDs.poi)\n ? {\n 'icon-image': this.mapLibreMap.getLayoutProperty(mapStyleLayerIDs.poi, 'icon-image'),\n 'icon-offset': this.mapLibreMap.getLayoutProperty(mapStyleLayerIDs.poi, 'icon-offset'),\n }\n : {};\n // TODO: check if the layers are present in the style, and if not, throw exception?\n return { pois };\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 // Unset, the POIs show: a config without `visible` brings hidden ones back.\n this._setVisible(true, false);\n }\n\n this.applyCategoriesFilter(config?.filters?.categories);\n this.applyCategoryIcons(config?.icon?.categoryIcons);\n poisLook.apply(this.tomtomMap, config);\n if (this.tomtomMap.mapReady) this.microMarkers.apply(this.visibilityComposer, config);\n\n return config;\n }\n\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return super._knobValue(id) ?? poisLook.valueOf(this.tomtomMap, id) ?? this.microMarkers.valueOf(id);\n }\n\n /**\n * Whether the POIs are set to be shown.\n *\n * @returns The `visible` setting; while nothing has set it, whether the style draws any POI layer.\n *\n * @example\n * ```typescript\n * if (pois.isVisible()) {\n * console.log('POIs are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.config?.visible ?? this.sourcesWithLayers.pois.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 for (const layerId of this.sourcesWithLayers.pois.sourceAndLayerIDs.layerIDs) {\n this.visibilityComposer.setVisible(layerId, VISIBILITY_CONTRIBUTOR, visible);\n }\n }\n if (updateConfig) {\n this.config = { ...this.config, visible };\n this.emitConfigChange();\n }\n }\n\n private applyCategoriesFilter(categoriesFilter: ValuesFilter<FilterablePOICategory> | undefined): void {\n if (this.tomtomMap.mapReady && (categoriesFilter || this.categoriesFilter)) {\n const poiFilter =\n categoriesFilter && isCompleteValuesFilter(categoriesFilter)\n ? buildMappedValuesFilter(\n 'category',\n categoriesFilter.show,\n getStyleCategories(categoriesFilter.values),\n )\n : undefined;\n // Without one, the layer keeps whatever the style, and any other contributor, filters it by.\n this.filterComposer.setClause(mapStyleLayerIDs.poi, CATEGORIES_CONTRIBUTOR, poiFilter);\n }\n this.categoriesFilter = categoriesFilter;\n }\n\n private applyCategoryIcons(categoryIcons: POIIconConfig['categoryIcons']): void {\n if (!this.tomtomMap.mapReady || !this.mapLibreMap.getLayer(mapStyleLayerIDs.poi)) return;\n\n const wanted: Record<string, AddedImage> = {};\n const imageByCategory = new Map<string, string>();\n const offsetByCategory = new Map<string, IconOffset>();\n for (const [mapCategory, icon] of iconsByMapCategory(categoryIcons)) {\n const imageID = toIconImageID(icon.id);\n wanted[imageID] = { image: icon.image, pixelRatio: icon.pixelRatio ?? ICON_PIXEL_RATIO };\n imageByCategory.set(mapCategory, imageID);\n offsetByCategory.set(mapCategory, { x: icon.offsetX ?? 0, y: icon.offsetY ?? 0 });\n }\n if (!imageByCategory.size && !Object.keys(this.addedIcons).length) return;\n\n const { 'icon-image': styleIconImage, 'icon-offset': styleIconOffset } = this.styleIconLayout;\n const layout: IconLayout = {\n 'icon-image': imageByCategory.size ? iconImageExpression(imageByCategory, styleIconImage) : styleIconImage,\n 'icon-offset': buildIconOffsetExpression(offsetByCategory, 'category', styleIconOffset) ?? styleIconOffset,\n };\n // The layer stops naming an image before it is removed, so no tile asks for a missing one.\n for (const property of iconLayoutProperties) {\n this.mapLibreMap.setLayoutProperty(mapStyleLayerIDs.poi, property, layout[property]);\n }\n this.addedIcons = syncImages(this.mapLibreMap, this.addedIcons, wanted);\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.pois.length}`);\n * shown.pois.forEach(poi => {\n * console.log(poi.properties.name, poi.properties.category);\n * });\n * ```\n */\n getShown(): { pois: POIsModuleFeature[] } {\n return {\n pois: this.sourcesWithLayers.pois.queryRenderedFeatures().map(poisMapping),\n };\n }\n\n /**\n * Gets the events interface for handling user interactions with POIs.\n *\n * @returns The module's events, with `on` / `off` for handlers and `where` for scoping.\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 this.moduleEvents<POIsModuleFeature>(['pois'], { mapping: poisMapping });\n }\n}\n","import type { LightDark } from '../../shared';\nimport { relativeLuminance } from '../../utils/colorUtils';\nimport type { CountryCrossingConfig } from '../types/routeModuleConfig';\n\n/** The SDK's near-black, the plaque on a light map and the label on a light plaque. */\nconst NEAR_BLACK = '#1A2024';\n\n/** The SDK's near-white, the plaque on a dark map and the label on a dark plaque. */\nconst NEAR_WHITE = '#F1F3F5';\n\n// Above this relative luminance (0 = black, 1 = white) a plaque needs the dark label.\nconst LIGHT_PLAQUE_THRESHOLD = 0.4;\n\n/** The two colours a crossing plaque is drawn with. */\nexport type CountryCrossingColors = {\n /** Fill of the plaque image. */\n plaque: string;\n /** Colour of the label written on it. */\n text: string;\n};\n\n/**\n * The colours a crossing draws with, given its config and the map's theme.\n *\n * @remarks\n * The plaque opposes the map so it reads as a label rather than as part of the canvas, and the\n * label opposes the plaque. A caller who sets only `color` still gets a readable label, because the\n * fallback is measured off that colour rather than off the theme.\n * @ignore\n */\nexport const resolveCountryCrossingColors = (\n config: CountryCrossingConfig | undefined,\n lightDark: LightDark,\n): CountryCrossingColors => {\n const plaque = config?.color ?? (lightDark === 'dark' ? NEAR_WHITE : NEAR_BLACK);\n const luminance = relativeLuminance(plaque);\n const readable = luminance !== undefined && luminance > LIGHT_PLAQUE_THRESHOLD ? NEAR_BLACK : NEAR_WHITE;\n\n return { plaque, text: config?.label?.color ?? readable };\n};\n","import type { SectionType } from '@tomtom-org/maps-sdk/core';\n\n// The section types no overlay draws at all:\n// - `country` tiles a route end to end, so a line drawn for it is a line over the whole route\n// that says only \"you are in a country\". What the driver wants from it is the crossing, and the\n// base map already draws that as a border. The sections stay on the route for anyone reading\n// them — the speed limit signs take their face and unit from exactly these.\n// - `lanes` describes a lane-assist HUD, not a geographic overlay.\n// - `leg` is route structure, already on screen as the waypoints between legs.\n// - `roadShields` needs shield images from the atlas the response points at, plus a symbol layer.\n// - `toll` is already on screen: `tollRoad` reports every stretch it does and more, so a second\n// overlay would draw the same geometry twice.\ntype UndrawnSectionType = 'country' | 'lanes' | 'leg' | 'roadShields' | 'toll';\n\n/**\n * A section type the module draws, and therefore one the caller can configure.\n *\n * @remarks\n * Every section type the API can return, less the ones nothing draws. Derived from the core\n * {@link SectionType} vocabulary, and {@link SECTION_REGISTRY} is keyed by it, so a section type\n * added to core has to be registered there or named undrawn above before this package compiles.\n *\n * @group Routing\n */\nexport type DrawnSectionType = Exclude<SectionType, UndrawnSectionType>;\n\n/**\n * The section types whose layers are generated from {@link SECTION_REGISTRY}.\n *\n * @remarks\n * Plain line overlays over the route, one registration each. The remaining drawn types\n * ({@link bespokeSectionTypes}) carry type-specific icons or data-driven colours, so they keep\n * hand-written layers — but they take the same {@link SectionDisplayConfig} knobs.\n *\n * @group Routing\n */\nexport const generatedSectionTypes = [\n 'carpool',\n 'carTrain',\n 'importantRoadStretch',\n 'lowEmissionZone',\n 'motorway',\n 'pedestrian',\n 'speedLimit',\n 'tollVignette',\n 'unpaved',\n 'urban',\n] as const;\n\n/**\n * The drawn section types whose layers are hand-written rather than generated.\n *\n * @group Routing\n */\nexport const bespokeSectionTypes = ['ferry', 'tollRoad', 'traffic', 'tunnel', 'vehicleRestricted'] as const;\n\n/**\n * A section type whose layers are generated from the registry.\n * @group Routing\n */\nexport type GeneratedSectionType = (typeof generatedSectionTypes)[number];\n\n/**\n * A drawn section type with hand-written layers.\n * @group Routing\n */\nexport type BespokeSectionType = (typeof bespokeSectionTypes)[number];\n\n/**\n * Every drawn section type, generated ones first.\n * @group Routing\n */\nexport const drawnSectionTypes = [\n ...generatedSectionTypes,\n ...bespokeSectionTypes,\n] as const satisfies readonly DrawnSectionType[];\n\n/**\n * How a section's line relates to the route line it marks — both where it sits in the layer order\n * and how wide it is drawn, since neither reads as intended without the other.\n *\n * @remarks\n * - `halo`: beneath the route's own lines and wider than its outline, so the section shows as a\n * band hugging the route on both sides. The route keeps its own colour.\n * - `inline`: above the route line at the route's own width, so the route itself appears in the\n * section's colour along that stretch. This is how `tunnel` and `ferry` draw.\n *\n * Every section defaults to whichever of the two it has always been drawn as.\n *\n * @group Routing\n */\nexport type SectionDrawStyle = 'halo' | 'inline';\n\n/**\n * The line pattern of a section.\n * @group Routing\n */\nexport type SectionLinePattern = 'solid' | 'dashed' | 'dotted';\n\n/**\n * One of the uniform knobs {@link SectionDisplayConfig} offers.\n * @group Routing\n */\nexport type SectionKnobName =\n | 'visible'\n | 'color'\n | 'opacity'\n | 'width'\n | 'widthFactor'\n | 'style'\n | 'pattern'\n | 'icon'\n | 'sign';\n\n// The knobs a section that bands the route answers to — every one but `traffic`, which colours and\n// dashes its line from the incident data.\nconst ALL_SECTION_KNOBS = [\n 'visible',\n 'color',\n 'opacity',\n 'width',\n 'widthFactor',\n 'style',\n 'pattern',\n 'icon',\n] as const satisfies readonly SectionKnobName[];\n\n// The knobs a section that posts a sign answers to. None of the band knobs are among them: there\n// is no line to colour, widen, dash or place an icon on.\nconst SIGN_SECTION_KNOBS = ['visible', 'sign'] as const satisfies readonly SectionKnobName[];\n\n// The source each drawn type's features live in. The generated types get one each; the five with\n// hand-written layers keep the source names they have always had.\ntype SectionSourceKeys = {\n ferry: 'ferries';\n tollRoad: 'tollRoads';\n traffic: 'incidents';\n tunnel: 'tunnels';\n vehicleRestricted: 'vehicleRestricted';\n} & { [T in GeneratedSectionType]: `${T}Sections` };\n\n/**\n * The key one section type's source, events scope and `getShown()` entry are filed under.\n *\n * @remarks\n * `routing.events[sectionSourceKey('lowEmissionZone')]` and\n * `routing.getShown()[sectionSourceKey('tunnel')]` both reach the section named by the type.\n *\n * @group Routing\n */\nexport type SectionSourceKey<T extends DrawnSectionType = DrawnSectionType> = (typeof SECTION_REGISTRY)[T]['sourceKey'];\n\ntype SectionRegistryEntry<T extends DrawnSectionType> = {\n /**\n * The key of the source holding this type's features, and of the layer specs drawn from it.\n */\n sourceKey: SectionSourceKeys[T];\n /**\n * Whether the type is drawn when nothing is configured.\n *\n * A type draws itself when its stretches are **sparse** along a route and **consequential**\n * for the driver — a stretch to pay for (`tollRoad`, `tollVignette`), to qualify for\n * (`lowEmissionZone`, `vehicleRestricted`), to drive the car aboard (`carTrain`, `ferry`),\n * to lose the signal in (`tunnel`), or to lose time on (`traffic`). A type\n * whose stretches cover most of a route is opt-in however consequential it is, because a band\n * that is everywhere says nothing and buries the route line: `motorway` and `urban` each run\n * nearly the whole length. So is a type the base map already draws, since the route overlay\n * would only repeat it.\n *\n * A type that posts a sign rather than a band escapes that rule — `speedLimit` covers the\n * whole route and still draws itself, because a sign is legible wherever it lands.\n */\n visible: boolean;\n /**\n * Where the type's line sits against the route line, and so how wide it is drawn. The generated\n * types that band the route sit beneath it; the five that draw themselves keep the relation they\n * have always had — `tollRoad` a band, the rest the route line's own colour along the stretch.\n *\n * Absent on a type whose presentation is a sign, which draws no line to place.\n */\n style?: SectionDrawStyle;\n /**\n * The line pattern the type is drawn with when nothing is configured. A broken line marks a\n * stretch the driver does not simply drive along as usual — the car carried (`carTrain`), the\n * surface unpaved, the access permit-bound or restricted.\n *\n * On a type drawn with two lines the pattern belongs to the principal one, so the outline\n * stays solid beneath it. Absent on a type whose presentation is a sign.\n */\n pattern?: SectionLinePattern;\n /**\n * The knobs this type answers to. A knob a type cannot honour is left out of the catalogue\n * rather than accepted and ignored — `traffic` colours its line by `magnitudeOfDelay`, so it\n * offers no `color`, and its icons come from the incident data, so it offers no `icon`.\n */\n knobs: readonly SectionKnobName[];\n};\n\n/**\n * What every drawn section type is, and which knobs it answers to.\n *\n * @remarks\n * The single table the section tier is built from: the layer specs, the per-type sources, the\n * visibility rules and the knob catalogue all read it, so none of them can drift from the others.\n * @ignore\n */\nexport const SECTION_REGISTRY = {\n carpool: {\n sourceKey: 'carpoolSections',\n visible: false,\n style: 'halo',\n pattern: 'solid',\n knobs: ALL_SECTION_KNOBS,\n },\n carTrain: {\n sourceKey: 'carTrainSections',\n visible: true,\n style: 'halo',\n pattern: 'dashed',\n knobs: ALL_SECTION_KNOBS,\n },\n importantRoadStretch: {\n sourceKey: 'importantRoadStretchSections',\n visible: false,\n style: 'halo',\n pattern: 'solid',\n knobs: ALL_SECTION_KNOBS,\n },\n lowEmissionZone: {\n sourceKey: 'lowEmissionZoneSections',\n visible: true,\n style: 'halo',\n pattern: 'solid',\n knobs: ALL_SECTION_KNOBS,\n },\n motorway: {\n sourceKey: 'motorwaySections',\n visible: false,\n style: 'halo',\n pattern: 'solid',\n knobs: ALL_SECTION_KNOBS,\n },\n pedestrian: {\n sourceKey: 'pedestrianSections',\n visible: false,\n style: 'halo',\n pattern: 'dashed',\n knobs: ALL_SECTION_KNOBS,\n },\n // The one type asked for a number rather than a stretch, so a sign per section is its whole\n // presentation. A line would mark every stretch that carries a limit — which is all of them,\n // since the limit changes at nearly every junction — and still never say what the limit is.\n speedLimit: {\n sourceKey: 'speedLimitSections',\n visible: true,\n knobs: SIGN_SECTION_KNOBS,\n },\n tollVignette: {\n sourceKey: 'tollVignetteSections',\n visible: true,\n style: 'halo',\n pattern: 'dashed',\n knobs: ALL_SECTION_KNOBS,\n },\n unpaved: {\n sourceKey: 'unpavedSections',\n visible: false,\n style: 'halo',\n pattern: 'dashed',\n knobs: ALL_SECTION_KNOBS,\n },\n urban: { sourceKey: 'urbanSections', visible: false, style: 'halo', pattern: 'solid', knobs: ALL_SECTION_KNOBS },\n ferry: { sourceKey: 'ferries', visible: true, style: 'inline', pattern: 'solid', knobs: ALL_SECTION_KNOBS },\n tollRoad: { sourceKey: 'tollRoads', visible: true, style: 'halo', pattern: 'solid', knobs: ALL_SECTION_KNOBS },\n traffic: {\n sourceKey: 'incidents',\n visible: true,\n style: 'inline',\n pattern: 'solid',\n knobs: ['visible', 'opacity', 'width', 'widthFactor', 'style'],\n },\n tunnel: { sourceKey: 'tunnels', visible: true, style: 'inline', pattern: 'solid', knobs: ALL_SECTION_KNOBS },\n vehicleRestricted: {\n sourceKey: 'vehicleRestricted',\n visible: true,\n style: 'inline',\n pattern: 'dotted',\n knobs: ALL_SECTION_KNOBS,\n },\n} as const satisfies { [T in DrawnSectionType]: SectionRegistryEntry<T> };\n\n/**\n * The knobs one section type answers to.\n *\n * @remarks\n * Read off {@link SECTION_REGISTRY}, so `sections.traffic.color` is a compile error rather than a\n * setting that draws nothing: `traffic` colours its line by `magnitudeOfDelay`.\n *\n * @group Routing\n */\nexport type SectionKnobOf<T extends DrawnSectionType = DrawnSectionType> =\n (typeof SECTION_REGISTRY)[T]['knobs'][number];\n\n/**\n * The section types that answer to the given knob — the inverse of {@link SectionKnobOf}.\n *\n * @example\n * ```typescript\n * type SignPosting = SectionTypeWithKnob<'sign'>; // 'speedLimit'\n * ```\n *\n * @group Routing\n */\nexport type SectionTypeWithKnob<KNOB extends SectionKnobName> = {\n [T in DrawnSectionType]: KNOB extends SectionKnobOf<T> ? T : never;\n}[DrawnSectionType];\n\n/**\n * Whether the given type answers to the given knob, narrowing `type` to {@link SectionTypeWithKnob}.\n *\n * @remarks\n * What a panel of section controls asks to decide which controls to render — the type system says\n * the same thing through {@link SectionKnobOf}, but only a value can be looped over. Once narrowed,\n * `` `sections.${type}.${knobName}` `` is a {@link RoutingKnobId}.\n *\n * @example\n * ```typescript\n * if (sectionSupportsKnob(type, 'sign')) {\n * const { range } = knobEntryOf(routingKnobCatalogue, `sections.${type}.sign.minZoom`);\n * }\n * ```\n *\n * @group Routing\n */\nexport const sectionSupportsKnob = <KNOB extends SectionKnobName>(\n type: DrawnSectionType,\n knobName: KNOB,\n): type is SectionTypeWithKnob<KNOB> => (SECTION_REGISTRY[type].knobs as readonly SectionKnobName[]).includes(knobName);\n\n/**\n * One type's registry entry, widened from the literal table.\n *\n * @remarks\n * {@link SECTION_REGISTRY} is `as const`, so indexing it with a union of types gives a union of\n * entry literals — and the band fields, which only the types that draw a band carry at all, cannot\n * be read off that union. Widening to the declared entry shape is what makes them readable as the\n * optionals they are.\n * @ignore\n */\nexport const sectionEntry = (type: DrawnSectionType): SectionRegistryEntry<DrawnSectionType> => SECTION_REGISTRY[type];\n\n/**\n * Whether the given drawn type has its layers generated from the registry.\n * @ignore\n */\nexport const isGeneratedSectionType = (type: DrawnSectionType): type is GeneratedSectionType => {\n const generated: readonly DrawnSectionType[] = generatedSectionTypes;\n return generated.includes(type);\n};\n\n/**\n * Whether the given type draws itself when `sections` says nothing about it.\n *\n * @remarks\n * The answer a panel of section controls needs to show which switches start on. Asking the map\n * instead cannot answer it: a type draws no layer for a route carrying none of its sections, so an\n * absent layer reads the same as a type switched off.\n *\n * @example\n * ```typescript\n * const [drawn, optIn] = partition(drawnSectionTypes, sectionDrawsByDefault);\n * ```\n *\n * @group Routing\n */\nexport const sectionDrawsByDefault = (type: DrawnSectionType): boolean => SECTION_REGISTRY[type].visible;\n\n/**\n * A generated type whose presentation is a road sign carrying the section's own value.\n *\n * @remarks\n * Derived from the knob catalogue, so offering `sign` and being drawn as one are the same fact.\n * @ignore\n */\nexport type SignPostingSectionType = {\n [T in GeneratedSectionType]: 'sign' extends SectionKnobOf<T> ? T : never;\n}[GeneratedSectionType];\n\n/**\n * A generated type whose presentation is a band along the stretch it covers.\n * @ignore\n */\nexport type BandedSectionType = Exclude<GeneratedSectionType, SignPostingSectionType>;\n\n/**\n * Whether the given generated type posts a sign rather than banding the route.\n * @ignore\n */\nexport const postsSign = (type: GeneratedSectionType): type is SignPostingSectionType =>\n sectionSupportsKnob(type, 'sign');\n\n/**\n * The generated types that band the route — every one whose value is not its own presentation.\n * @ignore\n */\nexport const bandedSectionTypes = generatedSectionTypes.filter((type): type is BandedSectionType => !postsSign(type));\n\n/**\n * The key the given section type's source, events scope and `getShown()` entry are filed under.\n *\n * @example\n * ```typescript\n * routing.events[sectionSourceKey('lowEmissionZone')].on('click', (section) => { … });\n * const tunnels = routing.getShown()[sectionSourceKey('tunnel')];\n * ```\n *\n * @group Routing\n */\nexport const sectionSourceKey = <T extends DrawnSectionType>(type: T): SectionSourceKey<T> =>\n SECTION_REGISTRY[type].sourceKey;\n","import { type DisplayUnits, TomTomConfig } from '@tomtom-org/maps-sdk/core';\nimport type { ToBeAddedLayerSpecTemplate, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { prefixLayerID } from '../../shared/layers/utils';\nimport {\n type GeneratedSectionType,\n generatedSectionTypes,\n type SectionSourceKey,\n sectionSourceKey,\n} from '../layers/sectionRegistry';\nimport type {\n RouteLayersConfig,\n RouteWaypointSize,\n RouteWidth,\n RoutingModuleConfig,\n WaypointStopNumbering,\n} from '../types/routeModuleConfig';\nimport type { RoutingLayersSpecs } from '../types/routingSourcesAndLayers';\n\n/**\n * The width preset a route line is drawn at when the config sets none.\n * @ignore\n */\nexport const ROUTE_DEFAULT_WIDTH: RouteWidth = 'm';\n\n/**\n * The size preset the waypoint pins are drawn at when the config sets none.\n * @ignore\n */\nexport const WAYPOINT_DEFAULT_SIZE: RouteWaypointSize = 'm';\n\n/**\n * How much wider a hovered or clicked route draws its outline when the config sets no factor.\n * @ignore\n */\nexport const HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR = 1.2;\n\n/**\n * How much larger a hovered or clicked waypoint draws its pin when the config sets no factor.\n * @ignore\n */\nexport const WAYPOINT_HIGHLIGHT_DEFAULT_SIZE_FACTOR = 1.2;\n\n/**\n * The halo width of the waypoint labels, in pixels, when the config sets none.\n * @ignore\n */\nexport const WAYPOINT_LABEL_DEFAULT_HALO_WIDTH = 1.5;\n\n/**\n * How the stops are marked inside their pins when the config sets nothing.\n * @ignore\n */\nexport const WAYPOINT_DEFAULT_STOP_NUMBERING: WaypointStopNumbering = 'numbers';\n\nconst mapLayerSpecs = (\n layerSpecs: Record<string, Partial<ToBeAddedLayerSpecTemplate>> = {},\n layerIDPrefix?: string,\n): ToBeAddedLayerSpecWithoutSource[] =>\n // The key of the entry is the layer ID. A configured spec is a patch to merge into a built\n // layer, so it carries no `type` of its own and only the assertion makes it stand in for a\n // whole one:\n Object.entries(layerSpecs).map(\n ([id, spec]) =>\n ({\n ...spec,\n id: prefixLayerID(id, layerIDPrefix),\n }) as ToBeAddedLayerSpecWithoutSource, // NOSONAR\n );\n\n/**\n * @ignore\n */\nexport const createLayersSpecs = (\n layerConfigs: RouteLayersConfig = {},\n layerIDPrefix?: string,\n): RoutingLayersSpecs => ({\n ...(Object.fromEntries(\n generatedSectionTypes.map((type) => [\n sectionSourceKey(type),\n mapLayerSpecs(layerConfigs.sections?.[type], layerIDPrefix),\n ]),\n ) as Pick<RoutingLayersSpecs, SectionSourceKey<GeneratedSectionType>>),\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?.traffic, 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 countryCrossings: mapLayerSpecs(layerConfigs.countryCrossings, layerIDPrefix),\n});\n\n/**\n * The display units the routes are labelled in: the configuration's, else the global ones. Read when\n * drawing, never stored, so `getConfig()` returns only what the caller set.\n * @ignore\n */\nexport const routeDisplayUnits = (config: RoutingModuleConfig | undefined): DisplayUnits | undefined =>\n config?.displayUnits ?? TomTomConfig.instance.get().displayUnits;\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { KnobRange, LayerSpecTemplate } from '../../shared';\nimport { clampFactor } from '../../shared/knobUtils';\nimport { scaleNumericValue } from '../../shared/layers/expressionTransforms';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport type { RouteWaypointSize, RouteWidth, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport { ROUTE_DEFAULT_WIDTH, WAYPOINT_DEFAULT_SIZE } from '../util/config';\n\nconst zoomRamp = (...stops: number[]): ExpressionSpecification => ['interpolate', ['linear'], ['zoom'], ...stops];\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 * Layer filter keeping the features of the selected route: the one for a layer that draws only that route.\n * @group Routing\n */\nexport const SELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'selected'];\n\n/**\n * Layer filter keeping the features of the deselected routes: the one for a layer that draws only those.\n * @group Routing\n */\nexport const DESELECTED_ROUTE_FILTER: ExpressionSpecification = ['==', ['get', 'routeState'], 'deselected'];\n\n/**\n * A line along the selected route's stretches of a section, with round joins.\n * @ignore\n */\nexport const selectedRouteLine = (\n paint: LineLayerSpecification['paint'],\n layout?: LineLayerSpecification['layout'],\n): LayerSpecTemplate<LineLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n layout: { 'line-join': 'round', ...layout },\n paint,\n});\n\n/**\n * A map POI's icon posted along the selected route from `minzoom`, growing to the POI's own size at\n * the zoom the map draws the POI.\n * @ignore\n */\nexport const selectedRoutePOISymbol = (\n iconImage: string,\n minzoom: number,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom,\n layout: {\n 'symbol-placement': 'point',\n 'symbol-avoid-edges': true,\n 'icon-image': iconImage,\n 'icon-size': zoomRamp(minzoom, 0.8, 16.5, 1),\n },\n});\n\n/**\n * An image id as the module instance adds it: suffixed with the instance index where there is one.\n * @ignore\n */\nexport const instanceImageID = (imageID: string, instanceIndex?: number): string =>\n instanceIndex === undefined ? imageID : suffixNumber(imageID, instanceIndex);\n\n/**\n * The values {@link RoutingModuleConfig.widthFactor} and a section's `widthFactor` are clamped to.\n *\n * @remarks\n * 0.5 of the thin preset is the thinnest that still draws a line inside its outline, 1 px within\n * 2 px at the lowest zoom; 2 of the thick one is a 30 px line at street zooms, about a motorway's\n * width. Beyond either end the route stops reading as a line over the roads it follows.\n * @ignore\n */\nexport const ROUTE_WIDTH_FACTOR_RANGE: KnobRange = { min: 0.5, max: 2, step: 0.1, bounds: 'hard' };\n\n/**\n * The values `waypoints.sizeFactor` is clamped to.\n *\n * @remarks\n * Spans a pin from about 14 px wide, half the small preset zoomed out, to 120 px, twice the large\n * one zoomed in.\n * @ignore\n */\nexport const ROUTE_WAYPOINT_SIZE_FACTOR_RANGE: KnobRange = { min: 0.5, max: 2, step: 0.1, bounds: 'hard' };\n\n/**\n * The values {@link WaypointHighlightConfig.sizeFactor} is clamped to.\n *\n * @remarks\n * Past 1.5 a hovered pin covers its neighbours' at the zooms where stops sit close together.\n * @ignore\n */\nexport const ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE: KnobRange = { min: 1, max: 1.5, step: 0.05, bounds: 'hard' };\n\n/**\n * The values {@link SectionIconConfig.sizeFactor} is clamped to.\n *\n * @remarks\n * From a quarter of the size the section type draws its icon at, to twice that size.\n * @ignore\n */\nexport const SECTION_ICON_SIZE_FACTOR_RANGE: KnobRange = { min: 0.25, max: 2, step: 0.05, bounds: 'hard' };\n\n/**\n * The values {@link RouteHighlightConfig.outlineWidthFactor} is clamped to.\n *\n * @remarks\n * A section halo is drawn beneath the route at 1.26 times its outline width or more, at every\n * preset and zoom; a wider outline on a hovered route would cover it.\n * @ignore\n */\nexport const ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE: KnobRange = { min: 1, max: 1.25, step: 0.05, bounds: 'hard' };\n\ntype SymbolLayout = NonNullable<SymbolLayerSpecification['layout']>;\n\n/** Room an icon a section posts where it starts keeps on a side facing the waypoint icon, in pixels. */\nconst SECTION_START_ICON_CLEARANCE = 10;\n\n/** MapLibre's own `icon-padding`, kept on the sides that face no waypoint icon. */\nconst DEFAULT_ICON_PADDING = 2;\n\n/**\n * The collision padding of an icon a route posts where its section starts, for the anchor of the\n * waypoint icon it may land on.\n *\n * @remarks\n * The anchor puts an edge of the waypoint icon on the point: a pin's tip for `bottom`, every edge\n * for `center`. Boxes that only touch leave both icons drawn, so the sides facing that edge keep\n * {@link SECTION_START_ICON_CLEARANCE} and the section icon gives way. An expression anchor can be\n * any of them, so every side keeps it.\n * @ignore\n */\nexport const sectionStartIconPadding = (\n waypointIconAnchor: SymbolLayout['icon-anchor'],\n): SymbolLayout['icon-padding'] => {\n const anchor = typeof waypointIconAnchor === 'string' ? waypointIconAnchor : 'center';\n const facing = (side: boolean): number =>\n anchor === 'center' || side ? SECTION_START_ICON_CLEARANCE : DEFAULT_ICON_PADDING;\n\n return [\n facing(anchor.startsWith('bottom')),\n facing(anchor.endsWith('left')),\n facing(anchor.startsWith('top')),\n facing(anchor.endsWith('right')),\n ];\n};\n\ntype IconSize = SymbolLayout['icon-size'];\n\nconst isExpression = (value: unknown): value is ExpressionSpecification => Array.isArray(value);\n\nconst scaleRamp = (ramp: ExpressionSpecification, factor: number): ExpressionSpecification => {\n if (factor === 1) return ramp;\n\n const scaled = scaleNumericValue(ramp, factor);\n return isExpression(scaled) ? scaled : ramp;\n};\n\n/**\n * The icon size of a section layer under the given {@link SectionIconConfig.sizeFactor}: the size\n * the layer draws, MapLibre's `1` where it sets none, times the clamped factor.\n * @ignore\n */\nexport const getSectionIconSize = (layerIconSize: IconSize, sizeFactor: number): IconSize => {\n const iconSize = layerIconSize ?? 1;\n const scaled = scaleNumericValue(iconSize, clampFactor(sizeFactor, SECTION_ICON_SIZE_FACTOR_RANGE));\n return typeof scaled === 'number' || isExpression(scaled) ? scaled : iconSize;\n};\n\n/**\n * How thick a route line is drawn: a preset and a multiplier over it.\n * @ignore\n */\nexport type RouteLineWidth = Pick<RoutingModuleConfig, 'width' | 'widthFactor'>;\n\nconst scaledWidth = (profiles: Record<RouteWidth, ExpressionSpecification>, lineWidth?: RouteLineWidth) =>\n scaleRamp(\n profiles[lineWidth?.width ?? ROUTE_DEFAULT_WIDTH],\n clampFactor(lineWidth?.widthFactor, ROUTE_WIDTH_FACTOR_RANGE),\n );\n\nconst LINE_FOREGROUND_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: zoomRamp(1, 2, 5, 3, 10, 5, 18, 7),\n m: zoomRamp(1, 3, 5, 4, 10, 7, 18, 10),\n l: zoomRamp(1, 5, 5, 6, 10, 11, 18, 15),\n};\n\nconst LINE_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: zoomRamp(1, 4, 5, 5, 10, 8, 18, 11),\n m: zoomRamp(1, 5, 5, 6, 10, 10, 18, 14),\n l: zoomRamp(1, 7, 5, 9, 10, 14, 18, 19),\n};\n\nconst WAYPOINT_ICON_SIZE = zoomRamp(8, 0.6, 22, 0.8);\n\n// Pixels of stop mark text per unit of pin icon size, so the mark keeps one proportion to the pin\n// head at every zoom.\nconst STOP_MARK_TEXT_SIZE_PER_ICON_SIZE = 21;\n\n// How high the SDK pin's head centre stands above its tip at icon size 1, in pixels: `pin.svg` is\n// 140 high with the head centred at y 59.6, drawn at a pixel ratio of 2.\nconst PIN_HEAD_CENTER_HEIGHT = (140 - 59.6) / 2;\n\nconst WAYPOINT_SIZE_PRESET_FACTORS: Record<RouteWaypointSize, number> = { s: 0.75, m: 1, l: 1.25 };\n\n/**\n * Returns the foreground line width expression for the given width.\n * @ignore\n */\nexport const getLineForegroundWidth = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaledWidth(LINE_FOREGROUND_WIDTHS, lineWidth);\n\n/**\n * Returns the outline line width expression for the given width.\n * @ignore\n */\nexport const getLineOutlineWidth = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaledWidth(LINE_OUTLINE_WIDTHS, lineWidth);\n\nconst waypointScale = (size?: RouteWaypointSize, sizeFactor?: number): number =>\n WAYPOINT_SIZE_PRESET_FACTORS[size ?? WAYPOINT_DEFAULT_SIZE] *\n clampFactor(sizeFactor, ROUTE_WAYPOINT_SIZE_FACTOR_RANGE);\n\n/**\n * Returns the waypoint icon size expression for the given size preset and multiplier.\n * @ignore\n */\nexport const getWaypointIconSize = (size?: RouteWaypointSize, sizeFactor?: number): ExpressionSpecification =>\n scaleRamp(WAYPOINT_ICON_SIZE, waypointScale(size, sizeFactor));\n\n/**\n * Returns the text size expression of the stop mark drawn inside a waypoint pin: a fixed multiple\n * of {@link getWaypointIconSize}, so {@link STOP_MARK_TEXT_OFFSET} centres it at every size and zoom.\n * @ignore\n */\nexport const getWaypointIndexTextSize = (size?: RouteWaypointSize, sizeFactor?: number): ExpressionSpecification =>\n scaleRamp(WAYPOINT_ICON_SIZE, STOP_MARK_TEXT_SIZE_PER_ICON_SIZE * waypointScale(size, sizeFactor));\n\n/**\n * The offset of a stop mark from its waypoint's point, in ems of its text size: the pin head's\n * centre.\n * @ignore\n */\nexport const STOP_MARK_TEXT_OFFSET: [number, number] = [0, -PIN_HEAD_CENTER_HEIGHT / STOP_MARK_TEXT_SIZE_PER_ICON_SIZE];\n\nconst SECTION_HALO_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: zoomRamp(1, 8, 5, 9, 10, 12, 18, 15),\n m: zoomRamp(1, 9, 5, 11, 10, 15, 18, 20),\n l: zoomRamp(1, 12, 5, 14, 10, 19, 18, 24),\n};\n\n/**\n * Returns the width expression of a section halo for the given width.\n *\n * @remarks\n * Wider than {@link getLineOutlineWidth} at every zoom, which is what makes a section drawn\n * beneath the route show as a band around it rather than disappear under the route's own outline.\n * @ignore\n */\nexport const getSectionHaloWidth = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaledWidth(SECTION_HALO_WIDTHS, lineWidth);\n\nconst INSTRUCTION_LINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: zoomRamp(16, 8, 22, 12),\n m: zoomRamp(16, 12, 22, 17),\n l: zoomRamp(16, 17, 22, 24),\n};\n\nconst INSTRUCTION_OUTLINE_WIDTHS: Record<RouteWidth, ExpressionSpecification> = {\n s: zoomRamp(16, 10, 22, 14),\n m: zoomRamp(16, 14, 22, 20),\n l: zoomRamp(16, 20, 22, 28),\n};\n\n/**\n * Returns the instruction line width expression for the given width.\n * @ignore\n */\nexport const getInstructionLineWidth = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaledWidth(INSTRUCTION_LINE_WIDTHS, lineWidth);\n\n/**\n * Returns the instruction outline width expression for the given width.\n * @ignore\n */\nexport const getInstructionOutlineWidth = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaledWidth(INSTRUCTION_OUTLINE_WIDTHS, lineWidth);\n\nconst INSTRUCTION_ARROW_SIZE = zoomRamp(16, 1, 22, 2);\n\n/**\n * Returns the instruction arrow icon size expression for the given width.\n *\n * @remarks\n * Only `widthFactor` scales it, not the preset.\n * @ignore\n */\nexport const getInstructionArrowSize = (lineWidth?: RouteLineWidth): ExpressionSpecification =>\n scaleRamp(INSTRUCTION_ARROW_SIZE, clampFactor(lineWidth?.widthFactor, ROUTE_WIDTH_FACTOR_RANGE));\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 { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport type { CountryCrossingConfig } from '../types/routeModuleConfig';\nimport { type CountryCrossingColors, resolveCountryCrossingColors } from '../util/countryCrossingColors';\nimport { instanceImageID, SELECTED_ROUTE_FILTER } from './shared';\n\n// The colours a crossing draws with on a light map, which is what a layer built without a theme\n// falls back to.\nconst DEFAULT_CROSSING_COLORS = resolveCountryCrossingColors(undefined, 'light');\n\n/**\n * The image id of the plaque a crossing label is written on, before any instance suffix.\n * @ignore\n */\nexport const COUNTRY_CROSSING_PLAQUE_IMAGE_ID = 'route-country-crossing-plaque';\n\n/**\n * The MapLibre layer id of the crossing labels, before any instance prefix.\n * @ignore\n */\nexport const COUNTRY_CROSSING_LAYER_ID = 'routeCountryCrossing';\n\n/**\n * From which zoom a crossing is drawn. Low enough for a continental route to show its borders while\n * still framed whole.\n * @ignore\n */\nexport const CROSSING_DEFAULT_MIN_ZOOM = 4;\n\n// One short string, so the label barely grows with the zoom and stays clear of the place names.\nconst CROSSING_TEXT_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 5, 11, 12, 13];\n\n// The feature's bearing as a map-aligned rotation: 0 is east, and a bearing past south is turned\n// back by half a turn so the label reads upright rather than inverted.\nconst CROSSING_ROUTE_ROTATION: ExpressionSpecification = [\n 'case',\n ['>', ['get', 'bearing'], 180],\n ['-', ['get', 'bearing'], 270],\n ['-', ['get', 'bearing'], 90],\n];\n\n/**\n * Builds the layer that writes each border crossing on its plaque.\n *\n * @remarks\n * A point layer, because a crossing is the seam between two country sections rather than a stretch\n * of road. `icon-text-fit` stretches the plaque around the label, so one image serves every\n * crossing at every zoom.\n * @ignore\n */\nexport const countryCrossingSymbol = (\n instanceIndex?: number,\n config?: CountryCrossingConfig,\n colors?: CountryCrossingColors,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const alignedToRoute = config?.alignment === 'route';\n\n return {\n type: 'symbol',\n filter: SELECTED_ROUTE_FILTER,\n minzoom: config?.minZoom ?? CROSSING_DEFAULT_MIN_ZOOM,\n layout: {\n 'symbol-placement': 'point',\n 'icon-image': instanceImageID(COUNTRY_CROSSING_PLAQUE_IMAGE_ID, instanceIndex),\n 'icon-text-fit': 'both',\n 'icon-text-fit-padding': [4, 8, 4, 8],\n 'icon-rotation-alignment': alignedToRoute ? 'map' : 'viewport',\n 'text-rotation-alignment': alignedToRoute ? 'map' : 'viewport',\n ...(alignedToRoute && {\n 'icon-rotate': CROSSING_ROUTE_ROTATION,\n 'text-rotate': CROSSING_ROUTE_ROTATION,\n }),\n 'text-field': ['get', 'label'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': CROSSING_TEXT_SIZE,\n // There are few enough crossings that keeping every one costs nothing in clutter.\n 'text-allow-overlap': true,\n 'icon-allow-overlap': true,\n ...(config?.visible === false && { visibility: 'none' }),\n },\n paint: { 'text-color': colors?.text ?? DEFAULT_CROSSING_COLORS.text },\n };\n};\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport { darkerShadeOf } from '../../utils/colorUtils';\nimport type {\n SectionDisplayConfig,\n SectionLineLayerName,\n SectionSignLayerName,\n SectionSignPlacement,\n SectionSignPriority,\n SectionSymbolLayerName,\n} from '../types/routeModuleConfig';\nimport type {\n BandedSectionType,\n BespokeSectionType,\n DrawnSectionType,\n GeneratedSectionType,\n SectionDrawStyle,\n SectionLinePattern,\n SignPostingSectionType,\n} from './sectionRegistry';\nimport { SECTION_REGISTRY, sectionEntry, sectionSupportsKnob } from './sectionRegistry';\nimport {\n getLineForegroundWidth,\n getSectionHaloWidth,\n getSectionIconSize,\n type RouteLineWidth,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n// Colours for the sections that band the route, chosen to be distinguishable from each other and\n// from the main route line. They live here rather than in the registry so the knob catalogue can\n// read the default off the built layer and cannot report a colour the layer does not use.\nconst GENERATED_SECTION_PAINT: Record<BandedSectionType, { color: string; opacity: number }> = {\n carpool: { color: '#6D4AC7', opacity: 0.55 },\n carTrain: { color: '#8A5A2B', opacity: 0.6 },\n importantRoadStretch: { color: '#C7761F', opacity: 0.6 },\n lowEmissionZone: { color: '#2E8B57', opacity: 0.5 },\n motorway: { color: '#1F6FB2', opacity: 0.4 },\n pedestrian: { color: '#B23F86', opacity: 0.6 },\n tollVignette: { color: '#8C6B1F', opacity: 0.55 },\n unpaved: { color: '#7A6A54', opacity: 0.6 },\n urban: { color: '#7E8C8E', opacity: 0.3 },\n};\n\nconst DASH_ARRAYS: Record<SectionLinePattern, [number, number] | undefined> = {\n solid: undefined,\n dashed: [2, 1.5],\n dotted: [0, 1.5],\n};\n\n/**\n * Which line of a multi-line section a spec draws, and so what `sections.<type>.color` means for it.\n *\n * @remarks\n * `principal` takes the configured colour as given; `outline` derives a darker shade of it, the\n * same way the main route line derives its own outline.\n * @ignore\n */\nexport type SectionLineRole = 'principal' | 'outline';\n\n/**\n * Whether the given type is drawn, per its configuration or its own default.\n * @ignore\n */\nexport const isSectionVisible = (type: DrawnSectionType, config?: SectionDisplayConfig): boolean =>\n config?.visible ?? SECTION_REGISTRY[type].visible;\n\n/**\n * How the given type is drawn against the route line, per its configuration or its own default.\n *\n * @remarks\n * `undefined` for a type that posts a sign, which draws no line to relate to the route.\n * @ignore\n */\nexport const sectionDrawStyle = (type: DrawnSectionType, config?: SectionDisplayConfig): SectionDrawStyle | undefined =>\n (sectionSupportsKnob(type, 'style') && config?.style) || sectionEntry(type).style;\n\n// The route layer each draw style anchors its sections beneath. `routeDeselectedOutline` is the\n// bottom of the route's own stack, so a halo lands under every line the route draws; a section\n// inserted beneath `routeLineArrows` covers the route line and leaves the arrows on top of it.\nconst SECTION_STYLE_ANCHOR: Record<SectionDrawStyle, string> = {\n halo: 'routeDeselectedOutline',\n inline: 'routeLineArrows',\n};\n\n/**\n * The route layer a section of the given type is inserted beneath.\n *\n * @remarks\n * The draw style decides this: a band around the route has to sit under the route's lines, and a\n * section that stands in for the route line has to sit over them. Types drawn with several lines\n * anchor their topmost line here and chain the rest to it, so the group moves as one.\n * @ignore\n */\nexport const sectionStyleAnchor = (type: DrawnSectionType, config?: SectionDisplayConfig): string | undefined => {\n const drawStyle = sectionDrawStyle(type, config);\n return drawStyle && SECTION_STYLE_ANCHOR[drawStyle];\n};\n\n// The pattern belongs to the line the eye reads as the section. On a type drawn with two, the\n// outline stays solid so it remains the continuous halo the dashes sit on.\nconst applyLinePatternKnob = (\n type: DrawnSectionType,\n paint: NonNullable<LineLayerSpecification['paint']>,\n config?: SectionDisplayConfig,\n): void => {\n const pattern = config?.pattern ?? sectionEntry(type).pattern;\n const dashArray = pattern && DASH_ARRAYS[pattern];\n if (dashArray) {\n paint['line-dasharray'] = dashArray;\n return;\n }\n\n delete paint['line-dasharray'];\n};\n\n/**\n * Applies the uniform section knobs to one line layer of a section.\n *\n * @remarks\n * The one place the semantic tier turns into paint, for all fifteen drawn types: the hand-written\n * layers of the bespoke types pass through here just as the generated ones do, which is what lets\n * `sections.tunnel.color` and `sections.urban.color` mean the same thing.\n *\n * A knob the type does not carry is left alone rather than defaulted over — `traffic` keeps the\n * dash pattern that encodes incident severity, because it offers no `pattern` knob to override it\n * with.\n * @ignore\n */\nexport const withSectionLineKnobs = (\n type: DrawnSectionType,\n spec: LayerSpecTemplate<LineLayerSpecification>,\n config?: SectionDisplayConfig,\n routeLineWidth?: RouteLineWidth,\n role: SectionLineRole = 'principal',\n): LayerSpecTemplate<LineLayerSpecification> => {\n const lineWidth: RouteLineWidth = {\n width: config?.width ?? routeLineWidth?.width,\n widthFactor: config?.widthFactor ?? routeLineWidth?.widthFactor,\n };\n const paint: LineLayerSpecification['paint'] = {\n ...spec.paint,\n 'line-width':\n sectionDrawStyle(type, config) === 'halo'\n ? getSectionHaloWidth(lineWidth)\n : getLineForegroundWidth(lineWidth),\n };\n\n if (config?.color && sectionSupportsKnob(type, 'color')) {\n const configuredColor = role === 'outline' ? darkerShadeOf(config.color) : config.color;\n if (configuredColor) paint['line-color'] = configuredColor;\n }\n\n if (config?.opacity !== undefined && sectionSupportsKnob(type, 'opacity')) {\n paint['line-opacity'] = config.opacity;\n }\n\n if (role === 'principal' && sectionSupportsKnob(type, 'pattern')) applyLinePatternKnob(type, paint, config);\n\n return {\n ...spec,\n layout: {\n ...spec.layout,\n ...(!isSectionVisible(type, config) && { visibility: 'none' }),\n },\n paint,\n };\n};\n\n/**\n * Applies the uniform section knobs to one symbol layer of a section.\n *\n * @remarks\n * The icon knob replaces the image on a section that already draws one, so a bespoke type with an\n * icon takes the same `sections.<type>.icon` as the generated types that have one added for them.\n * Placement is set only where asked for, and a size factor multiplies the size the layer was\n * written with, so replacing the image on a bespoke type keeps its zoom ramp and its anchoring.\n * @ignore\n */\nexport const withSectionSymbolKnobs = (\n type: DrawnSectionType,\n spec: LayerSpecTemplate<SymbolLayerSpecification>,\n config?: SectionDisplayConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const icon = sectionSupportsKnob(type, 'icon') ? config?.icon : undefined;\n return {\n ...spec,\n layout: {\n ...spec.layout,\n ...(icon && {\n 'icon-image': icon.image,\n ...(icon.sizeFactor !== undefined && {\n 'icon-size': getSectionIconSize(spec.layout?.['icon-size'], icon.sizeFactor),\n }),\n ...(icon.placement && {\n 'symbol-placement': icon.placement === 'along' ? 'line' : 'line-center',\n }),\n }),\n ...(!isSectionVisible(type, config) && { visibility: 'none' }),\n },\n };\n};\n\n/**\n * Builds the line layer of one generated section that bands the route.\n * @ignore\n */\nexport const sectionLine = (\n type: BandedSectionType,\n config?: SectionDisplayConfig,\n routeLineWidth?: RouteLineWidth,\n): LayerSpecTemplate<LineLayerSpecification> =>\n withSectionLineKnobs(\n type,\n {\n filter: SELECTED_ROUTE_FILTER,\n type: 'line',\n // Round caps are what turn the zero-length dashes of the `dotted` pattern into dots;\n // with butt caps they render as slivers.\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': GENERATED_SECTION_PAINT[type].color,\n 'line-opacity': GENERATED_SECTION_PAINT[type].opacity,\n },\n },\n config,\n routeLineWidth,\n );\n\n/**\n * Builds the icon layer of one generated section, for a configuration that asks for an icon.\n *\n * @remarks\n * A section is a stretch of line, so the icon goes either once at its centre or repeatedly along\n * it: MapLibre places symbols on a line no other way.\n * @ignore\n */\nexport const sectionSymbol = (\n type: GeneratedSectionType,\n config: SectionDisplayConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> | undefined => {\n if (!config.icon || !sectionSupportsKnob(type, 'icon')) return undefined;\n\n return withSectionSymbolKnobs(\n type,\n {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n layout: {\n 'symbol-placement': 'line-center',\n // The caller asked for these icons, so they win over whatever the basemap wanted\n // to put in the same place, and they stay upright as the map rotates.\n 'icon-allow-overlap': true,\n 'icon-rotation-alignment': 'viewport',\n },\n },\n config,\n );\n};\n\n/**\n * The zoom a road sign starts drawing at, which is the bottom of {@link SIGN_ICON_SIZE}'s\n * ramp. Further out, neighbouring stretches fall in the same few pixels, so the handful that\n * survive the collision are an arbitrary sample of the drive.\n * @ignore\n */\nexport const SIGN_DEFAULT_MIN_ZOOM = 10;\n\n/**\n * Where a section's signs are posted when its config says nothing.\n * @ignore\n */\nexport const SIGN_DEFAULT_PLACEMENT: SectionSignPlacement = 'atChange';\n\n/**\n * Screen pixels between one sign and the next along a stretch, under `along` placement.\n *\n * Wide enough that a run of them reads as the same limit repeated rather than as a row of separate\n * facts, and close enough that a reader zoomed into a long stretch still has one in view.\n */\nconst SIGN_SPACING = 260;\n\n// A sign grows with the zoom, the way the ferry and toll-road icons do. It stays smaller than a\n// waypoint pin at every zoom: what it marks is context for the road, not a place on it.\nconst SIGN_ICON_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 10, 0.5, 16, 0.7];\n\n// A posted limit draws a tenth smaller than a start sign: a route carries many more of them, and\n// each is the least of what the route says about a spot.\nconst POSTED_SIGN_ICON_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 10, 0.45, 16, 0.63];\n\n// The number scales with the face it sits on: small enough not to crowd the ring, large enough\n// that three digits are still legible at the zoom the signs start drawing at.\nconst POSTED_SIGN_TEXT_SIZE: ExpressionSpecification = ['interpolate', ['linear'], ['zoom'], 10, 8, 16, 11.5];\n\n/**\n * Which signs win the space when two fall in it: the longest stretches first.\n *\n * MapLibre places a layer's symbols in ascending sort key, and the first placed keeps its spot. A\n * section's length in points is the best proxy available for how much of the drive its limit\n * governs, so negating it puts the governing limits on the map and drops the incidental ones —\n * rather than letting the order features happen to arrive in decide.\n */\nconst SIGN_SORT_KEY: ExpressionSpecification = ['-', ['get', 'startPointIndex'], ['get', 'endPointIndex']];\n\n// Where the number sits on its face. A disc is empty, so the number takes the middle; a plaque\n// prints its own words across the top, so the number goes below them or lands on top of \"LIMIT\".\n// Offsets are in ems of the text size, so they hold as the sign scales.\nconst SIGN_TEXT_OFFSET: ExpressionSpecification = [\n 'case',\n ['==', ['get', 'signFace'], 'plaque'],\n ['literal', [0, 0.55]],\n ['literal', [0, 0]],\n];\n\nconst signPlacement = (config?: SectionDisplayConfig): SectionSignPlacement =>\n config?.sign?.placement ?? SIGN_DEFAULT_PLACEMENT;\n\n/**\n * What a section type's signs give way to: the configured `sign.priority`, or the default of the\n * placement in force.\n *\n * @ignore\n */\nexport const signPriority = (config?: SectionDisplayConfig): SectionSignPriority =>\n config?.sign?.priority ?? (signPlacement(config) === 'atChange' ? 'belowRouteIcons' : 'belowMapLabels');\n\n/**\n * How the sign layer is anchored, per the placement it is drawn for.\n *\n * @remarks\n * Both read the section's own line, so this is the whole of the difference. MapLibre anchors a\n * symbol on a line at its **first vertex** under `point` placement, which is where the section\n * starts and so where its value begins to apply.\n */\nconst signAnchorLayout = (\n placement: SectionSignPlacement,\n): Pick<NonNullable<SymbolLayerSpecification['layout']>, 'symbol-placement' | 'symbol-spacing'> =>\n placement === 'atChange'\n ? { 'symbol-placement': 'point' }\n : { 'symbol-placement': 'line', 'symbol-spacing': SIGN_SPACING };\n\n/**\n * Builds the sign layer of a section type whose information is its own number rather than the\n * stretch it covers, which is the whole of what that type draws.\n *\n * @remarks\n * The face and the number both come from the feature, so one layer draws every country's sign.\n *\n * Overlap stays disallowed. A route splits into a section wherever its geometry does, so a route\n * can carry over a hundred speed limit sections, and MapLibre then keeps the signs that fit.\n * @ignore\n */\nexport const sectionSign = (\n type: SignPostingSectionType,\n config?: SectionDisplayConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n return {\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: config?.sign?.minZoom ?? SIGN_DEFAULT_MIN_ZOOM,\n layout: {\n ...signAnchorLayout(signPlacement(config)),\n 'icon-image': ['get', 'signImageID'],\n 'icon-size': POSTED_SIGN_ICON_SIZE,\n 'icon-rotation-alignment': 'viewport',\n 'text-field': ['get', 'signLabel'],\n 'text-font': [MAP_BOLD_FONT],\n 'text-size': POSTED_SIGN_TEXT_SIZE,\n 'text-offset': SIGN_TEXT_OFFSET,\n 'text-rotation-alignment': 'viewport',\n 'symbol-sort-key': SIGN_SORT_KEY,\n ...(!isSectionVisible(type, config) && { visibility: 'none' }),\n },\n // The numerals are the country's too — near-black almost everywhere, blue in Japan.\n paint: { 'text-color': ['get', 'signNumeralsColor'] },\n };\n};\n\n/**\n * The drawn types marked with a road sign of their own where each of their sections starts.\n * @ignore\n */\nexport type SectionStartSignType = Extract<BespokeSectionType, 'tunnel' | 'vehicleRestricted'>;\n\n/**\n * The image ids of the start signs, before any instance suffix.\n * @ignore\n */\nexport const SECTION_START_SIGN_IMAGE_IDS: Record<SectionStartSignType, string> = {\n tunnel: 'route-tunnel-sign',\n vehicleRestricted: 'route-vehicle-restricted-sign',\n};\n\n/**\n * Builds the layer that posts a type's sign where each of its sections starts.\n *\n * @remarks\n * `point` placement anchors the sign at the section's first vertex, as the speed limit signs are.\n * Overlap stays disallowed, so the sign gives way to every symbol layer above its own.\n * @ignore\n */\nexport const sectionStartSign = (\n type: SectionStartSignType,\n instanceIndex?: number,\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n filter: SELECTED_ROUTE_FILTER,\n type: 'symbol',\n minzoom: SIGN_DEFAULT_MIN_ZOOM,\n layout: {\n 'symbol-placement': 'point',\n 'icon-image':\n instanceIndex === undefined\n ? SECTION_START_SIGN_IMAGE_IDS[type]\n : suffixNumber(SECTION_START_SIGN_IMAGE_IDS[type], instanceIndex),\n 'icon-size': SIGN_ICON_SIZE,\n 'icon-rotation-alignment': 'viewport',\n },\n});\n\n// One naming scheme for every generated section layer, so the ids the module adds and the keys a\n// `layers.sections` override reaches them by cannot diverge.\nconst sectionLayerID = <T extends GeneratedSectionType, K extends string>(\n type: T,\n kind: K,\n): `routeSection${Capitalize<T>}${K}` =>\n `routeSection${(type.charAt(0).toUpperCase() + type.slice(1)) as Capitalize<T>}${kind}`;\n\n/**\n * The MapLibre layer id of a generated section's line layer, before any instance prefix.\n * @ignore\n */\nexport const sectionLineLayerID = <T extends GeneratedSectionType>(type: T): SectionLineLayerName<T> =>\n sectionLayerID(type, 'Line');\n\n/**\n * The MapLibre layer id of a generated section's icon layer, before any instance prefix.\n * @ignore\n */\nexport const sectionSymbolLayerID = <T extends GeneratedSectionType>(type: T): SectionSymbolLayerName<T> =>\n sectionLayerID(type, 'Symbol');\n\n/**\n * The MapLibre layer id of a generated section's sign layer, before any instance prefix.\n * @ignore\n */\nexport const sectionSignLayerID = <T extends GeneratedSectionType>(type: T): SectionSignLayerName<T> =>\n sectionLayerID(type, 'Sign');\n","import type { CommonPlaceProps, Waypoint } from '@tomtom-org/maps-sdk/core';\nimport 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 waypoints between origin and destination, numbered starting from 1.\n * 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 * The mark the stop's pin draws inside it, under {@link WaypointsConfig.stopNumbering}: `'2'`\n * or `'B'` for the second stop.\n *\n * @remarks\n * Absent on the origin and destination, under `'none'`, and on a stop drawn with a `middle.image`\n * or a mapped icon, none of which carries a mark.\n */\n stopDisplayLabel?: string;\n\n /**\n * How long the route waits at this waypoint, formatted for display.\n *\n * @remarks\n * Taken from {@link WaypointProps.pauseDurationSeconds} and rendered on the stop's label by\n * default (e.g. `30 min`). Absent when the waypoint carries no wait. Charging-stop pins carry\n * the same property, so both kinds of pin say how long you are there in the same words.\n *\n * @example\n * ```typescript\n * stopDuration: '30 min'\n * ```\n */\n stopDuration?: string;\n};\n\n/**\n * A waypoint as {@link RoutingModule} draws it, as `waypoints.icon.mapping` and\n * `waypoints.label.title` receive it: the properties it was given — a place's, when it was a search\n * result — and its {@link WaypointDisplayProps}.\n *\n * @group Routing\n */\nexport type DisplayWaypoint = Waypoint<WaypointDisplayProps & Partial<CommonPlaceProps>>;\n\n/**\n * @ignore\n */\nexport const STOP_DISPLAY_LABEL = 'stopDisplayLabel';\n","import type { DistanceUnitsType, GetPositionEntryPointOption } from '@tomtom-org/maps-sdk/core';\nimport type { SVGIconStyleOptions } from '../shared';\nimport {\n defaultOf,\n everyOf,\n type KnobDefinition,\n LABEL_HALO_WIDTH_RANGE,\n LABEL_SIZE_RANGE,\n OPACITY_RANGE,\n withIds,\n ZOOM_RANGE,\n} from '../shared/knobUtils';\nimport { MAP_BOLD_FONT, mapFonts } from '../shared/layers/commonLayerProps';\nimport { CROSSING_DEFAULT_MIN_ZOOM } from './layers/countryCrossingLayers';\nimport { SIGN_DEFAULT_MIN_ZOOM, SIGN_DEFAULT_PLACEMENT } from './layers/sectionLayers';\nimport {\n type DrawnSectionType,\n drawnSectionTypes,\n isGeneratedSectionType,\n type SectionDrawStyle,\n type SectionKnobName,\n type SectionLinePattern,\n sectionEntry,\n} from './layers/sectionRegistry';\nimport {\n ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE,\n ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE,\n ROUTE_WAYPOINT_SIZE_FACTOR_RANGE,\n ROUTE_WIDTH_FACTOR_RANGE,\n SECTION_ICON_SIZE_FACTOR_RANGE,\n} from './layers/shared';\nimport type {\n CountryCrossingAlignment,\n RouteWaypointSize,\n RouteWidth,\n SectionIconPlacement,\n SectionSignPlacement,\n SectionSignPriority,\n SectionSignUnit,\n WaypointStopNumbering,\n} from './types/routeModuleConfig';\nimport type {\n RoutingKnob,\n RoutingKnobId,\n RoutingKnobValueOf,\n SectionKnobLeaf,\n SectionKnobLeafValue,\n} from './types/routingKnobs';\nimport { FINISH_INDEX, MIDDLE_INDEX, START_INDEX, type WaypointIndexType } from './types/waypointDisplayProps';\nimport {\n HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR,\n ROUTE_DEFAULT_WIDTH,\n WAYPOINT_DEFAULT_SIZE,\n WAYPOINT_DEFAULT_STOP_NUMBERING,\n WAYPOINT_HIGHLIGHT_DEFAULT_SIZE_FACTOR,\n WAYPOINT_LABEL_DEFAULT_HALO_WIDTH,\n} from './util/config';\n\ntype WaypointPinKnobId = `waypoints.icon.${WaypointIndexType}.style.${keyof SVGIconStyleOptions}`;\n\ntype RouteKnobId = Exclude<RoutingKnobId, `sections.${string}` | WaypointPinKnobId>;\n\nconst routeWidths = everyOf<RouteWidth>()(['s', 'm', 'l']);\n\nconst routeKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'Everything the module draws; hiding it keeps the shown routes and waypoints.',\n default: true,\n },\n color: {\n kind: 'color',\n description:\n \"Colour of the route line, its darker outline and the waypoint pins. Unset: the map's accent if a theme sets one, else the SDK's blue.\",\n },\n width: {\n kind: 'enum',\n description: 'Thickness preset of the route line, which the section and instruction lines follow.',\n options: routeWidths,\n default: ROUTE_DEFAULT_WIDTH,\n },\n widthFactor: {\n kind: 'factor',\n description:\n 'Multiplier over the width preset; the instruction arrows and sections without their own follow it.',\n range: ROUTE_WIDTH_FACTOR_RANGE,\n default: 1,\n },\n 'highlight.outlineWidthFactor': {\n kind: 'factor',\n description:\n 'Multiplier on the outline width of a hovered or clicked route, selected or not; `1` draws it as any other.',\n range: ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE,\n default: HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR,\n },\n 'waypoints.size': {\n kind: 'enum',\n description: 'Size preset of the waypoint pins and their stop marks: `s` is 0.75 × `m`, `l` 1.25 × `m`.',\n options: everyOf<RouteWaypointSize>()(['s', 'm', 'l']),\n default: WAYPOINT_DEFAULT_SIZE,\n },\n 'waypoints.sizeFactor': {\n kind: 'factor',\n description: 'Multiplier over the waypoint size preset.',\n range: ROUTE_WAYPOINT_SIZE_FACTOR_RANGE,\n default: 1,\n },\n 'waypoints.icon.style.fillColor': {\n kind: 'color',\n description: \"Fill colour of the waypoint pins, in place of the route's.\",\n },\n 'waypoints.icon.style.outlineColor': {\n kind: 'color',\n description: 'Outline colour of the waypoint pins.',\n },\n 'waypoints.icon.style.outlineOpacity': {\n kind: 'number',\n description: 'Outline opacity of the waypoint pins.',\n range: OPACITY_RANGE,\n },\n 'waypoints.stopNumbering': {\n kind: 'enum',\n description: 'How the stop pins are marked: `1, 2, 3…`, `A, B, C…`, or left empty.',\n options: everyOf<WaypointStopNumbering>()(['numbers', 'letters', 'none']),\n default: WAYPOINT_DEFAULT_STOP_NUMBERING,\n },\n 'waypoints.highlight.sizeFactor': {\n kind: 'factor',\n description: 'Multiplier on the pin size of a hovered or clicked waypoint; `1` draws it as any other.',\n range: ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE,\n default: WAYPOINT_HIGHLIGHT_DEFAULT_SIZE_FACTOR,\n },\n 'waypoints.label.visible': {\n kind: 'toggle',\n description: 'Whether the waypoint labels show: the place name, and the stop duration under it.',\n default: true,\n },\n 'waypoints.label.color': {\n kind: 'color',\n description: 'Colour of the waypoint labels. Unset: `#333333`, `#FFFFFF` on a dark map.',\n },\n 'waypoints.label.haloColor': {\n kind: 'color',\n description: 'Colour of the halo around the waypoint labels. Unset: `#FFFFFF`, `#333333` on a dark map.',\n },\n 'waypoints.label.size': {\n kind: 'number',\n description: 'Text size of the waypoint labels, in pixels. Unset: 14 to 16 as the zoom grows.',\n range: LABEL_SIZE_RANGE,\n },\n 'waypoints.label.haloWidth': {\n kind: 'number',\n description: 'Width of the halo around the waypoint labels, in pixels.',\n range: LABEL_HALO_WIDTH_RANGE,\n default: WAYPOINT_LABEL_DEFAULT_HALO_WIDTH,\n },\n 'waypoints.label.opacity': {\n kind: 'number',\n description: 'Opacity of the waypoint labels.',\n range: OPACITY_RANGE,\n default: 1,\n },\n 'waypoints.label.font': {\n kind: 'enums',\n description: 'Font faces of the waypoint labels, in order: the first one available wins.',\n options: mapFonts,\n default: [MAP_BOLD_FONT],\n },\n 'waypoints.entryPoints': {\n kind: 'enum',\n description: \"Draw each waypoint at its place's main entry point when it has one, or at its own coordinates.\",\n options: everyOf<GetPositionEntryPointOption>()(['main-when-available', 'ignore']),\n default: 'ignore',\n },\n 'displayUnits.distance.type': {\n kind: 'enum',\n description:\n 'Unit system of the summary bubble distances, and of the speed limit signs on a route without `country` sections. Unset follows the global config.',\n options: everyOf<DistanceUnitsType>()(['metric', 'imperial_us', 'imperial_uk']),\n },\n 'displayUnits.distance.kilometers': {\n kind: 'text',\n description: 'Label after a distance in kilometres. Unset follows the global config, else `km`.',\n },\n 'displayUnits.distance.meters': {\n kind: 'text',\n description: 'Label after a distance in metres. Unset follows the global config, else `m`.',\n },\n 'displayUnits.distance.miles': {\n kind: 'text',\n description: 'Label after a distance in miles. Unset follows the global config, else `mi`.',\n },\n 'displayUnits.distance.feet': {\n kind: 'text',\n description: 'Label after a distance in feet. Unset follows the global config, else `ft`.',\n },\n 'displayUnits.distance.yards': {\n kind: 'text',\n description: 'Label after a distance in yards. Unset follows the global config, else `yd`.',\n },\n 'displayUnits.time.hours': {\n kind: 'text',\n description: 'Label after the hours of a duration. Unset follows the global config, else `hr`.',\n },\n 'displayUnits.time.minutes': {\n kind: 'text',\n description: 'Label after the minutes of a duration. Unset follows the global config, else `min`.',\n },\n 'chargingStops.visible': {\n kind: 'toggle',\n description: 'The charging stop icons and labels along an EV route.',\n default: true,\n },\n 'chargingStops.label.visible': {\n kind: 'toggle',\n description: 'The labels beside the charging stop icons.',\n default: true,\n },\n 'summaryBubbles.visible': {\n kind: 'toggle',\n description: \"The bubbles giving each route's distance, duration and traffic delay.\",\n default: true,\n },\n 'countryCrossings.visible': {\n kind: 'toggle',\n description: 'The plaques naming both countries at each border crossing; needs `country` sections.',\n default: true,\n },\n 'countryCrossings.minZoom': {\n kind: 'number',\n description: 'Lowest zoom a border crossing plaque is drawn at.',\n range: ZOOM_RANGE,\n default: CROSSING_DEFAULT_MIN_ZOOM,\n },\n 'countryCrossings.color': {\n kind: 'color',\n description: \"Colour of the border crossing plaques. Unset follows the map's light/dark theme.\",\n },\n 'countryCrossings.label.color': {\n kind: 'color',\n description: 'Label colour of the border crossing plaques. Unset: near-black or near-white, whichever reads.',\n },\n 'countryCrossings.alignment': {\n kind: 'enum',\n description: \"Whether a crossing plaque stays level with the screen or turns to the route's bearing.\",\n options: everyOf<CountryCrossingAlignment>()(['viewport', 'route']),\n default: 'viewport',\n },\n} as const satisfies { [ID in RouteKnobId]: KnobDefinition<RoutingKnobValueOf<ID>> };\n\nconst sectionKnobDefinitions: {\n [LEAF in SectionKnobLeaf]: (type: DrawnSectionType) => KnobDefinition<SectionKnobLeafValue<LEAF>>;\n} = {\n visible: (type) => ({\n kind: 'toggle',\n description: `The \\`${type}\\` sections.`,\n default: sectionEntry(type).visible,\n }),\n color: (type) => ({ kind: 'color', description: `Line colour of the \\`${type}\\` sections.` }),\n opacity: (type) => ({\n kind: 'number',\n description: `Line opacity of the \\`${type}\\` sections.`,\n range: OPACITY_RANGE,\n }),\n width: (type) => ({\n kind: 'enum',\n description: `Line width preset of the \\`${type}\\` sections; unset follows the route's.`,\n options: routeWidths,\n }),\n widthFactor: (type) => ({\n kind: 'factor',\n description: `Multiplier over the \\`${type}\\` sections' width preset; unset follows the route's.`,\n range: ROUTE_WIDTH_FACTOR_RANGE,\n }),\n style: (type) => ({\n kind: 'enum',\n description: `\\`halo\\` bands the route along the \\`${type}\\` sections; \\`inline\\` replaces its line.`,\n options: everyOf<SectionDrawStyle>()(['halo', 'inline']),\n ...defaultOf(sectionEntry(type).style),\n }),\n pattern: (type) => ({\n kind: 'enum',\n description: `Line pattern of the \\`${type}\\` sections.`,\n options: everyOf<SectionLinePattern>()(['solid', 'dashed', 'dotted']),\n ...defaultOf(sectionEntry(type).pattern),\n }),\n 'icon.image': (type) => ({\n kind: 'image',\n description: `Sprite image id of the \\`${type}\\` sections' icon; an id the sprite lacks draws nothing.`,\n }),\n 'icon.placement': (type) => ({\n kind: 'enum',\n description: `Whether the \\`${type}\\` icon sits once mid-section or repeats along it; set with \\`icon.image\\`.`,\n options: everyOf<SectionIconPlacement>()(['center', 'along']),\n ...defaultOf(isGeneratedSectionType(type) ? 'center' : undefined),\n }),\n 'icon.sizeFactor': (type) => ({\n kind: 'factor',\n description: `Multiplier over the \\`${type}\\` icon's size; set with \\`icon.image\\`.`,\n range: SECTION_ICON_SIZE_FACTOR_RANGE,\n default: 1,\n }),\n 'sign.placement': (type) => ({\n kind: 'enum',\n description: `Whether a \\`${type}\\` sign stands only where the value changes or repeats along the stretch.`,\n options: everyOf<SectionSignPlacement>()(['atChange', 'along']),\n default: SIGN_DEFAULT_PLACEMENT,\n }),\n 'sign.minZoom': (type) => ({\n kind: 'number',\n description: `Lowest zoom the \\`${type}\\` signs are drawn at.`,\n range: ZOOM_RANGE,\n default: SIGN_DEFAULT_MIN_ZOOM,\n }),\n 'sign.priority': (type) => ({\n kind: 'enum',\n description: `What the \\`${type}\\` signs give way to in a symbol collision; unset follows the placement.`,\n options: everyOf<SectionSignPriority>()(['belowMapLabels', 'belowRouteIcons', 'aboveRouteIcons']),\n }),\n 'sign.unit': (type) => ({\n kind: 'enum',\n description: `Unit every \\`${type}\\` sign reads in; unset, each follows its country, or the display units without \\`country\\` sections.`,\n options: everyOf<SectionSignUnit>()(['km/h', 'mph']),\n }),\n};\n\nconst waypointPinNames: Record<WaypointIndexType, string> = {\n start: 'origin pin',\n middle: 'stop pins',\n finish: 'destination pin',\n};\n\nconst waypointPinStyleKnobDefinitions: {\n [LEAF in keyof SVGIconStyleOptions]-?: (pins: string) => KnobDefinition<NonNullable<SVGIconStyleOptions[LEAF]>>;\n} = {\n fillColor: (pins) => ({ kind: 'color', description: `Fill colour of the ${pins}, over the shared pin style.` }),\n outlineColor: (pins) => ({\n kind: 'color',\n description: `Outline colour of the ${pins}, over the shared pin style.`,\n }),\n outlineOpacity: (pins) => ({\n kind: 'number',\n description: `Outline opacity of the ${pins}, over the shared pin style.`,\n range: OPACITY_RANGE,\n }),\n};\n\nconst waypointPinKnobs = (): RoutingKnob[] =>\n ([START_INDEX, MIDDLE_INDEX, FINISH_INDEX] as const).flatMap((role) =>\n (Object.keys(waypointPinStyleKnobDefinitions) as (keyof SVGIconStyleOptions)[]).map(\n (leaf): RoutingKnob => ({\n id: `waypoints.icon.${role}.style.${leaf}`,\n ...waypointPinStyleKnobDefinitions[leaf](waypointPinNames[role]),\n }),\n ),\n );\n\n// Sound only for a leaf of a knob the registry lists for `type`, which is all `buildCatalogue` passes.\nconst sectionKnobId = (type: DrawnSectionType, leaf: SectionKnobLeaf): RoutingKnobId =>\n `sections.${type}.${leaf}` as RoutingKnobId;\n\nconst buildCatalogue = (): readonly RoutingKnob[] => {\n const sectionKnobLeaves = Object.keys(sectionKnobDefinitions) as SectionKnobLeaf[];\n // A knob opens into its own leaf, or into the fields of `icon` and `sign`.\n const leavesOf = (knobName: SectionKnobName): SectionKnobLeaf[] =>\n sectionKnobLeaves.filter((leaf) => leaf === knobName || leaf.startsWith(`${knobName}.`));\n\n return [\n // Each role's pin knobs follow the knobs every pin shares.\n ...withIds(routeKnobDefinitions).flatMap((knob): RoutingKnob[] =>\n knob.id === 'waypoints.icon.style.outlineOpacity' ? [knob, ...waypointPinKnobs()] : [knob],\n ),\n ...drawnSectionTypes.flatMap((type) =>\n sectionEntry(type)\n .knobs.flatMap(leavesOf)\n .map((leaf): RoutingKnob => ({ id: sectionKnobId(type, leaf), ...sectionKnobDefinitions[leaf](type) })),\n ),\n ];\n};\n\n/**\n * Every routing knob with its kind, description, range or options and, where one value holds\n * everywhere, its default: the route-wide knobs first, then each drawn section type's. Static data,\n * readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link RoutingModuleConfig}, at the path its id names. Not\n * knobs: `layers`, `beforeLayerConfig`, the charging stop icons and title, the waypoint images, icon\n * mapping and label title, which take structured, function or MapLibre values, and `events`. A\n * `displayUnits` knob left unset follows the global config.\n *\n * @group Routing\n */\nexport const routingKnobCatalogue: readonly RoutingKnob[] = /* @__PURE__ */ buildCatalogue();\n\n/**\n * Every routing knob id, in catalogue order.\n *\n * @group Routing\n */\nexport const routingKnobIds: readonly RoutingKnobId[] = /* @__PURE__ */ routingKnobCatalogue.map(({ id }) => id);\n","import type { ExpressionSpecification } from 'maplibre-gl';\nimport { MAP_REGULAR_FONT } from '../../shared/layers/commonLayerProps';\n\n// Display property holding how long a route stands still at a stop, ready to render. Shared by the\n// two kinds of pin that can carry one: a waypoint the caller asked to wait at, and a charging stop\n// the routing service planned. Both mean the same thing to a reader — how long you are here — so\n// both use this one property, formatted and styled the same way.\nconst STOP_DURATION = 'stopDuration';\n\n// Secondary text colour: the duration reads as data beside the stop's name, not as part of it.\nconst STOP_DURATION_COLOR = '#727C85';\n\n/**\n * The stop duration as a secondary section of a pin label — muted, and in the regular weight while\n * the rest of the label is bold.\n *\n * @remarks\n * Spread into a `format` expression, after whatever separator the label needs.\n *\n * @ignore\n */\nexport const stopDurationLabelSection: [ExpressionSpecification, Record<string, unknown>] = [\n ['coalesce', ['get', STOP_DURATION], ''],\n { 'text-color': STOP_DURATION_COLOR, 'text-font': ['literal', [MAP_REGULAR_FONT]] },\n];\n\n/**\n * @ignore\n */\nexport const hasStopDuration: ExpressionSpecification = ['has', STOP_DURATION];\n","import type {\n DataDrivenPropertyValueSpecification,\n FormattedSpecification,\n SymbolLayerSpecification,\n} from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { buildCustomIconOffsets, buildIconOffsetExpression } from '../../shared/layers/iconOffset';\nimport { pinLayerBaseSpec } from '../../shared/layers/symbolLayers';\nimport type { ChargingStopLabelConfig, ChargingStopsConfig } from '../types/routeModuleConfig';\nimport { stopDurationLabelSection } from '../util/stopDuration';\nimport { SELECTED_ROUTE_FILTER } from './shared';\n\nconst chargingStopTextField = (\n config: ChargingStopLabelConfig | undefined,\n): DataDrivenPropertyValueSpecification<FormattedSpecification> => {\n if (config?.visible === false) {\n return '';\n }\n\n // The duration shown is the whole time at the stop, styled by the same\n // `stopDurationLabelSection` the waypoint pins use, so both kinds of pin read alike. A label\n // that wants only the charging part can still ask for `chargingDuration`.\n return (\n config?.title ?? [\n 'format',\n ['get', 'title'],\n {},\n '\\n',\n {},\n ['get', 'chargingPower'],\n {},\n ' • ',\n {},\n ...stopDurationLabelSection,\n ]\n );\n};\n\n/**\n * @ignore\n * @see toDisplayChargingStops\n */\nexport const chargingStopSymbol = (\n config: ChargingStopsConfig | undefined,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n // Custom icons are registered under their raw `id` (see RoutingModule), and\n // `toDisplayChargingStops` writes that same raw id into each feature's `iconID`, so the\n // offset expression matches unsuffixed ids — unlike places, which suffixes by instance.\n const iconOffset = buildIconOffsetExpression(buildCustomIconOffsets(config?.icon?.customIcons));\n\n return {\n ...pinLayerBaseSpec,\n filter: SELECTED_ROUTE_FILTER,\n minzoom: 4,\n layout: {\n ...pinLayerBaseSpec.layout,\n 'text-field': chargingStopTextField(config?.label),\n ...(iconOffset && { 'icon-offset': iconOffset }),\n },\n paint: {\n ...pinLayerBaseSpec.paint,\n },\n };\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport {\n getInstructionArrowSize,\n getInstructionLineWidth,\n getInstructionOutlineWidth,\n type RouteLineWidth,\n SELECTED_ROUTE_FILTER,\n} 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 = (lineWidth?: RouteLineWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionOutlineWidth(lineWidth),\n 'line-color': 'grey',\n },\n});\n\n/**\n * @ignore\n */\nexport const instructionLine = (lineWidth?: RouteLineWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...commonProps,\n type: 'line',\n layout: commonLineLayout,\n paint: {\n 'line-width': getInstructionLineWidth(lineWidth),\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 = (lineWidth?: RouteLineWidth): 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': getInstructionArrowSize(lineWidth),\n },\n});\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { selectedRouteLine, selectedRoutePOISymbol } from './shared';\n\n/**\n * @ignore\n */\nexport const routeFerriesLine: LayerSpecTemplate<LineLayerSpecification> = selectedRouteLine({\n 'line-color': '#6dc4ed',\n});\n\nconst ferryTerminalSymbol = selectedRoutePOISymbol('poi-ferry_terminal', 6);\n\n/**\n * @ignore\n */\nexport const routeFerriesSymbol: LayerSpecTemplate<SymbolLayerSpecification> = {\n ...ferryTerminalSymbol,\n // zoom where the map POI naturally appears:\n maxzoom: 16.5,\n layout: {\n ...ferryTerminalSymbol.layout,\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 { hasActiveEventState } from '../../shared/layers/eventState';\nimport { scaledWhen } from '../../shared/layers/expressionTransforms';\nimport { HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR } from '../util/config';\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 type RouteLineWidth,\n SELECTED_ROUTE_FILTER,\n} from './shared';\n\n// A hovered or clicked route widens its outline alone, in the outline colour, so the route keeps\n// its own colour.\nconst outlineWidth = (lineWidth: RouteLineWidth | undefined, highlightFactor: number) =>\n scaledWhen(hasActiveEventState, getLineOutlineWidth(lineWidth), highlightFactor);\n\nconst 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 * @param highlightFactor - How much wider a hovered or clicked route draws its outline, clamped.\n * @ignore\n */\nexport const routeDeselectedOutline = (\n lineWidth?: RouteLineWidth,\n highlightFactor = HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_OUTLINE_COLOR,\n 'line-width': outlineWidth(lineWidth, highlightFactor),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeDeselectedLine = (lineWidth?: RouteLineWidth): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: DESELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': DESELECTED_FOREGROUND_COLOR,\n 'line-width': getLineForegroundWidth(lineWidth),\n },\n});\n\n/**\n * @param highlightFactor - How much wider a hovered or clicked route draws its outline, clamped.\n * @ignore\n */\nexport const routeOutline = (\n lineWidth?: RouteLineWidth,\n outlineColor?: string,\n highlightFactor = HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR,\n): LayerSpecTemplate<LineLayerSpecification> => ({\n ...routeLineBaseTemplate,\n filter: SELECTED_ROUTE_FILTER,\n paint: {\n 'line-color': outlineColor ?? ROUTE_LINE_OUTLINE_COLOR,\n 'line-width': outlineWidth(lineWidth, highlightFactor),\n },\n});\n\n/**\n * @ignore\n */\nexport const routeMainLine = (\n lineWidth?: RouteLineWidth,\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(lineWidth),\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 { selectedRouteLine, selectedRoutePOISymbol } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTollRoadsOutline: LayerSpecTemplate<LineLayerSpecification> = selectedRouteLine(\n { 'line-color': '#BEBFFA' },\n { 'line-cap': 'round' },\n);\n\n/**\n * @ignore\n */\nexport const routeTollRoadsSymbol: LayerSpecTemplate<SymbolLayerSpecification> = selectedRoutePOISymbol(\n 'poi-toll_plaza',\n 4,\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 TrafficIncidentDetailsModule} 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 { DelayMagnitude } from '@tomtom-org/maps-sdk/core';\nimport type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { incidentColorKnobIds } from '../../shared/styleKnobsApplied';\nimport type { StyleKnobSettings } from '../../styling/StyleKnobs';\nimport { severityDashedFilter, severityLineDashColor } from '../../traffic/util/trafficIncidentStyle';\nimport {\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\ntype ConcreteDelayMagnitude = Extract<DelayMagnitude, 'minor' | 'moderate' | 'major'>;\n\n/**\n * The colour of each delay magnitude a route's traffic section is drawn in.\n * @ignore\n */\nexport type TrafficDelayColors = Record<ConcreteDelayMagnitude, string>;\n\n/**\n * The knobs that recolour a route's traffic sections.\n * @ignore\n */\nexport const trafficDelayColorKnobIds = [\n incidentColorKnobIds.minor,\n incidentColorKnobIds.moderate,\n incidentColorKnobIds.major,\n] as const;\n\n/**\n * The delay colours in force: the `traffic.incidents.colors.*` knob where one is set, else the SDK's\n * own. Those equal the incident colours of the light styles, but not the darker ones of the dark\n * styles, which is why the style's own colours are not followed unless a knob sets them.\n * @ignore\n */\nexport const trafficDelayColors = (settings: Readonly<StyleKnobSettings> = {}): TrafficDelayColors => ({\n minor: settings[incidentColorKnobIds.minor] ?? MINOR_DELAY_COLOR,\n moderate: settings[incidentColorKnobIds.moderate] ?? MODERATE_DELAY_COLOR,\n major: settings[incidentColorKnobIds.major] ?? MAJOR_DELAY_COLOR,\n});\n\n/**\n * A feature's value by its `magnitudeOfDelay`: one per magnitude given, `fallback` for any other.\n * @ignore\n */\nexport const matchMagnitudeOfDelay = ({\n minor,\n moderate,\n major,\n indefinite,\n fallback,\n}: Record<ConcreteDelayMagnitude, string> & { indefinite?: string; fallback: string }): ExpressionSpecification => [\n 'match',\n ['get', 'magnitudeOfDelay'],\n 'minor',\n minor,\n 'moderate',\n moderate,\n 'major',\n major,\n ...(indefinite ? ['indefinite', indefinite] : []),\n fallback,\n];\n\n/**\n * @ignore\n */\nexport const magnitudeOfDelayTextColor = matchMagnitudeOfDelay({\n minor: MINOR_DELAY_LABEL_COLOR,\n moderate: MODERATE_DELAY_COLOR,\n major: MAJOR_DELAY_COLOR,\n indefinite: '#666666',\n fallback: UNKNOWN_DELAY_COLOR,\n});\n\n/**\n * @ignore\n */\nexport const routeIncidentsBGLine = (colors: TrafficDelayColors): LayerSpecTemplate<LineLayerSpecification> => ({\n type: 'line',\n layout: { 'line-cap': 'round' },\n paint: { 'line-color': matchMagnitudeOfDelay({ ...colors, fallback: UNKNOWN_DELAY_BG_COLOR }) },\n});\n\n/**\n * @ignore\n */\nexport const routeIncidentsDashedLine: LayerSpecTemplate<LineLayerSpecification> = {\n type: 'line',\n filter: severityDashedFilter,\n layout: { 'line-join': 'round' },\n paint: {\n 'line-color': severityLineDashColor,\n 'line-dasharray': [1.5, 1],\n },\n};\n\n// The icon each selected-route incident that has one is drawn with, read from `iconIDProperty`.\nconst routeIncidentsSymbol = (\n iconIDProperty: 'jamIconID' | 'causeIconID',\n layout: SymbolLayerSpecification['layout'],\n paint: SymbolLayerSpecification['paint'] = {},\n): LayerSpecTemplate<SymbolLayerSpecification> => ({\n filter: ['all', ['has', iconIDProperty], 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 'icon-image': ['get', iconIDProperty],\n ...layout,\n },\n paint,\n});\n\n/**\n * @ignore\n */\nexport const routeIncidentsJamSymbol = routeIncidentsSymbol(\n 'jamIconID',\n {\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 {\n 'text-color': magnitudeOfDelayTextColor,\n 'text-halo-color': '#FFFFFF',\n 'text-halo-width': 1,\n },\n);\n\n/**\n * @ignore\n */\nexport const routeIncidentsCauseSymbol = routeIncidentsSymbol('causeIconID', {\n 'icon-anchor': 'bottom-right',\n // Cause symbols have no label in them.\n});\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { selectedRouteLine } from './shared';\n\n/**\n * @ignore\n */\nexport const routeTunnelsLine: LayerSpecTemplate<LineLayerSpecification> = selectedRouteLine({\n 'line-color': '#000000',\n 'line-opacity': 0.3,\n});\n","import type { LineLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate } from '../../shared';\nimport { ROUTE_LINE_FOREGROUND_COLOR, ROUTE_LINE_OUTLINE_COLOR, selectedRouteLine } from './shared';\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedBackgroundLine: LayerSpecTemplate<LineLayerSpecification> = selectedRouteLine({\n 'line-color': ROUTE_LINE_OUTLINE_COLOR,\n});\n\n/**\n * @ignore\n */\nexport const routeVehicleRestrictedDottedLine: LayerSpecTemplate<LineLayerSpecification> = selectedRouteLine(\n { 'line-color': ROUTE_LINE_FOREGROUND_COLOR },\n { 'line-cap': 'round' },\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 { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './routeMainLineLayers';\nimport { magnitudeOfDelayTextColor, matchMagnitudeOfDelay } from './routeTrafficSectionLayers';\nimport { DESELECTED_SECONDARY_COLOR, instanceImageID, 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 imageID = (baseImageID: string) => instanceImageID(baseImageID, instanceIndex);\n\n return {\n type: 'symbol',\n layout: {\n 'icon-image': [\n 'case',\n SELECTED_ROUTE_FILTER,\n imageID(SELECTED_SUMMARY_POPUP_IMAGE_ID),\n imageID(DESELECTED_SUMMARY_POPUP_IMAGE_ID),\n ],\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 matchMagnitudeOfDelay({\n minor: imageID(TRAFFIC_MINOR_IMAGE_ID),\n moderate: imageID(TRAFFIC_MODERATE_IMAGE_ID),\n major: imageID(TRAFFIC_MAJOR_IMAGE_ID),\n fallback: imageID(TRAFFIC_CLEAR_IMAGE_ID),\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 { ExpressionSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { LayerSpecTemplate, LightDark } from '../../shared';\nimport { clampFactor } from '../../shared/knobUtils';\nimport { MAP_BOLD_FONT } from '../../shared/layers/commonLayerProps';\nimport { hasActiveEventState } from '../../shared/layers/eventState';\nimport { scaledWhen } from '../../shared/layers/expressionTransforms';\nimport { buildCustomIconOffsets, buildIconOffsetExpression } from '../../shared/layers/iconOffset';\nimport {\n labelLayoutProperties,\n labelPaintProperties,\n themeAdaptiveLabelColors,\n titleTextField,\n} from '../../shared/layers/labelProperties';\nimport {\n PIN_TRANSLATE,\n pinIconBaseLayout,\n pinIconBasePaint,\n pinTextBaseLayout,\n pinTextBasePaint,\n TITLE,\n} from '../../shared/layers/symbolLayers';\nimport type { RoutingModuleConfig, WaypointLabelConfig } from '../types/routeModuleConfig';\nimport { STOP_DISPLAY_LABEL } from '../types/waypointDisplayProps';\nimport { WAYPOINT_HIGHLIGHT_DEFAULT_SIZE_FACTOR, WAYPOINT_LABEL_DEFAULT_HALO_WIDTH } from '../util/config';\nimport { hasStopDuration, stopDurationLabelSection } from '../util/stopDuration';\nimport {\n getWaypointIconSize,\n getWaypointIndexTextSize,\n ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE,\n STOP_MARK_TEXT_OFFSET,\n} from './shared';\n\n// `title` is often absent -- a waypoint given as bare coordinates has no name -- so the newline\n// between the two is only emitted when both are actually there. The duration itself is styled by\n// `stopDurationLabelSection`, which the charging-stop pins use too.\nconst waypointLabelTextField: ExpressionSpecification = [\n 'format',\n ['coalesce', ['get', TITLE], ''],\n {},\n ['case', ['all', ['has', TITLE], hasStopDuration], '\\n', ''],\n {},\n ...stopDurationLabelSection,\n];\n\n// TODO: reusable display of pins with indexes, not just waypoints\n/**\n * The waypoint pins, each carrying its stop mark, at the configured size, grown while hovered or\n * clicked.\n * @ignore\n */\nexport const waypointSymbols = (config: RoutingModuleConfig): LayerSpecTemplate<SymbolLayerSpecification> => {\n const highlightFactor = clampFactor(\n config.waypoints?.highlight?.sizeFactor,\n ROUTE_WAYPOINT_HIGHLIGHT_SIZE_FACTOR_RANGE,\n WAYPOINT_HIGHLIGHT_DEFAULT_SIZE_FACTOR,\n );\n // Custom icons keep their raw `id`, which `toDisplayWaypoints` writes into `iconID` as given.\n const iconOffset = buildIconOffsetExpression(buildCustomIconOffsets(config.waypoints?.icon?.customIcons));\n return {\n type: 'symbol',\n paint: {\n ...pinIconBasePaint,\n 'text-color': '#ffffff',\n 'text-translate': PIN_TRANSLATE,\n 'text-translate-anchor': 'viewport',\n },\n layout: {\n ...pinIconBaseLayout,\n 'icon-size': scaledWhen(\n hasActiveEventState,\n getWaypointIconSize(config.waypoints?.size, config.waypoints?.sizeFactor),\n highlightFactor,\n ),\n ...(iconOffset && { 'icon-offset': iconOffset }),\n 'text-field': ['get', STOP_DISPLAY_LABEL],\n 'text-font': [MAP_BOLD_FONT],\n // The mark's offset is in ems, so growing it with the pin keeps it centred in the pin head.\n 'text-size': scaledWhen(\n hasActiveEventState,\n getWaypointIndexTextSize(config.waypoints?.size, config.waypoints?.sizeFactor),\n highlightFactor,\n ),\n 'text-offset': STOP_MARK_TEXT_OFFSET,\n 'text-allow-overlap': true,\n 'symbol-sort-key': ['abs', ['-', ['get', 'index'], 1000]],\n },\n };\n};\n\n/**\n * The waypoint labels as `label` draws them, in the label colours of the map's light/dark theme\n * where it sets none.\n * @ignore\n */\nexport const waypointLabels = (\n lightDark: LightDark,\n label?: WaypointLabelConfig,\n): LayerSpecTemplate<SymbolLayerSpecification> => {\n const { textColor, haloColor } = themeAdaptiveLabelColors(lightDark);\n return {\n type: 'symbol',\n paint: {\n ...pinTextBasePaint,\n 'text-color': textColor,\n 'text-halo-width': WAYPOINT_LABEL_DEFAULT_HALO_WIDTH,\n 'text-halo-color': haloColor,\n ...labelPaintProperties(label),\n },\n layout: {\n ...pinTextBaseLayout,\n 'text-field': label?.visible === false ? '' : titleTextField(label?.title, waypointLabelTextField),\n 'text-anchor': 'top',\n 'text-offset': [0, 0.4],\n ...labelLayoutProperties(label),\n },\n };\n};\n","import type { LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport {\n type HasAdditionalLayersConfig,\n type LayerSpecTemplate,\n type LightDark,\n mapStyleLayerIDs,\n type ToBeAddedLayerSpecTemplate,\n} from '../../shared';\nimport { clampFactor } from '../../shared/knobUtils';\nimport { type BeforeLayerPartOf, partBeforeLayerIDOf } from '../../shared/layers/beforeLayer';\nimport { mergeLayer, suffixNumber } from '../../shared/layers/utils';\nimport { darkerShadeOf } from '../../utils/colorUtils';\nimport type { RouteLayersConfig, RoutingBeforeLayerConfig, RoutingModuleConfig } from '../types/routeModuleConfig';\nimport { HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR } from '../util/config';\nimport { resolveCountryCrossingColors } from '../util/countryCrossingColors';\nimport { chargingStopSymbol } from './chargingStopLayers';\nimport { COUNTRY_CROSSING_LAYER_ID, countryCrossingSymbol } from './countryCrossingLayers';\nimport { INSTRUCTION_ARROW_IMAGE_ID, 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 type TrafficDelayColors,\n trafficDelayColors,\n} from './routeTrafficSectionLayers';\nimport { routeTunnelsLine } from './routeTunnelSectionLayers';\nimport { routeVehicleRestrictedBackgroundLine, routeVehicleRestrictedDottedLine } from './routeVehicleRestrictedLayers';\nimport type { SectionLineRole } from './sectionLayers';\nimport {\n sectionLine,\n sectionLineLayerID,\n sectionSign,\n sectionSignLayerID,\n sectionStartSign,\n sectionStyleAnchor,\n sectionSymbol,\n sectionSymbolLayerID,\n withSectionLineKnobs,\n withSectionSymbolKnobs,\n} from './sectionLayers';\nimport type { BespokeSectionType } from './sectionRegistry';\nimport { generatedSectionTypes, postsSign } from './sectionRegistry';\nimport { ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE, type RouteLineWidth, sectionStartIconPadding } 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: HasAdditionalLayersConfig['additional'],\n layerIDPrefix: string | undefined,\n): HasAdditionalLayersConfig['additional'] => {\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 * 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 * @param lightDark - The map style's light/dark theme, which the waypoint labels and the country\n * crossing plaques follow.\n * @param trafficColors - The colours traffic sections draw each delay magnitude in.\n * @ignore\n */\nexport const buildRoutingLayers = (\n config: RoutingModuleConfig = {},\n layerIDPrefix?: string,\n instanceIndex?: number,\n lightDark: LightDark = 'light',\n trafficColors: TrafficDelayColors = trafficDelayColors(),\n): Required<RouteLayersConfig> => {\n // Each part is anchored through the layers at the bottom of its chains, and every other layer\n // pins to one of those, so moving them moves the part in one piece.\n const partBeforeID = (\n part: BeforeLayerPartOf<RoutingBeforeLayerConfig>,\n defaultBeforeID: string | undefined,\n ): string | undefined => partBeforeLayerIDOf(config.beforeLayerConfig, part, defaultBeforeID);\n const mainLinesBeforeID = partBeforeID('mainLines', mapStyleLayerIDs.lowestLabel);\n const instructionLinesBeforeID = partBeforeID('instructionLines', mapStyleLayerIDs.lowestLabel);\n const routeIconsBeforeID = partBeforeID('icons', undefined);\n\n const configLayers = config.layers;\n const configSectionLayers = configLayers?.sections;\n const routeColor = config.color;\n const outlineColor = routeColor ? darkerShadeOf(routeColor) : undefined;\n const routeLineWidth: RouteLineWidth = { width: config.width, widthFactor: config.widthFactor };\n const highlightFactor = clampFactor(\n config.highlight?.outlineWidthFactor,\n ROUTE_HIGHLIGHT_OUTLINE_WIDTH_FACTOR_RANGE,\n HIGHLIGHT_DEFAULT_OUTLINE_WIDTH_FACTOR,\n );\n const routeInstructionArrow = instructionArrow(routeLineWidth);\n\n const sectionConfigs = config.sections;\n\n const routeWaypointSymbol = mergeLayer(\n {\n ...waypointSymbols(config),\n beforeID: prefixBeforeID('routeSummaryBubbleSymbol', layerIDPrefix),\n },\n configLayers?.waypoints?.routeWaypointSymbol,\n );\n const startIconPadding = sectionStartIconPadding(routeWaypointSymbol.layout?.['icon-anchor']);\n const withStartIconPadding = <T extends LayerSpecTemplate<SymbolLayerSpecification>>(spec: T): T => ({\n ...spec,\n layout: { ...spec.layout, 'icon-padding': startIconPadding },\n });\n\n // The five bespoke sections keep hand-written layers, but they reach the map the same way the\n // generated ones do: the uniform knobs of their own type first, then the caller's raw spec over\n // them. Naming the type once is what keeps a layer, the configuration it reads and the anchor\n // it lands on from drifting apart. `beforeID` defaults to where the draw style puts the type,\n // which every line but a chained one wants.\n const bespokeSectionLine = (\n type: BespokeSectionType,\n spec: LayerSpecTemplate<LineLayerSpecification>,\n options?: { beforeID?: string; role?: SectionLineRole },\n ): ToBeAddedLayerSpecTemplate<LineLayerSpecification> => ({\n ...withSectionLineKnobs(type, spec, sectionConfigs?.[type], routeLineWidth, options?.role),\n beforeID: prefixBeforeID(options?.beforeID ?? sectionStyleAnchor(type, sectionConfigs?.[type]), layerIDPrefix),\n });\n\n const bespokeSectionSymbol = (\n type: BespokeSectionType,\n spec: LayerSpecTemplate<SymbolLayerSpecification>,\n beforeID: string | undefined,\n ): ToBeAddedLayerSpecTemplate<SymbolLayerSpecification> => ({\n ...withSectionSymbolKnobs(type, withStartIconPadding(spec), sectionConfigs?.[type]),\n beforeID: prefixBeforeID(beforeID, layerIDPrefix),\n });\n\n // The generated sections: one registry entry each, rather than a source/layer/config/events\n // quartet per type. Where the line lands follows its draw style, while an icon it asks for\n // ranks with the route's icons either way, the lowest of them: above the map's labels, so it is\n // drawn, and under the section icons the route draws without being asked.\n const generatedSections = Object.fromEntries(\n generatedSectionTypes.map((type) => {\n const sectionConfig = sectionConfigs?.[type];\n const lineLayerID = sectionLineLayerID(type);\n const symbolLayerID = sectionSymbolLayerID(type);\n const advancedConfig = configSectionLayers?.[type] as\n | (Record<string, Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>> &\n HasAdditionalLayersConfig)\n | undefined;\n // A type that posts a sign draws nothing else: a line along the stretch would mark\n // every one that carries a value without ever saying what the value is. Which icons\n // the sign gives way to is a collision matter the module settles once its layers are\n // on the map, not a `beforeID`.\n if (postsSign(type)) {\n const signLayerID = sectionSignLayerID(type);\n return [\n type,\n {\n [signLayerID]: mergeLayer(\n withStartIconPadding(sectionSign(type, sectionConfig)),\n advancedConfig?.[signLayerID],\n ),\n ...prefixBeforeIDs(advancedConfig?.additional, layerIDPrefix),\n },\n ];\n }\n\n const symbolLayer = sectionConfig && sectionSymbol(type, sectionConfig);\n return [\n type,\n {\n [lineLayerID]: mergeLayer(\n {\n ...sectionLine(type, sectionConfig, routeLineWidth),\n beforeID: prefixBeforeID(sectionStyleAnchor(type, sectionConfig), layerIDPrefix),\n },\n advancedConfig?.[lineLayerID],\n ),\n ...(symbolLayer && {\n [symbolLayerID]: mergeLayer(\n { ...symbolLayer, beforeID: prefixBeforeID('routeTunnelSymbol', layerIDPrefix) },\n advancedConfig?.[symbolLayerID],\n ),\n }),\n ...prefixBeforeIDs(advancedConfig?.additional, layerIDPrefix),\n },\n ];\n }),\n );\n\n return {\n mainLines: {\n routeLineArrows: mergeLayer(\n { ...routeLineArrows, beforeID: mainLinesBeforeID },\n configLayers?.mainLines?.routeLineArrows,\n ),\n routeLine: mergeLayer(\n {\n ...routeMainLine(routeLineWidth, routeColor),\n beforeID: prefixBeforeID('routeIncidentBackgroundLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeLine,\n ),\n routeOutline: mergeLayer(\n {\n ...routeOutline(routeLineWidth, outlineColor, highlightFactor),\n beforeID: prefixBeforeID('routeLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeOutline,\n ),\n routeDeselectedLine: mergeLayer(\n { ...routeDeselectedLine(routeLineWidth), beforeID: prefixBeforeID('routeOutline', layerIDPrefix) },\n configLayers?.mainLines?.routeDeselectedLine,\n ),\n routeDeselectedOutline: mergeLayer(\n {\n ...routeDeselectedOutline(routeLineWidth, highlightFactor),\n beforeID: prefixBeforeID('routeDeselectedLine', layerIDPrefix),\n },\n configLayers?.mainLines?.routeDeselectedOutline,\n ),\n ...prefixBeforeIDs(configLayers?.mainLines?.additional, layerIDPrefix),\n },\n waypoints: {\n routeWaypointSymbol,\n routeWaypointLabel: mergeLayer(\n {\n ...waypointLabels(lightDark, config.waypoints?.label),\n beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix),\n },\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 ...generatedSections,\n traffic: {\n routeIncidentJamSymbol: mergeLayer(\n bespokeSectionSymbol('traffic', routeIncidentsJamSymbol, 'routeChargingStopSymbol'),\n configSectionLayers?.traffic?.routeIncidentJamSymbol,\n ),\n routeIncidentCauseSymbol: mergeLayer(\n bespokeSectionSymbol('traffic', routeIncidentsCauseSymbol, 'routeChargingStopSymbol'),\n configSectionLayers?.traffic?.routeIncidentCauseSymbol,\n ),\n routeIncidentBackgroundLine: mergeLayer(\n bespokeSectionLine('traffic', routeIncidentsBGLine(trafficColors), {\n beforeID: 'routeIncidentDashedLine',\n }),\n configSectionLayers?.traffic?.routeIncidentBackgroundLine,\n ),\n routeIncidentDashedLine: mergeLayer(\n bespokeSectionLine('traffic', routeIncidentsDashedLine),\n configSectionLayers?.traffic?.routeIncidentDashedLine,\n ),\n ...prefixBeforeIDs(configSectionLayers?.traffic?.additional, layerIDPrefix),\n },\n ferry: {\n routeFerryLine: mergeLayer(\n bespokeSectionLine('ferry', routeFerriesLine),\n configSectionLayers?.ferry?.routeFerryLine,\n ),\n routeFerrySymbol: mergeLayer(\n bespokeSectionSymbol('ferry', routeFerriesSymbol, 'routeIncidentJamSymbol'),\n configSectionLayers?.ferry?.routeFerrySymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.ferry?.additional, layerIDPrefix),\n },\n tollRoad: {\n routeTollRoadOutline: mergeLayer(\n bespokeSectionLine('tollRoad', routeTollRoadsOutline),\n configSectionLayers?.tollRoad?.routeTollRoadOutline,\n ),\n routeTollRoadSymbol: mergeLayer(\n bespokeSectionSymbol('tollRoad', routeTollRoadsSymbol, 'routeChargingStopSymbol'),\n configSectionLayers?.tollRoad?.routeTollRoadSymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tollRoad?.additional, layerIDPrefix),\n },\n tunnel: {\n routeTunnelLine: mergeLayer(\n bespokeSectionLine('tunnel', routeTunnelsLine),\n configSectionLayers?.tunnel?.routeTunnelLine,\n ),\n // The lowest of the section icons the route draws on its own, yet above the map's\n // labels: a tunnel sign marks the stretch only where it starts.\n routeTunnelSymbol: mergeLayer(\n bespokeSectionSymbol('tunnel', sectionStartSign('tunnel', instanceIndex), 'routeFerrySymbol'),\n configSectionLayers?.tunnel?.routeTunnelSymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.tunnel?.additional, layerIDPrefix),\n },\n vehicleRestricted: {\n // The background is the continuous halo the dots sit on, so it chains to the line\n // above it rather than to the route, and derives its colour as an outline.\n routeVehicleRestrictedBackgroundLine: mergeLayer(\n bespokeSectionLine('vehicleRestricted', routeVehicleRestrictedBackgroundLine, {\n beforeID: 'routeVehicleRestrictedForegroundLine',\n role: 'outline',\n }),\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedBackgroundLine,\n ),\n routeVehicleRestrictedForegroundLine: mergeLayer(\n bespokeSectionLine('vehicleRestricted', routeVehicleRestrictedDottedLine),\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedForegroundLine,\n ),\n // A stretch the vehicle may not use is the one sign the driver has to act on, so it\n // ranks just under the traffic icons, above the ferry and tunnel ones.\n routeVehicleRestrictedSymbol: mergeLayer(\n bespokeSectionSymbol(\n 'vehicleRestricted',\n sectionStartSign('vehicleRestricted', instanceIndex),\n 'routeIncidentJamSymbol',\n ),\n configSectionLayers?.vehicleRestricted?.routeVehicleRestrictedSymbol,\n ),\n ...prefixBeforeIDs(configSectionLayers?.vehicleRestricted?.additional, layerIDPrefix),\n },\n },\n instructionLines: {\n routeInstructionLine: mergeLayer(\n { ...instructionLine(routeLineWidth), beforeID: instructionLinesBeforeID },\n configLayers?.instructionLines?.routeInstructionLine,\n ),\n routeInstructionOutline: mergeLayer(\n {\n ...instructionOutline(routeLineWidth),\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 ...routeInstructionArrow,\n beforeID: prefixBeforeID('routeInstructionLine', layerIDPrefix),\n ...(instanceIndex !== undefined && {\n layout: {\n ...routeInstructionArrow.layout,\n 'icon-image': suffixNumber(INSTRUCTION_ARROW_IMAGE_ID, instanceIndex),\n },\n }),\n },\n configLayers?.instructionArrows?.routeInstructionArrowSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.instructionArrows?.additional, layerIDPrefix),\n },\n summaryBubbles: {\n routeSummaryBubbleSymbol: mergeLayer(\n {\n ...(instanceIndex === undefined\n ? summaryBubbleSymbolPoint\n : buildSummaryBubbleSymbolPoint(instanceIndex)),\n ...(routeIconsBeforeID && { beforeID: routeIconsBeforeID }),\n },\n configLayers?.summaryBubbles?.routeSummaryBubbleSymbol,\n ),\n ...prefixBeforeIDs(configLayers?.summaryBubbles?.additional, layerIDPrefix),\n },\n countryCrossings: {\n [COUNTRY_CROSSING_LAYER_ID]: mergeLayer(\n {\n ...countryCrossingSymbol(\n instanceIndex,\n config.countryCrossings,\n resolveCountryCrossingColors(config.countryCrossings, lightDark),\n ),\n // A crossing draws whatever else is in its place, so it sits under the waypoint\n // pins: a plaque naming a border is never worth the stop it would cover.\n beforeID: prefixBeforeID('routeWaypointSymbol', layerIDPrefix),\n },\n configLayers?.countryCrossings?.[COUNTRY_CROSSING_LAYER_ID],\n ),\n ...prefixBeforeIDs(configLayers?.countryCrossings?.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 type { SVGIconStyleOptions } from '../../shared';\nimport { ICON_PIXEL_RATIO, isDOMImageSupported, svgToImg } from '../../shared/imageUtils';\nimport { parseSvg, pinSvg } from '../../shared/resources';\nimport type { SectionStartSignType } from '../layers/sectionLayers';\nimport type { SpeedLimitSignFace } from '../types/routeSections';\nimport type { WaypointIndexType } from '../types/waypointDisplayProps';\nimport countryCrossingPlaqueSvgRaw from './country-crossing-plaque.svg?raw';\nimport finishSvgRaw from './finish.svg?raw';\nimport instructionArrowSvgRaw from './instruction-line-arrow.svg?raw';\nimport speedLimitPlaqueSvgRaw from './speed-limit-plaque.svg?raw';\nimport startSvgRaw from './start.svg?raw';\nimport summaryMapBubbleSvgRaw from './summary-map-bubble.svg?raw';\nimport trafficSvgRaw from './traffic.svg?raw';\nimport tunnelSignSvgRaw from './tunnel-sign.svg?raw';\n\n// Builds no image where the DOM is missing (SSR, node tests), since building the SVG needs it.\nconst svgImg = (buildSvg: () => SVGElement): HTMLImageElement =>\n isDOMImageSupported() ? svgToImg(buildSvg()) : (undefined as never as HTMLImageElement);\n\n/**\n * @ignore\n */\nexport const instructionArrowIconImg = svgImg(() => parseSvg(instructionArrowSvgRaw));\n\n/**\n * @ignore\n */\nexport const summaryMapBubbleImg = (color: string): HTMLImageElement =>\n svgImg(() => {\n const svg = parseSvg(summaryMapBubbleSvgRaw);\n svg.querySelector('#bubble')?.setAttribute('fill', color);\n svg.querySelector('#pin')?.setAttribute('fill', color);\n return 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: ICON_PIXEL_RATIO,\n stretchX: [\n [20, 45],\n [100, 130],\n ],\n stretchY: [[20, 35]],\n content: [10, 10, 130, 45],\n};\n\n/**\n * Options to stretch the crossing plaque around whatever label it carries.\n * * They are tightly coupled with the SVG original dimensions: the stretchable bands are the middle\n * of the rounded rectangle, so the corners keep their radius at any width.\n * @ignore\n */\nexport const countryCrossingPlaqueImageOptions: Partial<StyleImageMetadata> = {\n pixelRatio: ICON_PIXEL_RATIO,\n stretchX: [[30, 110]],\n stretchY: [[25, 35]],\n content: [10, 10, 130, 50],\n};\n\n/**\n * The plaque a border crossing's label is written on. The country codes are drawn over it as text,\n * so one image serves every crossing.\n * @ignore\n */\nexport const countryCrossingPlaqueImg = (color: string): HTMLImageElement =>\n svgImg(() => {\n const svg = parseSvg(countryCrossingPlaqueSvgRaw);\n svg.querySelector('#plaque')?.setAttribute('fill', color);\n return svg;\n });\n\nconst SIGN_RED = '#D0021B';\n\n// Every round sign the route posts: a face inside a ring whose outer edge sits 1 px inside the 46 px\n// canvas, drawn at twice that so it stays crisp at pixelRatio 2.\nconst roundSignSvg = (face: string, ring: string, ringWidth: number): string =>\n `<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"92\" height=\"92\" viewBox=\"0 0 46 46\"><circle cx=\"23\" cy=\"23\" r=\"${22 - ringWidth / 2}\" fill=\"${face}\" stroke=\"${ring}\" stroke-width=\"${ringWidth}\"/></svg>`;\n\nconst WHITE_DISC_SVG = roundSignSvg('#fff', SIGN_RED, 5);\n\nconst SPEED_LIMIT_SIGN_SVGS: Record<SpeedLimitSignFace, string> = {\n whiteDisc: WHITE_DISC_SVG,\n yellowDisc: roundSignSvg('#FFCE00', SIGN_RED, 5),\n plaque: speedLimitPlaqueSvgRaw,\n};\n\nconst SECTION_START_SIGN_SVGS: Record<SectionStartSignType, string> = {\n tunnel: tunnelSignSvgRaw,\n // Empty, it is the \"closed to all vehicles\" sign. \"No entry\" would read as a one-way street.\n vehicleRestricted: WHITE_DISC_SVG,\n};\n\nconst signImg = (svg: string): HTMLImageElement => svgImg(() => parseSvg(svg));\n\n/**\n * The sign a tunnel or a vehicle-restricted stretch is marked with where it starts.\n * @ignore\n */\nexport const sectionStartSignImg = (type: SectionStartSignType): HTMLImageElement =>\n signImg(SECTION_START_SIGN_SVGS[type]);\n\n/**\n * The speed limit sign faces: the white disc most of Europe posts on, the yellow one the Nordics\n * use, and the upright plaque of the United States. The number is part of none of them — it is\n * drawn over the face as text — so one image serves every limit a route carries.\n * @ignore\n */\nexport const speedLimitSignImg = (face: SpeedLimitSignFace): HTMLImageElement => signImg(SPEED_LIMIT_SIGN_SVGS[face]);\n\n/**\n * @ignore\n */\nexport const trafficImg = (color: string): HTMLImageElement =>\n svgImg(() => {\n const svg = parseSvg(trafficSvgRaw);\n const main = svg.querySelector('#main');\n main?.setAttribute('transform', 'scale(2)');\n main?.setAttribute('fill', color);\n return svg;\n });\n\n// The stop pin is left empty: its number or letter rides over it as text.\nconst waypointGlyphs: Record<WaypointIndexType, string | undefined> = {\n start: startSvgRaw,\n middle: undefined,\n finish: finishSvgRaw,\n};\n\n/**\n * The SDK's pin for a waypoint role, in the given style.\n * @ignore\n */\nexport const waypointPinImg = (indexType: WaypointIndexType, style: SVGIconStyleOptions): HTMLImageElement =>\n svgImg(() => {\n const svg = pinSvg(style);\n const glyph = waypointGlyphs[indexType];\n if (glyph) svg.appendChild(parseSvg(glyph));\n\n return svg;\n });\n","import { type CountrySectionProps, generateId, type Route, type Routes } from '@tomtom-org/maps-sdk/core';\nimport { bearing as bearingBetween } from '@turf/turf';\nimport type { Position } from 'geojson';\nimport type { MapGeoJSONFeature } from 'maplibre-gl';\nimport type {\n CountryCrossingFeature,\n DisplayCountryCrossing,\n DisplayCountryCrossings,\n DisplayRouteProps,\n} from '../types/displayRoutes';\n\n/** The glyph between the two country codes on a crossing label, stating the direction of travel. */\nconst CROSSING_ARROW = '\\u2192';\n\n/**\n * Bearing of the route as it crosses, in degrees clockwise from north.\n *\n * @remarks\n * Taken from the points either side of the seam rather than from the seam forward, so a crossing\n * that lands on a bend reads the direction of travel through it instead of the first step out.\n * Turf reports -180..180; the layer's rotation expects 0..360.\n */\nconst bearingAtCrossing = (coordinates: Position[], index: number): number => {\n const before = coordinates[index - 1] ?? coordinates[index];\n const after = coordinates[index + 1] ?? coordinates[index];\n if (before === after) return 0;\n\n return (bearingBetween(before, after) + 360) % 360;\n};\n\nconst toCrossingFeature = (\n departed: CountrySectionProps,\n entered: CountrySectionProps,\n coordinates: Position[],\n routeProps: DisplayRouteProps,\n): DisplayCountryCrossing => {\n const id = generateId();\n\n return {\n type: 'Feature',\n id,\n geometry: { type: 'Point', coordinates: coordinates[entered.startPointIndex] },\n properties: {\n id,\n fromCountryCode: departed.countryCodeISO2,\n toCountryCode: entered.countryCodeISO2,\n label: `${departed.countryCodeISO2} ${CROSSING_ARROW} ${entered.countryCodeISO2}`,\n bearing: bearingAtCrossing(coordinates, entered.startPointIndex),\n fromSectionStartPointIndex: departed.startPointIndex,\n toSectionStartPointIndex: entered.startPointIndex,\n routeState: routeProps.routeState,\n routeIndex: routeProps.index,\n },\n };\n};\n\n/**\n * The crossings along one route: the boundary between each pair of consecutive country sections.\n *\n * @remarks\n * Country sections partition a route end to end — each one ends where the next begins — so every\n * seam between two of them is a border, and a route has one crossing fewer than it has sections.\n * Two consecutive sections always name different countries; one country over two sections would be\n * a single section.\n */\nconst crossingsOfRoute = (route: Route<DisplayRouteProps>): DisplayCountryCrossing[] => {\n const countries = route.properties.sections.country;\n if (!countries?.length) return [];\n\n // Sorted rather than paired in response order, so a crossing cannot read backwards.\n const ordered = [...countries].sort((left, right) => left.startPointIndex - right.startPointIndex);\n const coordinates = route.geometry.coordinates;\n\n return ordered.slice(1).flatMap((entered, index) => {\n if (!coordinates[entered.startPointIndex]) return [];\n\n return [toCrossingFeature(ordered[index], entered, coordinates, route.properties)];\n });\n};\n\n/**\n * Turns the shown routes into the border crossings along them.\n *\n * @remarks\n * Reads the routes' own `country` sections, so a caller who did not ask for them gets no crossings\n * and the module issues no request of its own.\n * @see countryCrossingLayers\n * @ignore\n */\nexport const toDisplayCountryCrossings = (routes: Routes<DisplayRouteProps>): DisplayCountryCrossings => ({\n type: 'FeatureCollection',\n features: routes.features.flatMap(crossingsOfRoute),\n});\n\n/**\n * Turns a clicked crossing back into the two `country` sections it joins.\n *\n * @remarks\n * MapLibre hands back feature properties as scalars, so the sections cannot ride on the feature and\n * are looked up on the route instead — by `startPointIndex`, which tells apart two sections of the\n * same country on a route that re-enters it.\n * @ignore\n */\nexport const toCountryCrossingEventFeature = (\n feature: MapGeoJSONFeature,\n shownRoutes: Routes<DisplayRouteProps>,\n): CountryCrossingFeature => {\n const properties = feature.properties as CountryCrossingFeature['properties'];\n const countries = shownRoutes.features[properties.routeIndex]?.properties.sections.country ?? [];\n const sectionAt = (startPointIndex: number) =>\n countries.find((country) => country.startPointIndex === startPointIndex);\n\n return {\n type: 'Feature',\n id: feature.id,\n geometry: feature.geometry as CountryCrossingFeature['geometry'],\n properties: {\n ...properties,\n fromSection: sectionAt(properties.fromSectionStartPointIndex),\n toSection: sectionAt(properties.toSectionStartPointIndex),\n },\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 { toPinSpriteImageID } from '../../shared/imageUtils';\nimport type { DisplayRouteProps, DisplayRouteRelatedProps } 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 toPinSpriteImageID('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 DisplayRouteRelatedProps & {\n chargingDuration: string;\n chargingPower: string;\n stopDuration?: string;\n };\n\n/**\n * The charging stops as displayed on the map, carrying the route state and index so a selection\n * change can restyle them in place.\n * @ignore\n */\nexport type 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 // The whole time at this stop, which is the charging plus any wait the caller\n // asked for at the same place. The two are one gap in the response, so the pin\n // shows the total and `chargingDuration` stays available for a label that wants\n // only the charging part.\n const stopDuration = formatDuration(\n leg.summary.stopTimeInSeconds ?? properties.chargingTimeInSeconds,\n config?.displayUnits?.time,\n );\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 ...(stopDuration && { stopDuration }),\n routeState: route.properties.routeState,\n // Carrying the route index is what lets `selectRoute` restyle these\n // rather than regenerate the whole collection.\n routeIndex: route.properties.index,\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 hasJam(sectionProps) ? formatDuration(sectionProps.delayInSeconds) : undefined;\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, TimeDisplayUnits, Waypoint, WaypointLike, Waypoints } from '@tomtom-org/maps-sdk/core';\nimport { formatDuration, generateId, getPosition, poiCategoriesToID } from '@tomtom-org/maps-sdk/core';\nimport type { Point, Position } from 'geojson';\nimport { mappedImageID } from '../../places/utils/iconMapping';\nimport { toPinImageID } from '../../places/utils/toPinImageID';\nimport { featureTitle } from '../../shared/layers/labelProperties';\nimport { instanceImageID } from '../layers/shared';\nimport type { PlanningWaypoint } from '../types/planningWaypoint';\nimport type { WaypointStopNumbering, WaypointsConfig } from '../types/routeModuleConfig';\nimport type { DisplayWaypoint, WaypointDisplayProps, WaypointIndexType } from '../types/waypointDisplayProps';\nimport { FINISH_INDEX, MIDDLE_INDEX, START_INDEX } from '../types/waypointDisplayProps';\nimport { WAYPOINT_DEFAULT_STOP_NUMBERING } from './config';\n\nconst indexTypeFor = (index: number, arrayLength: number): WaypointIndexType =>\n index === 0 ? START_INDEX : index < arrayLength - 1 ? MIDDLE_INDEX : FINISH_INDEX;\n\n/**\n * The label {@link RoutingModule} gives a waypoint when {@link WaypointLabelConfig.title} is not a\n * function: the POI name, else the free-form address, else none.\n *\n * @example\n * ```typescript\n * // \"Home\" at the finish, the default label elsewhere\n * routingModule.applyConfig({\n * waypoints: {\n * label: {\n * title: (waypoint) =>\n * waypoint.properties.indexType === 'finish' ? 'Home' : (buildWaypointTitle(waypoint) ?? ''),\n * },\n * },\n * });\n * ```\n *\n * @group Routing\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\n/**\n * The id of each waypoint role's pin image, before the module instance's suffix.\n * @ignore\n */\nexport const WAYPOINT_IMAGE_IDS: Record<WaypointIndexType, string> = {\n start: 'waypointStart',\n middle: 'waypointStop',\n finish: 'waypointFinish',\n};\n\n/**\n * The image id of a waypoint role's pin, suffixed for the module instance that adds it.\n * @ignore\n */\nexport const getImageIDForWaypoint = (indexType: WaypointIndexType, instanceIndex?: number): string =>\n instanceImageID(WAYPOINT_IMAGE_IDS[indexType], instanceIndex);\n\n// Bijective base 26, as spreadsheet columns count: 1 → A, 26 → Z, 27 → AA.\nconst toLetters = (stopNumber: number): string => {\n let letters = '';\n for (let rest = stopNumber; rest > 0; rest = Math.floor((rest - 1) / 26)) {\n letters = String.fromCodePoint(65 + ((rest - 1) % 26)) + letters;\n }\n return letters;\n};\n\n/**\n * The mark a stop's pin draws inside it under the given numbering, for its 1-based stop number.\n * @ignore\n */\nexport const stopDisplayLabel = (\n stopNumber: number,\n numbering: WaypointStopNumbering = WAYPOINT_DEFAULT_STOP_NUMBERING,\n) => {\n if (numbering === 'none') return undefined;\n\n return numbering === 'letters' ? toLetters(stopNumber) : String(stopNumber);\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 * The given waypoints as features, each with an id — its own, else a generated one — so drawing\n * them again keeps the ids their event states are put by. Placeholders keep their place.\n * @ignore\n */\nexport const toIdentifiedWaypoints = (waypoints: PlanningWaypoint[]): PlanningWaypoint[] =>\n waypoints.map((waypointInput) => {\n if (!waypointInput) return waypointInput;\n\n const waypoint = asWaypoint(waypointInput);\n return { ...waypoint, id: waypoint.id ?? generateId() };\n });\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 timeUnits?: TimeDisplayUnits,\n): Waypoints<WaypointDisplayProps> => ({\n type: 'FeatureCollection',\n // A placeholder draws nothing but keeps its index, since it still holds a place in a planner panel.\n features: waypoints.flatMap((waypointInput, index) => {\n if (!waypointInput) return [];\n\n const waypoint: Waypoint = asWaypoint(waypointInput);\n const indexType = indexTypeFor(index, waypoints.length);\n const id = (waypoint.id as string) ?? generateId();\n const stopDuration = formatDuration(waypoint.properties.pauseDurationSeconds, timeUnits);\n const displayWaypoint: DisplayWaypoint = {\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 ...(stopDuration && { stopDuration }),\n iconID: getImageIDForWaypoint(indexType, instanceIndex),\n // The origin holds index 0, so a middle waypoint's own index is its stop number:\n ...(indexType === MIDDLE_INDEX && { stopDisplayIndex: index }),\n },\n };\n\n const title = featureTitle(displayWaypoint, options?.label?.title, buildWaypointTitle);\n if (title) displayWaypoint.properties.title = title;\n\n const iconID = mappedImageID(displayWaypoint, options?.icon?.mapping, (category) =>\n toPinImageID(poiCategoriesToID[category]),\n );\n if (iconID) {\n displayWaypoint.properties.iconID = iconID;\n } else if (indexType === MIDDLE_INDEX && !options?.icon?.middle?.image?.image) {\n const label = stopDisplayLabel(index, options?.stopNumbering);\n if (label) displayWaypoint.properties.stopDisplayLabel = label;\n }\n return [displayWaypoint];\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 ?? 0) > 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} 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\n// The worst concrete delay along the route.\nconst summaryDelayMagnitude = (route: Route): DelayMagnitude | undefined => {\n const trafficSections = route.properties.sections.traffic ?? [];\n return (['major', 'moderate', 'minor'] as const).find((magnitude) =>\n trafficSections.some((section) => section.magnitudeOfDelay === magnitude),\n );\n};\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 {\n type CountrySectionProps,\n type DisplayUnits,\n type DistanceUnitsType,\n MILE_IN_METERS,\n type SpeedLimitSectionProps,\n TomTomConfig,\n} from '@tomtom-org/maps-sdk/core';\nimport { suffixNumber } from '../../shared/layers/utils';\nimport type { DisplayRouteProps } from '../types/displayRoutes';\nimport type { SectionSignUnit } from '../types/routeModuleConfig';\nimport type { DisplaySpeedLimitSectionProps, SpeedLimitSignFace } from '../types/routeSections';\n\n/** The step a limit is posted in, so a converted number lands on one a sign can show. */\nconst MPH_STEP = 5;\n\n/** The colour a number is printed in where it is not near-black. */\nconst BLUE_NUMERALS = '#0B5FA5';\n\n/** The near-black every other country prints its numbers in. */\nconst DEFAULT_NUMERALS = '#1A2024';\n\n/**\n * How one place posts a speed limit, as far as it differs from the white disc reading km/h in\n * near-black. Every field left out falls back to that.\n */\ntype SignConvention = { face?: SpeedLimitSignFace; unit?: SectionSignUnit; numerals?: string };\n\n// What each country posts, by ISO 3166-1 alpha-3 code. A country missing here posts the default.\nconst SIGN_BY_COUNTRY: Record<string, SignConvention> = {\n // The country a sign stands in decides its unit: a route through Kent shown to an app\n // configured in kilometres still passes signs reading mph.\n GBR: { unit: 'mph' },\n LBR: { unit: 'mph' },\n MMR: { unit: 'mph' },\n USA: { face: 'plaque', unit: 'mph' },\n // The Nordic yellow disc, so a sign there does not read as a Dutch one.\n SWE: { face: 'yellowDisc' },\n FIN: { face: 'yellowDisc' },\n ISL: { face: 'yellowDisc' },\n // Japan posts the same disc, in blue.\n JPN: { numerals: BLUE_NUMERALS },\n};\n\n// With no country to go on, the units the app displays in are the best answer available: a US app\n// gets the plaque it would see on the road, and any other imperial reader gets the disc in mph.\nconst SIGN_BY_DISPLAY_UNITS: Record<DistanceUnitsType, SignConvention> = {\n metric: {},\n imperial_uk: { unit: 'mph' },\n imperial_us: { face: 'plaque', unit: 'mph' },\n};\n\n/**\n * The image each sign face is drawn from, before any instance suffix.\n *\n * @remarks\n * The one list of the faces there are: the module adds an image per entry, the SVG table in\n * `routing/resources` is keyed by the same names, and a section names its face here. Adding a face\n * to {@link SpeedLimitSignFace} fails to compile until all three carry it.\n * @ignore\n */\nexport const SPEED_LIMIT_IMAGE_ID_BY_FACE: Record<SpeedLimitSignFace, string> = {\n whiteDisc: 'routeSpeedLimitWhiteDisc',\n yellowDisc: 'routeSpeedLimitYellowDisc',\n plaque: 'routeSpeedLimitPlaque',\n};\n\n/**\n * The country a point of the route lies in, or `undefined` when the route was not asked for its\n * country sections. They tile a route end to end, so the one holding a point is its country.\n */\nconst countryAt = (pointIndex: number, countrySections?: CountrySectionProps[]): string | undefined =>\n countrySections?.find((country) => pointIndex >= country.startPointIndex && pointIndex <= country.endPointIndex)\n ?.countryCodeISO3;\n\n/** The number a sign shows: converted for its unit, and rounded to the step limits are posted in. */\nconst postedSpeedLimit = (maxSpeedLimitInKmh: number, unit: SectionSignUnit): number =>\n unit === 'mph'\n ? Math.round((maxSpeedLimitInKmh * 1000) / MILE_IN_METERS / MPH_STEP) * MPH_STEP\n : maxSpeedLimitInKmh;\n\n/**\n * Turns each speed limit section into the sign that posts it: the face to draw it on, the colour of\n * its numerals, and the number to print.\n *\n * @remarks\n * A section carries only `maxSpeedLimitInKmh`, so everything that varies comes from the country the\n * stretch runs through — read off the route's own `country` sections — and from the display units\n * where the route carries none. An explicit `unit` on the knob wins over both, for an app posting\n * one unit everywhere.\n * @ignore\n */\nexport const toDisplaySpeedLimitSectionProps =\n (instanceIndex: number, options?: { unit?: SectionSignUnit; displayUnits?: DisplayUnits }) =>\n (\n sectionProps: SpeedLimitSectionProps,\n routeProps?: DisplayRouteProps,\n ): Omit<DisplaySpeedLimitSectionProps, 'routeState' | 'routeIndex'> => {\n const country = countryAt(sectionProps.startPointIndex, routeProps?.sections.country);\n const convention = country\n ? (SIGN_BY_COUNTRY[country] ?? {})\n : SIGN_BY_DISPLAY_UNITS[\n options?.displayUnits?.distance?.type ??\n TomTomConfig.instance.get().displayUnits?.distance?.type ??\n 'metric'\n ];\n const face = convention.face ?? 'whiteDisc';\n const unit = options?.unit ?? convention.unit ?? 'km/h';\n\n return {\n ...sectionProps,\n signImageID: suffixNumber(SPEED_LIMIT_IMAGE_ID_BY_FACE[face], instanceIndex),\n signLabel: String(postedSpeedLimit(sectionProps.maxSpeedLimitInKmh, unit)),\n // The layer offsets the number by face: a plaque prints its own words where a disc is\n // empty, so the number cannot sit in the same place on both.\n signFace: face,\n signNumeralsColor: convention.numerals ?? DEFAULT_NUMERALS,\n };\n };\n","import type {\n DisplayUnits,\n Route,\n Routes,\n SpeedLimitSectionProps,\n Waypoint,\n Waypoints,\n} from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport type { MapGeoJSONFeature, StyleImageMetadata } from 'maplibre-gl';\nimport {\n AbstractDataOwnedMapModule,\n type CombinedEvents,\n GeoJSONSourceWithLayers,\n mapStyleLayerIDs,\n type SVGIconStyleOptions,\n type UserEventsWithStates,\n} from '../shared';\nimport { mapAccentColor, onAccentColorChange } from '../shared/accentColor';\nimport { ICON_PIXEL_RATIO } from '../shared/imageUtils';\nimport { prefixLayerID, suffixNumber } from '../shared/layers/utils';\nimport {\n addLayers,\n addOrUpdateImage,\n existingBeforeLayerID,\n updateLayersAndSource,\n waitUntilMapIsReady,\n} from '../shared/mapUtils';\nimport { addCustomImages, type PaintedPin, paintPin } from '../shared/pinImages';\nimport { withEventState } from '../shared/SourceWithLayers';\nimport { onStyleKnobsApplied } from '../shared/styleKnobsApplied';\nimport type { TomTomMap } from '../TomTomMap';\nimport { COUNTRY_CROSSING_LAYER_ID, COUNTRY_CROSSING_PLAQUE_IMAGE_ID } from './layers/countryCrossingLayers';\nimport { INSTRUCTION_ARROW_IMAGE_ID } from './layers/guidanceLayers';\nimport { DESELECTED_SUMMARY_POPUP_IMAGE_ID, SELECTED_SUMMARY_POPUP_IMAGE_ID } from './layers/routeMainLineLayers';\nimport { trafficDelayColorKnobIds, trafficDelayColors } from './layers/routeTrafficSectionLayers';\nimport { buildRoutingLayers } from './layers/routingLayers';\nimport {\n isSectionVisible,\n SECTION_START_SIGN_IMAGE_IDS,\n type SectionStartSignType,\n sectionSignLayerID,\n signPriority,\n} from './layers/sectionLayers';\nimport {\n drawnSectionTypes,\n type GeneratedSectionType,\n generatedSectionTypes,\n postsSign,\n type SectionSourceKey,\n sectionSourceKey,\n} from './layers/sectionRegistry';\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 countryCrossingPlaqueImageOptions,\n countryCrossingPlaqueImg,\n instructionArrowIconImg,\n sectionStartSignImg,\n speedLimitSignImg,\n summaryBubbleImageOptions,\n summaryMapBubbleImg,\n trafficImg,\n waypointPinImg,\n} from './resources';\nimport type { CountryCrossingFeature, DisplayRouteProps, DisplayRouteSummary } from './types/displayRoutes';\nimport type { DisplayInstruction } from './types/guidance';\nimport type { PlanningWaypoint } from './types/planningWaypoint';\nimport type { RoutingModuleConfig, SectionDisplayConfig } from './types/routeModuleConfig';\nimport type {\n DisplaySpeedLimitSectionProps,\n DisplayTrafficSectionProps,\n RouteSection,\n RouteSections,\n SpeedLimitSignFace,\n} from './types/routeSections';\nimport type { RoutingLayersSpecs, RoutingSourcesWithLayers } from './types/routingSourcesAndLayers';\nimport type { ShowRoutesOptions } from './types/showOptions';\nimport {\n FINISH_INDEX,\n MIDDLE_INDEX,\n START_INDEX,\n type WaypointDisplayProps,\n type WaypointIndexType,\n} from './types/waypointDisplayProps';\nimport { createLayersSpecs, routeDisplayUnits } from './util/config';\nimport { type CountryCrossingColors, resolveCountryCrossingColors } from './util/countryCrossingColors';\nimport { toCountryCrossingEventFeature, toDisplayCountryCrossings } from './util/countryCrossings';\nimport { toDisplayChargingStops } from './util/displayChargingStops';\nimport { toDisplayTrafficSectionProps } from './util/displayTrafficSectionProps';\nimport { getImageIDForWaypoint, toDisplayWaypoints, toIdentifiedWaypoints } 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';\nimport { SPEED_LIMIT_IMAGE_ID_BY_FACE, toDisplaySpeedLimitSectionProps } from './util/speedLimitSigns';\n\n/**\n * What `routing.events.on('shown-features', ...)` receives: whichever of routes or waypoints was\n * just rendered.\n *\n * @group Routing\n */\nexport type RoutingShownFeatures = { routes: Route | Routes } | { waypoints: PlanningWaypoint[] | Waypoints };\n\n/**\n * Event surface of {@link RoutingModule}: the module's own events, plus one named scope per part\n * of a route it draws.\n *\n * @group Routing\n */\nexport type RoutingEvents = CombinedEvents<MapGeoJSONFeature, RoutingModuleConfig, RoutingShownFeatures> & {\n /** One scope per section type generated from the registry, keyed `<type>Sections`. */\n [K in SectionSourceKey<GeneratedSectionType>]: UserEventsWithStates<RouteSection>;\n} & {\n /** The route lines themselves. */\n mainLines: UserEventsWithStates<Route<DisplayRouteProps>>;\n /** Origin, destination and intermediate stops. */\n waypoints: UserEventsWithStates<Waypoint<WaypointDisplayProps>>;\n /** Charging stops along an EV route. */\n chargingStops: UserEventsWithStates<RouteSection>;\n /** The summary bubbles shown against each alternative. */\n summaryBubbles: UserEventsWithStates<DisplayRouteSummary>;\n /** The border crossings along the route, each carrying the two `country` sections it joins. */\n countryCrossings: UserEventsWithStates<CountryCrossingFeature>;\n /** Traffic incidents on the route. */\n incidents: UserEventsWithStates<RouteSection<DisplayTrafficSectionProps>>;\n /** Sections the vehicle is restricted from. */\n vehicleRestricted: UserEventsWithStates<RouteSection>;\n /** Ferry sections. */\n ferries: UserEventsWithStates<RouteSection>;\n /** Toll road sections. */\n tollRoads: UserEventsWithStates<RouteSection>;\n /** Tunnel sections. */\n tunnels: UserEventsWithStates<RouteSection>;\n /** The guidance instruction lines. */\n instructionLines: UserEventsWithStates<DisplayInstruction>;\n};\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 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 * - Waypoint pins per role — origin, numbered or lettered stops, destination — with hover and\n * click events\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.create(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 AbstractDataOwnedMapModule<RoutingSourcesWithLayers, RoutingModuleConfig> {\n private layersSpecs!: RoutingLayersSpecs;\n private layerIDPrefix!: string;\n // What each role's pin was last painted from, so a change that moves none of them repaints none.\n private paintedWaypointPins?: Record<WaypointIndexType, PaintedPin>;\n // The waypoints `showWaypoints` was last given, with their ids fixed, for a waypoint setting to\n // draw them again from.\n private shownWaypoints?: PlanningWaypoint[];\n // The display units the summary bubbles and speed limit signs were last labelled in — the\n // module's, or the global ones — so a change of either re-shows them.\n private appliedDisplayUnits?: DisplayUnits;\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 * **Instances:**\n * `RoutingModule` owns the sources, layers and images it adds, all suffixed per instance. Every\n * call therefore returns a **new, independent** instance, and stacking several of them on one\n * map is a supported thing to do.\n *\n * **Configuration Options:**\n * {@link routingKnobCatalogue} lists every plain-valued display setting; `layers` takes complete\n * MapLibre layer specs.\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.create(map);\n * ```\n *\n * @example\n * With custom configuration:\n * ```typescript\n * const routingModule = await RoutingModule.create(map, {\n * displayUnits: { distance: { type: 'imperial_us' } },\n * waypoints: {\n * entryPoints: 'main-when-available'\n * }\n * });\n * ```\n */\n static async create(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(map, config);\n // A colour the caller chose wins over the accent (`TomTomMap.mapColors`).\n onAccentColorChange(map, () => {\n if (this.config?.color === undefined) this.config = this._applyConfig(this.config);\n });\n onStyleKnobsApplied(map, trafficDelayColorKnobIds, () => this.repaintTrafficSections());\n }\n\n // Updates the traffic section layers alone: they are all a delay colour knob reaches, and a colour\n // picker moves it every frame.\n private repaintTrafficSections(): void {\n const { incidents } = this.buildLayersSpecs(this.config);\n if (isEqual(incidents, this.layersSpecs.incidents)) return;\n\n updateLayersAndSource(\n incidents,\n this.layersSpecs.incidents,\n this.sourcesWithLayers.incidents,\n this.mapLibreMap,\n );\n this.layersSpecs = { ...this.layersSpecs, incidents };\n }\n\n // The colour the routes draw in: the caller's `color`, else the map's accent (if a theme\n // set one), else the SDK's own route blue inside the layer builders.\n private routeColorOf(config: RoutingModuleConfig | undefined): string | undefined {\n return config?.color ?? mapAccentColor(this.tomtomMap);\n }\n\n // Built from the caller's configuration every time, never from what was last painted, so a new\n // accent reaches every layer the route colour tints.\n private buildLayersSpecs(config: RoutingModuleConfig | undefined): RoutingLayersSpecs {\n const layers = buildRoutingLayers(\n { ...config, color: this.routeColorOf(config) },\n this.layerIDPrefix,\n this.instanceIndex,\n this.tomtomMap.styleLightDarkTheme,\n trafficDelayColors(this.tomtomMap._styleKnobSettings),\n );\n return createLayersSpecs(layers, this.layerIDPrefix);\n }\n\n private createSourcesWithLayers(layersSpecs: RoutingLayersSpecs): RoutingSourcesWithLayers {\n const sourcePrefix = suffixNumber('routes', this.instanceIndex);\n // One source per generated section, from the same table the layers come from.\n const generatedSectionSources = Object.fromEntries(\n generatedSectionTypes.map((type) => {\n const key = sectionSourceKey(type);\n return [\n key,\n new GeoJSONSourceWithLayers(this.mapLibreMap, `${sourcePrefix}-${key}`, layersSpecs[key], false),\n ];\n }),\n ) as Pick<RoutingSourcesWithLayers, SectionSourceKey<GeneratedSectionType>>;\n return {\n ...generatedSectionSources,\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 countryCrossings: new GeoJSONSourceWithLayers(\n this.mapLibreMap,\n `${sourcePrefix}-countryCrossings`,\n layersSpecs.countryCrossings,\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 = this.buildLayersSpecs(config);\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 options: Partial<StyleImageMetadata> = { pixelRatio: ICON_PIXEL_RATIO };\n\n // Generate instance-specific image IDs to support multiple RoutingModule instances\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(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 // One image per sign face, never per limit: the number rides over the face as text, so a\n // route through any country is served by the faces this loop adds once.\n for (const [face, imageID] of Object.entries(SPEED_LIMIT_IMAGE_ID_BY_FACE)) {\n this.addImageIfNotExisting(\n suffixNumber(imageID, this.instanceIndex),\n speedLimitSignImg(face as SpeedLimitSignFace),\n options,\n );\n }\n for (const [type, imageID] of Object.entries(SECTION_START_SIGN_IMAGE_IDS)) {\n this.addImageIfNotExisting(\n suffixNumber(imageID, this.instanceIndex),\n sectionStartSignImg(type as SectionStartSignType),\n options,\n );\n }\n // Forgotten rather than reused: the layers are new, and the theme behind the colours may\n // have changed with the style that replaced them.\n this.appliedCrossingColors = undefined;\n this.addImageIfNotExisting(\n suffixNumber(COUNTRY_CROSSING_PLAQUE_IMAGE_ID, this.instanceIndex),\n countryCrossingPlaqueImg(this.crossingColors(this.config).plaque),\n { ...options, ...countryCrossingPlaqueImageOptions },\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 return routingSourcesWithLayers;\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config?: RoutingModuleConfig) {\n const displayUnits = routeDisplayUnits(config);\n const displayUnitsChanged = !isEqual(this.appliedDisplayUnits, displayUnits);\n this.appliedDisplayUnits = displayUnits;\n this.addConfiguredImages(config);\n\n // The constructor's application lands on layers just built from the same config; every later\n // one updates them.\n if (!this._initializing) {\n // replace existing configuration with new one\n const newLayersSpecs = this.buildLayersSpecs(config);\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; the route's layer chains cross sources (the route line\n // pins to the traffic line, the waypoints to the summary bubbles), so moving a part\n // restacks them all\n const listOfSources = Object.values(this.sourcesWithLayers) as GeoJSONSourceWithLayers[];\n addLayers(\n listOfSources.flatMap((source) => source._layerSpecs),\n this.mapLibreMap,\n !isEqual(this.config?.beforeLayerConfig, config?.beforeLayerConfig),\n );\n this.reshowSpeedLimitSigns(config, displayUnitsChanged);\n this.applySignPriority(config);\n this.layersSpecs = newLayersSpecs;\n }\n\n this.reshowWaypoints(config, displayUnitsChanged);\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 = config?.summaryBubbles?.visible !== false;\n const visibilityChanged = !isEqual(this.config?.summaryBubbles?.visible, config?.summaryBubbles?.visible);\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, displayUnits),\n );\n }\n\n this.applyCountryCrossingConfig(config);\n // Last, over every part re-shown above and every layer the new config added hidden.\n this.applyVisibility(config);\n\n return config;\n }\n\n /**\n * Adds the images the configuration brings: each waypoint role's pin, and the custom icons of\n * the waypoints and charging stops.\n *\n * @remarks\n * Runs on every config change and after every style change, which drops the images. A pin\n * follows the route colour under the configured styles, so a new accent repaints it too;\n * {@link paintPin} skips the ones nothing moved.\n */\n private addConfiguredImages(config: RoutingModuleConfig | undefined): void {\n const icon = config?.waypoints?.icon;\n const baseStyle: SVGIconStyleOptions = { fillColor: this.routeColorOf(config), ...icon?.style };\n const paint = (indexType: WaypointIndexType) =>\n paintPin(\n this.mapLibreMap,\n getImageIDForWaypoint(indexType, this.instanceIndex),\n icon?.[indexType],\n baseStyle,\n (style) => waypointPinImg(indexType, style),\n this.paintedWaypointPins?.[indexType],\n );\n this.paintedWaypointPins = {\n start: paint(START_INDEX),\n middle: paint(MIDDLE_INDEX),\n finish: paint(FINISH_INDEX),\n };\n addCustomImages(this.mapLibreMap, icon?.customIcons);\n addCustomImages(this.mapLibreMap, config?.chargingStops?.icon?.customIcons);\n }\n\n /**\n * Draws the shown waypoints again when a waypoint setting or the time unit their stop durations\n * read in changed: the icon, stop mark and title each waypoint draws are on the feature.\n */\n private reshowWaypoints(config: RoutingModuleConfig | undefined, displayUnitsChanged: boolean): void {\n if (!this.shownWaypoints || this._initializing) return;\n if (!displayUnitsChanged && isEqual(this.config?.waypoints, config?.waypoints)) return;\n\n // A setting changes how a pin draws, not whether it is hovered or clicked.\n const waypoints = this.sourcesWithLayers.waypoints;\n const displayWaypoints = this.displayWaypointsOf(this.shownWaypoints, config);\n waypoints.show({\n ...displayWaypoints,\n features: displayWaypoints.features.map((waypoint) =>\n withEventState(waypoint, waypoints.findById(waypoint.id)?.feature.properties.eventState),\n ),\n });\n }\n\n private displayWaypointsOf(waypoints: PlanningWaypoint[], config: RoutingModuleConfig | undefined) {\n return toDisplayWaypoints(waypoints, config?.waypoints, this.instanceIndex, routeDisplayUnits(config)?.time);\n }\n\n /**\n * Shows the speed limit sections, each carrying the sign that posts it.\n *\n * @remarks\n * `speedLimit` is the one type drawn from its own number rather than its extent, so the number,\n * the face and the numeral colour are derived here and ride on the feature. That makes the unit\n * in force an input to the data rather than to the paint, which is why a change to it re-runs\n * this instead of restyling the layer in place.\n */\n private showSpeedLimitSections(\n displayRoutes: Routes<DisplayRouteProps>,\n config: RoutingModuleConfig | undefined,\n ): void {\n this.sourcesWithLayers.speedLimitSections.show(\n toDisplayRouteSections<SpeedLimitSectionProps, DisplaySpeedLimitSectionProps>(\n displayRoutes,\n 'speedLimit',\n toDisplaySpeedLimitSectionProps(this.instanceIndex, {\n unit: config?.sections?.speedLimit?.sign?.unit,\n displayUnits: routeDisplayUnits(config),\n }),\n ),\n );\n }\n\n /** The colours the crossings draw with, which follow the map's theme unless configured. */\n private crossingColors(config: RoutingModuleConfig | undefined): CountryCrossingColors {\n return resolveCountryCrossingColors(config?.countryCrossings, this.tomtomMap.styleLightDarkTheme);\n }\n\n /**\n * What {@link applyCountryCrossingConfig} last painted the crossings with.\n *\n * @remarks\n * Tracked here rather than re-derived from `this.config`, because the theme is an input the\n * config cannot see: a style change moves the resolved colours while the config says the same\n * thing. Cleared when the sources are rebuilt, so a restored style repaints from scratch.\n */\n private appliedCrossingColors: CountryCrossingColors | undefined;\n\n /**\n * Brings the border crossings in line with a changed configuration.\n *\n * @remarks\n * The source is gated on `visible`, as the summary bubbles' is, so switching them back on\n * re-derives the features: a layer spec cannot put back data the source does not hold.\n *\n * Both colours are applied here rather than left to the layer spec: the plaque is an image, so\n * a new colour has to replace it, and the label's colour follows the map theme, which the spec\n * that built the layer cannot know about after a style change.\n */\n private applyCountryCrossingConfig(config: RoutingModuleConfig | undefined): void {\n const crossingsVisible = config?.countryCrossings?.visible !== false;\n const visibilityChanged = !isEqual(this.config?.countryCrossings?.visible, config?.countryCrossings?.visible);\n\n if (!crossingsVisible && visibilityChanged) {\n this.sourcesWithLayers.countryCrossings.clear();\n } else if (crossingsVisible && visibilityChanged) {\n this.sourcesWithLayers.countryCrossings.show(\n toDisplayCountryCrossings(this.sourcesWithLayers.mainLines.shownFeatures),\n );\n }\n\n const colors = this.crossingColors(config);\n if (isEqual(this.appliedCrossingColors, colors)) return;\n\n this.appliedCrossingColors = colors;\n addOrUpdateImage(\n 'add-or-update',\n suffixNumber(COUNTRY_CROSSING_PLAQUE_IMAGE_ID, this.instanceIndex),\n countryCrossingPlaqueImg(colors.plaque),\n this.mapLibreMap,\n countryCrossingPlaqueImageOptions,\n );\n\n const crossingLayerID = prefixLayerID(COUNTRY_CROSSING_LAYER_ID, this.layerIDPrefix);\n if (this.mapLibreMap.getLayer(crossingLayerID)) {\n this.mapLibreMap.setPaintProperty(crossingLayerID, 'text-color', colors.text, { validate: false });\n }\n }\n\n /**\n * Re-derives the posted signs when the unit they read in changed.\n *\n * @remarks\n * No paint property can restate a number, so `sign.unit` and `displayUnits` are the two knobs\n * that have to reach the features rather than the layers — the summary bubbles are re-shown for\n * the same reason. Runs before the visibility pass, so the re-shown source goes through it.\n */\n private reshowSpeedLimitSigns(config: RoutingModuleConfig | undefined, displayUnitsChanged: boolean): void {\n const signUnitChanged = !isEqual(\n this.config?.sections?.speedLimit?.sign?.unit,\n config?.sections?.speedLimit?.sign?.unit,\n );\n if (!signUnitChanged && !displayUnitsChanged) return;\n\n if (!this.sourcesWithLayers.speedLimitSections.shownFeatures.features.length) return;\n\n this.showSpeedLimitSections(this.sourcesWithLayers.mainLines.shownFeatures, config);\n }\n\n /**\n * Draws each part while its source holds features and the module is visible, and a section type\n * only while its `sections.<type>.visible` is on as well.\n *\n * @remarks\n * \"Has features\" alone is not enough for sections. Almost every route has urban stretches, a\n * tunnel or a country crossing somewhere along it, so it would draw all fifteen types the moment\n * a route is shown, whatever the caller asked for.\n *\n * This runs right after anything that shows features and after any config change, in the same\n * tick, so nothing renders in between.\n * @ignore\n */\n protected applyVisibility(config: RoutingModuleConfig | undefined): void {\n const visible = config?.visible !== false;\n for (const source of Object.values(this.sourcesWithLayers)) {\n source.setLayersVisible(visible && !!source.shownFeatures.features.length);\n }\n for (const type of drawnSectionTypes) {\n if (isSectionVisible(type, config?.sections?.[type])) continue;\n\n this.sourcesWithLayers[sectionSourceKey(type)].setLayersVisible(false);\n }\n }\n\n /** The id of the layer posting this type's signs, for a type that posts them. */\n private signLayerID(type: GeneratedSectionType): string | undefined {\n if (!postsSign(type)) return undefined;\n\n return prefixLayerID(sectionSignLayerID(type), this.layerIDPrefix);\n }\n\n /**\n * Settles which icons a section's signs give way to.\n *\n * @remarks\n * MapLibre resolves symbol collisions from the topmost layer down, so this is a matter of where\n * the sign layer sits — and it cannot be a `beforeID`, because the layers a sign yields to are\n * added when the features that need them are shown: a route with no waypoints shown has no\n * waypoint layer to anchor to, and an anchor that is not on the map costs the sign layer\n * itself. So the stacking is settled here instead, after each show and each config change.\n */\n private applySignPriority(config: RoutingModuleConfig | undefined): void {\n for (const type of generatedSectionTypes) {\n const signLayerID = this.signLayerID(type);\n if (!signLayerID || !this.mapLibreMap.getLayer(signLayerID)) continue;\n\n const sectionConfig: SectionDisplayConfig | undefined = config?.sections?.[type];\n this.mapLibreMap.moveLayer(signLayerID, this.signAnchorLayerID(signLayerID, sectionConfig));\n }\n }\n\n /**\n * The layer a section's signs are moved under, for what they are configured to give way to.\n *\n * @remarks\n * `aboveRouteIcons` anchors to nothing, which puts the layer on top, and `belowMapLabels` to\n * the base map's lowest label. `belowRouteIcons` goes under the lowest of the route's icons,\n * wherever `beforeLayerConfig` put them. The arrows drawn along the lines are not icons, even\n * when `beforeLayerConfig` lifts the lines above the labels.\n */\n private signAnchorLayerID(signLayerID: string, config?: SectionDisplayConfig): string | undefined {\n const priority = signPriority(config);\n if (priority === 'aboveRouteIcons') return undefined;\n if (priority === 'belowMapLabels') return mapStyleLayerIDs.lowestLabel;\n\n const lineArrowLayerIDs = new Set(\n ['routeLineArrows', 'routeInstructionArrowSymbol'].map((layerID) =>\n prefixLayerID(layerID, this.layerIDPrefix),\n ),\n );\n return this.mapLibreMap\n .getStyle()\n .layers.find(\n (layer) =>\n layer.type === 'symbol' &&\n layer.id !== signLayerID &&\n !lineArrowLayerIDs.has(layer.id) &&\n (!this.layerIDPrefix || layer.id.startsWith(this.layerIDPrefix)),\n )?.id;\n }\n\n /** @ignore */\n protected discardShownData(): void {\n // `restoreDataAndConfigImpl` reads the shown features off the sources themselves, and a clean\n // switch rebuilds those empty; the waypoints are the one input kept to draw again.\n this.shownWaypoints = undefined;\n }\n\n /**\n * @ignore\n */\n protected restoreDataAndConfigImpl() {\n // Collected by key, not closed over: `initSourcesWithLayers` replaces every source before the\n // re-show below. Each source shows its own feature type, and TypeScript cannot keep a key\n // correlated with that type through an index access, so the payload stays unconstrained here\n // and `show` is what actually checks it.\n const previouslyShown = Object.fromEntries(\n Object.entries(this.sourcesWithLayers).map(([key, sourceWithLayers]) => [\n key,\n sourceWithLayers.shownFeatures,\n ]),\n ) as Record<keyof RoutingSourcesWithLayers, never>;\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 this.applyVisibility(this.config);\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.\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 charged-road (`tollRoad`) 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 // `tollRoad` is the section type that answers \"does this stretch cost money to drive\" — it\n // covers per-use tolls, vignette-only motorways and urban charge zones alike, where `toll`\n // covers only the first. It is also the type this overlay has always been named for. The\n // symbol still uses the toll-plaza icon: the sprite offers no generic road-charge icon, and\n // picking per-scheme icons is the job of the follow-up that gives `toll` and `tollVignette`\n // their own treatment.\n this.sourcesWithLayers.tollRoads.show(toDisplayRouteSections(displayRoutes, 'tollRoad'));\n for (const type of generatedSectionTypes) {\n if (postsSign(type)) continue;\n\n this.sourcesWithLayers[sectionSourceKey(type)].show(toDisplayRouteSections(displayRoutes, type));\n }\n this.showSpeedLimitSections(displayRoutes, this.config);\n this.applySignPriority(this.config);\n this.sourcesWithLayers.instructionLines.show(toDisplayInstructions(displayRoutes));\n this.sourcesWithLayers.instructionArrows.show(toDisplayInstructionArrows(displayRoutes));\n if (this.config?.countryCrossings?.visible !== false) {\n this.sourcesWithLayers.countryCrossings.show(toDisplayCountryCrossings(displayRoutes));\n } else {\n this.sourcesWithLayers.countryCrossings.clear();\n }\n\n if (this.config?.summaryBubbles?.visible !== false) {\n this.sourcesWithLayers.summaryBubbles.show(\n toDisplayRouteSummaries(displayRoutes, routeDisplayUnits(this.config)),\n );\n } else {\n this.sourcesWithLayers.summaryBubbles.clear();\n }\n this.applyVisibility(this.config);\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 // Charging stops now carry their route index, so a selection change restyles them like\n // every other route-related source instead of regenerating the whole collection.\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.chargingStops);\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 for (const type of generatedSectionTypes) {\n showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers[sectionSourceKey(type)]);\n }\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 showFeaturesWithRouteSelection(updatedRoutes, this.sourcesWithLayers.countryCrossings);\n this.applyVisibility(this.config);\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.\n *\n * Every location the route is calculated through belongs here, so that its pin carries its\n * role and stop mark and the route's events reach it. A place the user is still choosing\n * among is a {@link PlacesModule} marker: clear it there once it joins the route. Each pin is\n * drawn as {@link RoutingModuleConfig.waypoints} sets, the per-role pins and an icon mapped\n * from the waypoint's data included.\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 shownWaypoints = toIdentifiedWaypoints(Array.isArray(waypoints) ? waypoints : waypoints.features);\n const displayWaypoints = this.displayWaypointsOf(shownWaypoints, this.config);\n await this.waitUntilModuleReady();\n this.shownWaypoints = shownWaypoints;\n this.sourcesWithLayers.waypoints.show(displayWaypoints);\n this.applyVisibility(this.config);\n // The pins are new layers to give way to, so where the signs sit is settled again.\n this.applySignPriority(this.config);\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.shownWaypoints = undefined;\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`: Sections that cost money to drive — the `tollRoad` section type\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 * - `countryCrossings`: Border crossings along the route\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 ...(Object.fromEntries(\n generatedSectionTypes.map((type) => [\n sectionSourceKey(type),\n this.sourcesWithLayers[sectionSourceKey(type)].shownFeatures,\n ]),\n ) as Record<SectionSourceKey<GeneratedSectionType>, RouteSections | undefined>),\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 countryCrossings: this.sourcesWithLayers.countryCrossings.shownFeatures,\n };\n }\n\n /** @ignore */\n protected eventStateSources(): (keyof RoutingSourcesWithLayers)[] {\n return ['mainLines'];\n }\n\n /**\n * Unified events interface for the routing module.\n *\n * `events` itself covers every route feature and the module's lifecycle events; the named\n * scopes below cover one part of a route each, and every one of them can be narrowed further\n * with `where()`.\n *\n * ```typescript\n * routing.events.mainLines.on('click', (route) => { ... });\n * routing.events.waypoints.on('hover', (waypoint) => { ... });\n *\n * // Only the long tunnels, with their own hover cursor:\n * routing.events.tunnels\n * .where((section) => section.properties.lengthInMeters > 500, { cursorOnHover: 'help' })\n * .on('click', showTunnel);\n *\n * const unsub = routing.events.on('config-change', (config) => { ... });\n * routing.events.on('shown-features', (features) => {\n * if ('routes' in features) { ... }\n * });\n * unsub();\n * ```\n */\n get events(): RoutingEvents {\n // Every generated section gets a scoped events surface for free, and joins the module-wide\n // one alongside the bespoke sections — that is the point of registering them from one table.\n const generatedSectionScopes = Object.fromEntries(\n generatedSectionTypes.map((type) => [\n sectionSourceKey(type),\n this.userEventsWithStates<RouteSection>([sectionSourceKey(type)]),\n ]),\n ) as Record<SectionSourceKey<GeneratedSectionType>, UserEventsWithStates<RouteSection>>;\n\n // `instructionArrows` is deliberately absent: the arrows are a decoration drawn along the\n // instruction lines, and a caller clicking one means to click the instruction.\n return this.buildEvents(\n this.moduleEventsWithShown<MapGeoJSONFeature, RoutingShownFeatures>(\n [\n 'mainLines',\n 'waypoints',\n 'chargingStops',\n 'summaryBubbles',\n 'countryCrossings',\n 'incidents',\n 'vehicleRestricted',\n 'ferries',\n 'tollRoads',\n 'tunnels',\n 'instructionLines',\n ...generatedSectionTypes.map(sectionSourceKey),\n ],\n this.shownFeaturesHandlers,\n ),\n {\n ...generatedSectionScopes,\n mainLines: this.userEventsWithStates<Route<DisplayRouteProps>>(['mainLines']),\n waypoints: this.userEventsWithStates<Waypoint<WaypointDisplayProps>>(['waypoints']),\n chargingStops: this.userEventsWithStates<RouteSection>(['chargingStops']),\n summaryBubbles: this.userEventsWithStates<DisplayRouteSummary>(['summaryBubbles']),\n countryCrossings: this.userEventsWithStates<CountryCrossingFeature>(['countryCrossings'], {\n mapping: (feature) =>\n toCountryCrossingEventFeature(feature, this.sourcesWithLayers.mainLines.shownFeatures),\n }),\n incidents: this.userEventsWithStates<RouteSection<DisplayTrafficSectionProps>>(['incidents']),\n vehicleRestricted: this.userEventsWithStates<RouteSection>(['vehicleRestricted']),\n ferries: this.userEventsWithStates<RouteSection>(['ferries']),\n tollRoads: this.userEventsWithStates<RouteSection>(['tollRoads']),\n tunnels: this.userEventsWithStates<RouteSection>(['tunnels']),\n instructionLines: this.userEventsWithStates<DisplayInstruction>(['instructionLines']),\n },\n );\n }\n\n /**\n * Returns the map style layer under which route lines are rendered, or undefined when they are\n * on top of the stack.\n * * Useful if you want to render extra layers just above the route ones but not on top of everything else.\n * * Follows `beforeLayerConfig`, and might differ depending on the loaded style/version.\n */\n getLayerToRenderLinesUnder(): string | undefined {\n const arrowsLayerID = prefixLayerID('routeLineArrows', this.layerIDPrefix);\n return existingBeforeLayerID(\n this.mapLibreMap,\n this.layersSpecs.mainLines.find((spec) => spec.id === arrowsLayerID)?.beforeID,\n );\n }\n}\n","import type { KnobEntry, KnobValueOf } from '../shared';\nimport { FACTOR_RANGE } from '../shared/knobUtils';\nimport { type BaseMapColorName, baseMapColorKnobs, mapColorDefinitions } from './mapColorCatalogue';\nimport type {\n StylingFoundationsKnob,\n StylingFoundationsKnobAppliesTo,\n StylingFoundationsKnobKind,\n StylingFoundationsKnobRange,\n} from './types/stylingTypes';\n\n// A definition whose kind the catalogue's types keep, so they can tell a colour knob from a toggle.\ntype KnobDefinitionOf<KIND extends StylingFoundationsKnobKind> = Omit<KnobEntry<string, KIND>, 'id' | 'default'> & {\n range?: StylingFoundationsKnobRange;\n appliesTo: StylingFoundationsKnobAppliesTo;\n};\n\ntype StylingFoundationsKnobDefinition = {\n [KIND in StylingFoundationsKnobKind]: KnobDefinitionOf<KIND>;\n}[StylingFoundationsKnobKind];\n\nconst factor = (description: string): KnobDefinitionOf<'factor'> => ({\n kind: 'factor',\n description,\n range: FACTOR_RANGE,\n appliesTo: 'any-style',\n});\n\nconst mapColor = (color: BaseMapColorName): KnobDefinitionOf<'color'> => ({\n kind: 'color',\n description: mapColorDefinitions[color].description,\n appliesTo: 'tomtom-styles',\n});\n\n/**\n * The styling knob catalogue: the look of the whole map — sizes across every part of the style, the\n * ten map colours, the projection and the sky. How each one reaches the style is in\n * `styleKnobDefinitions`, under the same id.\n *\n * Ids are public API — additive only. Ranges follow the validated grammar of TomTom's style editor (factors\n * 0.5–1.5 in 0.1 steps) so a value that looks right in the designer's tool looks right here.\n * @ignore\n */\nexport const knobDefinitions = /* @__PURE__ */ (() =>\n ({\n // Sizing — transforms over whatever the style does, so they work on any style.\n 'labels.sizeFactor': factor('Size of every base-map text label (places, roads, POIs, house numbers).'),\n 'symbols.sizeFactor': factor('Size of every base-map icon (POI icons, route shields, road arrows).'),\n 'roads.widthFactor': factor('Width of all road lines, keeping the hierarchy motorway → path.'),\n\n // Map colours — the themes' ten Foundations colours in their own order, each carrying the theme's\n // summary of what it reaches. `setMapColors` is the typed front door to these knobs.\n ...baseMapColorKnobs(mapColor),\n 'colors.accent': {\n kind: 'color',\n description:\n 'The colour for what is drawn on top of the map — routes, geometries, places. Not a base-map ' +\n 'layer: the modules that draw their own layers default to it when a theme sets it.',\n appliesTo: 'any-style',\n },\n\n // View — projection and atmosphere. Map-level MapLibre state the SDK owns here so it survives\n // style switches (MapLibre resets both on every `setStyle`). 3D terrain needs the elevation\n // style part loaded first, which a synchronous knob cannot do: it is `TerrainModule`'s.\n 'view.projection': {\n kind: 'enum',\n description:\n 'How the map is drawn: flat Mercator or a globe. The globe flattens to Mercator between zoom 11 and 12 by design.',\n options: ['mercator', 'globe'],\n appliesTo: 'any-style',\n },\n 'view.sky': {\n kind: 'toggle',\n description:\n 'A sky with atmosphere behind the globe and above a tilted flat map. Without it MapLibre draws a fully transparent sky, so a globe arrives with no atmosphere.',\n appliesTo: 'any-style',\n },\n 'view.skyColor': {\n kind: 'color',\n description:\n 'Colour of the sky (and of the atmosphere halo around the globe). Defaults to a daylit blue on a light style and a night blue on a dark one.',\n appliesTo: 'any-style',\n },\n 'view.horizonColor': {\n kind: 'color',\n description:\n 'Colour of the sky at the horizon. Defaults with the style theme, so a dark map gets a dim horizon rather than a white one.',\n appliesTo: 'any-style',\n },\n 'view.spaceColor': {\n kind: 'color',\n description:\n 'Colour behind the map, which a globe leaves visible around the planet. The map canvas is transparent, so without this the page background shows through. Defaults with the style theme: white on a light style, near-black on a dark one.',\n appliesTo: 'any-style',\n },\n }) as const satisfies Record<string, StylingFoundationsKnobDefinition>)();\n\n/**\n * The id of a styling knob — see {@link stylingFoundationsKnobCatalogue} for what each one does.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsKnobId = keyof typeof knobDefinitions;\n\n/**\n * Every styling knob id, in catalogue order.\n *\n * @group Map Styling\n */\nexport const stylingFoundationsKnobIds = /* @__PURE__ */ Object.keys(knobDefinitions) as StylingFoundationsKnobId[];\n\n/**\n * Every styling knob with its kind, description, range or options and the styles it works on, in\n * catalogue order. Static data: a UI or a coding agent reads it with no map loaded, and\n * {@link getKnob} gives the value in force on a live one.\n *\n * @group Map Styling\n */\nexport const stylingFoundationsKnobCatalogue: readonly StylingFoundationsKnob[] =\n /* @__PURE__ */ stylingFoundationsKnobIds.map((id): StylingFoundationsKnob => ({ id, ...knobDefinitions[id] }));\n\n/**\n * The value type a given styling knob takes.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsKnobValueOf<ID extends StylingFoundationsKnobId> = KnobValueOf<\n (typeof knobDefinitions)[ID]['kind']\n>;\n","import { isEqual } from 'lodash-es';\nimport type {\n AllLayoutProperties,\n AllPaintProperties,\n FilterSpecification,\n LayerSpecification,\n Map as MapLibreMap,\n StyleSetterOptions,\n} from 'maplibre-gl';\nimport type { LayerFilterComposer } from '../shared/layers/layerFilterComposer';\nimport { type LayerSelector, matchesLayerSelector, type StyleSources } from '../shared/layers/layerSelector';\nimport { type StyleLayerGroupName, styleLayerGroupSelector } from './styleLayerGroups';\n\n/**\n * How {@link StylingFoundationsModule.layers} picks style layers: by a named group, layer type, vector-tile\n * source layer and case-insensitive id fragments. Every constraint given must hold.\n *\n * @group Map Styling\n */\nexport type LayerQuery = {\n /**\n * A named group of style layers: a base-map group, the same vocabulary as {@link BaseMapModule},\n * or the layers of another style-owned module (`pois`, `trafficFlow`, `trafficIncidents`, `hillshade`).\n */\n group?: StyleLayerGroupName;\n /** The style's own `metadata.group` values, for groups the SDK does not name. */\n metadataGroups?: string[];\n /** Layer types (`line`, `fill`, `symbol`, …). */\n layerTypes?: LayerSpecification['type'][];\n /** Vector-tile source layers (`roads`, `places`, `poi`, …). */\n sourceLayers?: string[];\n /** The layer id must contain one of these (case-insensitive). */\n idIncludes?: string[];\n /** The layer id must contain none of these (case-insensitive). */\n idExcludes?: string[];\n};\n\n/**\n * MapLibre types each property accessor against the literal name it is given, a pairing a name picked\n * at runtime cannot express. The styling foundations module reads and writes through this view of the same\n * methods, with values that are the style's own or an expression wrapped around them.\n * @ignore\n */\nexport type RuntimeLayerProperties = {\n getLayoutProperty(layerId: string, property: string): unknown;\n getPaintProperty(layerId: string, property: string): unknown;\n setLayoutProperty(layerId: string, property: string, value: unknown, options: StyleSetterOptions): unknown;\n setPaintProperty(layerId: string, property: string, value: unknown, options: StyleSetterOptions): unknown;\n};\n\n// The raw layer edits' key with the shared filter composer.\nconst LAYER_EDITS_CONTRIBUTOR = 'styling.layers';\n\n// A raw paint or layout override on a set of layers.\ntype PropertyOverride = { kind: 'paint' | 'layout'; property: string; value: unknown };\n// An override on one layer, with the value it replaced there.\ntype OverrideEntry = { override: PropertyOverride; before: unknown };\n\n/**\n * The advanced tier of the styling foundations module: raw MapLibre paint, layout and filter edits over a\n * query of style layers, re-applied across style switches.\n *\n * @remarks\n * **This is the version-coupled tier.** Layer ids, expressions and even which layers exist belong to\n * the loaded style version, so an edit may match nothing on the next one: the selection is then empty\n * and the SDK warns once. Prefer a knob or {@link StylingFoundationsModule.setMapColors} where one fits.\n *\n * Overrides are not part of `getConfig()`: they are edits to this style, not portable settings.\n *\n * **Only the style's own layers.** The layers a data-owned module adds (places, routes, geometries…)\n * take their overrides through that module's `layers` configuration, and the ones you add are yours.\n *\n * @example\n * ```typescript\n * styling.layers.query({ group: 'roadLabels' }).setPaint({ 'text-color': '#93c5fd' });\n * styling.layers.query({ metadataGroups: ['water'], layerTypes: ['fill'] }).setPaint({ 'fill-opacity': 0.6 });\n * styling.layers.query({ idIncludes: ['railway'] }).setVisible(false);\n * styling.layers.query({ group: 'trafficFlow', layerTypes: ['line'] }).setPaint({ 'line-opacity': 0.6 });\n * styling.layers.query({ group: 'roadLabels' }).reset();\n * ```\n *\n * @group Map Styling\n */\nexport class LayerSelection {\n /**\n * @ignore\n */\n constructor(\n private readonly registry: LayerOverrides,\n readonly query: LayerQuery,\n ) {}\n\n /**\n * The ids of the layers the query matches in the loaded style, in draw order. Empty when the\n * style has no such layers.\n */\n get layerIds(): string[] {\n return this.registry.resolve(this.query);\n }\n\n /** Sets paint properties on every matched layer. */\n setPaint(paint: Partial<AllPaintProperties>): this {\n for (const [property, value] of Object.entries(paint))\n this.registry.override(this.query, { kind: 'paint', property, value });\n return this;\n }\n\n /** Sets layout properties on every matched layer. */\n setLayout(layout: Partial<AllLayoutProperties>): this {\n for (const [property, value] of Object.entries(layout))\n this.registry.override(this.query, { kind: 'layout', property, value });\n return this;\n }\n\n /**\n * Replaces the filter the style gave every matched layer. The filters other modules add, such as\n * POI categories and traffic filters, still narrow it.\n */\n setFilter(filter: FilterSpecification | undefined): this {\n this.registry.overrideFilter(this.query, filter);\n return this;\n }\n\n /** Shows or hides every matched layer. */\n setVisible(visible: boolean): this {\n return this.setLayout({ visibility: visible ? 'visible' : 'none' });\n }\n\n /** Removes every override made through the styling foundations module on the matched layers. */\n reset(): this {\n this.registry.reset(this.query);\n return this;\n }\n}\n\n/**\n * The styling foundations module's registry of raw layer overrides: resolves queries against the loaded style,\n * applies overrides, remembers what each property was before, and re-applies after a style switch.\n * Filters go through the map's {@link LayerFilterComposer}, which holds the style's own filter.\n * @ignore\n */\nexport class LayerOverrides {\n private layers: LayerSpecification[] = [];\n private sources: StyleSources = {};\n // Per layer id, per property.\n private readonly properties = new Map<string, Map<string, OverrideEntry>>();\n // Per layer id: the filter that replaces the style's own.\n private readonly filters = new Map<string, FilterSpecification | undefined>();\n\n constructor(\n private readonly mapLibreMap: MapLibreMap,\n private readonly filterComposer: LayerFilterComposer,\n private readonly warn: (key: string, message: string) => void,\n ) {}\n\n /** Points the registry at a freshly loaded style; overrides are kept and re-applied by {@link reapply}. */\n index(layers: LayerSpecification[], sources: StyleSources): void {\n this.layers = layers;\n this.sources = sources;\n }\n\n /** Forgets every override (a clean style switch, which also clears the filter composer). */\n clear(): void {\n this.properties.clear();\n this.filters.clear();\n }\n\n resolve(query: LayerQuery): string[] {\n const selectors = selectorsOf(query);\n const ids = this.layers\n .filter((layer) => selectors.every((selector) => matchesLayerSelector(selector, layer, this.sources)))\n .map((layer) => layer.id);\n if (ids.length === 0) {\n this.warn(\n `query:${JSON.stringify(query)}`,\n `layers.query(${JSON.stringify(query)}) matches no layer in the loaded style; the edit will have no effect.`,\n );\n }\n return ids;\n }\n\n override(query: LayerQuery, override: PropertyOverride): void {\n const key = `${override.kind}:${override.property}`;\n for (const layerId of this.resolve(query)) {\n const perLayer = this.properties.get(layerId) ?? new Map<string, OverrideEntry>();\n const existing = perLayer.get(key);\n perLayer.set(key, { override, before: existing ? existing.before : this.read(layerId, override) });\n this.properties.set(layerId, perLayer);\n this.write(layerId, override);\n }\n }\n\n overrideFilter(query: LayerQuery, filter: FilterSpecification | undefined): void {\n for (const layerId of this.resolve(query)) {\n this.filters.set(layerId, filter);\n this.filterComposer.setTransform(layerId, LAYER_EDITS_CONTRIBUTOR, () => filter);\n }\n }\n\n reset(query: LayerQuery): void {\n for (const layerId of this.resolve(query)) {\n for (const { override, before } of this.properties.get(layerId)?.values() ?? []) {\n this.write(layerId, { ...override, value: before });\n }\n this.properties.delete(layerId);\n if (this.filters.delete(layerId)) {\n this.filterComposer.setTransform(layerId, LAYER_EDITS_CONTRIBUTOR, undefined);\n }\n }\n }\n\n /**\n * Lands every override again on top of the current style. What an override replaces is re-read\n * only where something else wrote under it since (a new style, or a knob on the same property);\n * a layer still carrying the override keeps the value it replaced.\n */\n reapply(): void {\n for (const [layerId, perLayer] of this.properties) {\n if (!this.mapLibreMap.getLayer(layerId)) continue;\n\n for (const entry of perLayer.values()) {\n const current = this.read(layerId, entry.override);\n if (!isEqual(current, entry.override.value)) entry.before = current;\n\n this.write(layerId, entry.override);\n }\n }\n for (const [layerId, filter] of this.filters) {\n this.filterComposer.setTransform(layerId, LAYER_EDITS_CONTRIBUTOR, () => filter);\n }\n }\n\n private read(layerId: string, { kind, property }: PropertyOverride): unknown {\n const properties: RuntimeLayerProperties = this.mapLibreMap;\n return kind === 'paint'\n ? properties.getPaintProperty(layerId, property)\n : properties.getLayoutProperty(layerId, property);\n }\n\n private write(layerId: string, { kind, property, value }: PropertyOverride): void {\n if (!this.mapLibreMap.getLayer(layerId)) return;\n\n const properties: RuntimeLayerProperties = this.mapLibreMap;\n const options = { validate: false };\n if (kind === 'paint') {\n properties.setPaintProperty(layerId, property, value, options);\n } else {\n properties.setLayoutProperty(layerId, property, value, options);\n }\n }\n}\n\n// Both selectors must hold, so the query narrows the group — never replaces or widens its constraints.\nconst selectorsOf = ({ group, ...selector }: LayerQuery): LayerSelector[] => [\n selector,\n ...(group ? [styleLayerGroupSelector(group)] : []),\n];\n","import type { ProjectionSpecification, SkySpecification } from 'maplibre-gl';\nimport type { LightDark } from '../shared';\nimport { matchesAnyLayerSelector } from '../shared/layers/layerSelector';\nimport { standardStyleIdOf } from '../shared/mapUtils';\nimport { ofMap } from '../shared/ofMap';\nimport type { TomTomMap } from '../TomTomMap';\nimport { isColorLiteral, opaqueColor } from '../utils/colorUtils';\nimport { type LiteralOwnership, type MapColorBid, pickAnchorLiteral, recolorValue } from './colorRewrite';\nimport {\n type BaseMapColorName,\n type MapColorName,\n type MapColors,\n type MapColorTarget,\n mapColorDefinitions,\n mapColorKnobId,\n mapColorNames,\n type PaintColorProperty,\n} from './mapColorCatalogue';\nimport {\n type DerivedMapColorName,\n landDerivedMapColors,\n mapColorDerivationInputs,\n withDerivedMapColors,\n} from './mapColorDerivation';\nimport { type StyleKnobExtension, StyleKnobs, type StyleKnobValue, styleValueOf, targetKey } from './StyleKnobs';\nimport type { MapLookMechanism, StyleKnobId } from './styleKnobDefinitions';\n\n// One (layer, property) a map-colour knob rewrites, with the ownership rule for literals inside it.\ntype MapColorTargetInstance = Pick<MapColorBid, 'recolorsTints'> & {\n color: MapColorName;\n layerId: string;\n property: PaintColorProperty;\n owns: LiteralOwnership;\n};\n\n// A map-level knob reaches the map itself rather than a layer, so it stands in for its one target\n// under a layer id no style can have.\nconst MAP_LEVEL = '\\u0000map';\n\n// Everything MapLibre accepts as a projection, which is a plain name only in the simple case.\ntype ProjectionValue = ProjectionSpecification['type'];\n\n// Our own sky, with both colours as literals rather than expressions — which is what lets `get()`\n// report them as the knob's default value.\ntype SkyDefaults = SkySpecification & { 'sky-color': string; 'horizon-color': string };\n\n// The sky the atmosphere is drawn in, per light/dark theme of the loaded style: a daylit blue with a\n// white horizon over a light map, a night blue with a dim horizon over a dark one. A light sky behind\n// a dark map reads as a halo around the globe rather than as atmosphere. Built in a pure function:\n// a bundler keeps an object spread at module level.\nconst SKY_DEFAULTS = /* @__PURE__ */ ((): Record<LightDark, SkyDefaults> => {\n const blends: SkySpecification = {\n 'sky-horizon-blend': 0.8,\n 'atmosphere-blend': 0.8,\n 'fog-ground-blend': 0.5,\n 'horizon-fog-blend': 0.8,\n };\n return {\n light: { ...blends, 'sky-color': '#88c6fc', 'horizon-color': '#ffffff', 'fog-color': '#ffffff' },\n dark: { ...blends, 'sky-color': '#0a1626', 'horizon-color': '#2b4a72', 'fog-color': '#0a1626' },\n };\n})();\n\n// What shows around a globe, where the map canvas is transparent and the page would otherwise show\n// through. Matches the sky, so the planet sits in air of the same colour it is lit by. Only used\n// where the page paints nothing of its own behind the map.\nconst SPACE_COLOR_DEFAULTS: Record<LightDark, string> = { light: '#ffffff', dark: '#0a1626' };\n\n// What a computed `background-color` reads as on a container the page never painted.\nconst UNPAINTED_BACKGROUNDS = new Set(['', 'transparent', 'rgba(0, 0, 0, 0)']);\n\n// The sky MapLibre draws for a style without one, written out because `setSky` has no \"unset\".\nconst CLEAR_SKY: SkySpecification = {\n 'sky-color': 'transparent',\n 'horizon-color': 'transparent',\n 'fog-color': 'transparent',\n 'fog-ground-blend': 1,\n 'atmosphere-blend': 0,\n};\n\nconst extensionsByMap = new WeakMap<TomTomMap, MapLookKnobs>();\n\n/**\n * The look of the whole map, as the {@link StylingFoundationsModule} knobs set it: the ten map colours — each\n * re-deriving every shade the style built from it, and on the mono styles the colours the theme\n * derives from `land` — the accent, the projection, the sky and the space around a globe. An\n * extension of the map's {@link StyleKnobs}, so a bundle without the styling foundations module carries none of it.\n *\n * @ignore\n */\nexport class MapLookKnobs implements StyleKnobExtension {\n // Per (layer, property), every map colour that reaches it: tunnels draw major and minor roads in one expression.\n private mapColorTargetsByKey = new Map<string, MapColorTargetInstance[]>();\n private mapColorTargetsByKnob = new Map<StyleKnobId, MapColorTargetInstance[]>();\n // Per colour, the style's own literal its shades are measured from.\n private mapColorAnchors = new Map<StyleKnobId, string>();\n // The colours the loaded style derives from `land`, as its theme does.\n private landDerivedColors: readonly DerivedMapColorName[] = [];\n // What the page painted behind the map before the space colour first painted over it: the value\n // `view.spaceColor` reports as its default and resets to, empty when the page painted nothing.\n // Captured once — by the second style load the container already carries the space colour.\n private originalSpaceColor: string | undefined;\n\n private constructor(private readonly engine: StyleKnobs) {}\n\n /** The extension of this map, created on first use. */\n static for(tomtomMap: TomTomMap): MapLookKnobs {\n return ofMap(extensionsByMap, tomtomMap, () => new MapLookKnobs(StyleKnobs.for(tomtomMap)));\n }\n\n private get tomtomMap(): TomTomMap {\n return this.engine.tomtomMap;\n }\n\n index(): void {\n this.mapColorTargetsByKey = new Map();\n this.mapColorTargetsByKnob = new Map();\n this.mapColorAnchors = new Map();\n const standardStyleID = standardStyleIdOf(this.tomtomMap.getStyleInput());\n this.landDerivedColors = (standardStyleID && landDerivedMapColors[standardStyleID]) ?? [];\n }\n\n resolve(_id: StyleKnobId, mechanism: MapLookMechanism): string[] {\n return mechanism.type === 'mapColor' ? this.resolveMapColor(mechanism.color) : [MAP_LEVEL];\n }\n\n refresh(_id: StyleKnobId, mechanism: MapLookMechanism): void {\n switch (mechanism.type) {\n case 'projection':\n this.refreshProjection();\n break;\n case 'sky':\n this.refreshSky();\n break;\n case 'space':\n this.refreshSpace();\n break;\n case 'mapColor':\n this.refreshMapColor(mechanism.color);\n break;\n case 'published':\n // Nothing on the map to write: the value reaches its readers through `mapColorsInForce`.\n break;\n }\n }\n\n defaultOf(id: StyleKnobId, mechanism: MapLookMechanism): StyleKnobValue | undefined {\n if (mechanism.type === 'mapColor') {\n const anchor = this.mapColorAnchors.get(id);\n return anchor && opaqueColor(anchor);\n }\n return this.mapLevelDefaultOf(id);\n }\n\n // A map colour the loaded style derives from one that is set, as it currently paints — nothing\n // for a colour the style has no layers for.\n derivedValueOf(id: StyleKnobId, mechanism: MapLookMechanism): StyleKnobValue | undefined {\n if (mechanism.type !== 'mapColor' || !this.mapColorAnchors.has(id)) return undefined;\n\n return this.mapColorsOf()[mechanism.color];\n }\n\n /**\n * The map colours in effect — set, derived from one that is set, or the style's own — for the\n * modules that draw their own layers (see `TomTomMap.mapColors`).\n */\n mapColorsInForce(): MapColors {\n const colors = this.mapColorsOf();\n for (const name of mapColorNames) {\n const anchor = this.mapColorAnchors.get(mapColorKnobId(name));\n if (colors[name] === undefined && anchor) colors[name] = opaqueColor(anchor);\n }\n return colors;\n }\n\n /**\n * Paints the container the canvas sits on. The one knob applied whether or not it was set:\n * nothing else paints behind the canvas, so leaving it alone shows the page through a globe. The\n * styling foundations module runs it on every style load, which is what makes it follow a light/dark switch.\n */\n refreshSpace(): void {\n const container = this.tomtomMap.mapLibreMap.getContainer();\n this.originalSpaceColor ??= paintedBackgroundOf(container);\n container.style.backgroundColor = this.engine.values['view.spaceColor'] ?? this.defaultSpaceColor();\n }\n\n // Indexes one semantic colour: the (layer, property) pairs it rewrites with the ownership rule\n // for each, and the anchor its shades are measured from. A layer reached by several targets — a\n // tunnel drawing every road class — is registered once per rule.\n private resolveMapColor(color: BaseMapColorName): string[] {\n const { layers, sources } = this.engine.indexedStyle;\n const knobId = mapColorKnobId(color);\n const definition = mapColorDefinitions[color];\n const instances: MapColorTargetInstance[] = [];\n for (const target of definition.targets) {\n for (const layer of layers.filter((candidate) =>\n matchesAnyLayerSelector(target.layers, candidate, sources),\n )) {\n for (const property of target.properties) {\n if (!this.engine.capture(layer, property)) continue;\n\n const instance = {\n color,\n layerId: layer.id,\n property,\n owns: ownershipOf(target),\n recolorsTints: target.recolorsTints,\n };\n instances.push(instance);\n const key = targetKey(layer.id, property);\n this.mapColorTargetsByKey.set(key, [...(this.mapColorTargetsByKey.get(key) ?? []), instance]);\n }\n }\n }\n this.mapColorTargetsByKnob.set(knobId, instances);\n\n const { anchor } = definition;\n const anchorLayer = layers.find(\n (candidate) =>\n matchesAnyLayerSelector(anchor.layers, candidate, sources) &&\n styleValueOf(candidate, anchor.property) !== undefined,\n );\n const literal =\n anchorLayer &&\n pickAnchorLiteral(\n styleValueOf(anchorLayer, anchor.property),\n ownershipOf({ roadClass: anchor.roadClass }),\n anchor.lastStop,\n );\n if (literal) this.mapColorAnchors.set(knobId, literal);\n\n // Without an anchor there is nothing to measure shades from: the colour is unreachable.\n return literal ? [...new Set(instances.map((instance) => instance.layerId))] : [];\n }\n\n // Re-derives a colour's layers, and those of every colour the loaded style derives from it.\n private refreshMapColor(color: BaseMapColorName): void {\n const palette = this.mapColorsOf();\n const colors = [\n color,\n ...this.landDerivedColors.filter((name) => mapColorDerivationInputs(name).includes(color)),\n ];\n const targets = new Map(\n colors\n .flatMap((name) => this.mapColorTargetsByKnob.get(mapColorKnobId(name)) ?? [])\n .map((target) => [targetKey(target.layerId, target.property), target]),\n );\n for (const { layerId, property } of targets.values()) {\n this.refreshMapColorProperty(layerId, property, palette);\n }\n }\n\n // The palette in force: the colours set, and those the loaded style derives from one of them.\n private mapColorsOf(): MapColors {\n const settings = this.engine.values;\n const given: MapColors = {};\n for (const name of mapColorNames) {\n const value = settings[mapColorKnobId(name)];\n if (typeof value === 'string') given[name] = value;\n }\n return withDerivedMapColors(given, this.landDerivedColors, this.mapColorAnchors.get(mapColorKnobId('land')));\n }\n\n // Re-derives one (layer, property) from the style's own value through every map colour that\n // reaches it. The colours bid together rather than in turn, so each judges the literals the style\n // shipped instead of what the previous colour left behind.\n private refreshMapColorProperty(layerId: string, property: PaintColorProperty, palette: MapColors): void {\n const originalValue = this.engine.originalValueOf(layerId, property);\n const bids: MapColorBid[] = [];\n const targets = this.mapColorTargetsByKey.get(targetKey(layerId, property)) ?? [];\n for (const { color, owns, recolorsTints } of targets) {\n const newColor = palette[color];\n const anchorColor = this.mapColorAnchors.get(mapColorKnobId(color));\n if (!newColor || !anchorColor) continue;\n\n bids.push({ anchorColor, newColor, owns, recolorsTints });\n }\n this.engine.setProperty(layerId, property, bids.length ? recolorValue(originalValue, bids) : originalValue);\n }\n\n private refreshProjection(): void {\n this.tomtomMap.mapLibreMap.setProjection({\n type: this.engine.values['view.projection'] ?? this.styleProjection(),\n });\n }\n\n private styleProjection(): ProjectionValue {\n return this.engine.indexedStyle.projection?.type ?? 'mercator';\n }\n\n // The sky is one MapLibre setting driven by three knobs: on/off and its two colours. The style's\n // own sky wins over our defaults, and those follow the style's light/dark theme. Off means no\n // sky, including over a style that ships one.\n private refreshSky(): void {\n const settings = this.engine.values;\n const styleSky = this.engine.indexedStyle.sky;\n const wanted = settings['view.sky'] ?? this.mapLevelDefaultOf('view.sky');\n const themeSky = SKY_DEFAULTS[this.tomtomMap.styleLightDarkTheme];\n const sky: SkySpecification = wanted\n ? {\n ...themeSky,\n ...styleSky,\n 'sky-color': settings['view.skyColor'] ?? styleSky?.['sky-color'] ?? themeSky['sky-color'],\n 'horizon-color':\n settings['view.horizonColor'] ?? styleSky?.['horizon-color'] ?? themeSky['horizon-color'],\n }\n : CLEAR_SKY;\n this.tomtomMap.mapLibreMap.setSky(sky, { validate: false });\n }\n\n // The page's own background where it painted one, so the knob gives it back on a reset instead\n // of the SDK keeping a colour the page never asked for; the theme's otherwise, since then\n // nothing at all paints behind the canvas.\n private defaultSpaceColor(): string {\n return this.originalSpaceColor || SPACE_COLOR_DEFAULTS[this.tomtomMap.styleLightDarkTheme];\n }\n\n // What the loaded style declares for the view knobs.\n private mapLevelDefaultOf(id: StyleKnobId): StyleKnobValue | undefined {\n const sky = this.engine.indexedStyle.sky;\n const themeSky = SKY_DEFAULTS[this.tomtomMap.styleLightDarkTheme];\n switch (id) {\n case 'view.projection': {\n // A style that drives its projection from an expression has no one value to report.\n const projection = this.styleProjection();\n return typeof projection === 'string' ? projection : undefined;\n }\n case 'view.sky':\n // A style without a sky, or with a fully transparent one, shows none.\n return sky !== undefined && sky['atmosphere-blend'] !== 0 && sky['sky-color'] !== 'transparent';\n case 'view.skyColor':\n return isColorLiteral(sky?.['sky-color']) ? sky['sky-color'] : themeSky['sky-color'];\n case 'view.horizonColor':\n return isColorLiteral(sky?.['horizon-color']) ? sky['horizon-color'] : themeSky['horizon-color'];\n case 'view.spaceColor':\n return this.defaultSpaceColor();\n default:\n return undefined;\n }\n }\n}\n\n// Which literals a map-colour target owns: all of them, or only those on one side of the major/minor\n// road split — where \"minor\" also takes the literals outside any road-class match.\nconst ownershipOf = (target: Pick<MapColorTarget, 'roadClass'>): LiteralOwnership => {\n if (target.roadClass === 'major') return (roadClass) => roadClass === 'major';\n if (target.roadClass === 'minor') return (roadClass) => roadClass !== 'major';\n return () => true;\n};\n\n// What the page paints behind the map container, empty when it paints nothing. Computed rather than\n// inline, since a background a stylesheet sets never reaches `style`.\nconst paintedBackgroundOf = (container: HTMLElement): string => {\n const painted = getComputedStyle(container).backgroundColor;\n return UNPAINTED_BACKGROUNDS.has(painted) ? '' : painted;\n};\n","import { baseMapLayerGroupSelector } from '../base/layerGroups';\nimport type { LayerSelector } from '../shared/layers/layerSelector';\nimport type { StylingFoundationsKnobId } from './knobCatalogue';\nimport { MapLookKnobs } from './MapLookKnobs';\nimport { type BaseMapColorName, baseMapColorKnobs } from './mapColorCatalogue';\nimport { styleKnobScope } from './StyleKnobs';\nimport { type StyleKnobDefinition, type StyleKnobTable, scaleKnob } from './styleKnobDefinitions';\n\nconst mapColor = (color: BaseMapColorName): StyleKnobDefinition<'color'> => ({\n kind: 'color',\n mechanisms: [{ type: 'mapColor', color }],\n});\n\n/**\n * How each {@link StylingFoundationsModule} knob reaches the loaded style — the look of the whole map.\n * @ignore\n */\nexport const stylingFoundationsStyleKnobs = /* @__PURE__ */ (() => {\n // Every symbol layer drawn from vector tiles: place, road, nature and POI labels, shields, house\n // numbers — on any style, TomTom's or not. GeoJSON-backed layers are the SDK's own overlays\n // (places, routes) and keep their own sizing.\n const baseMapSymbols: LayerSelector = { sourceTypes: ['vector'], layerTypes: ['symbol'] };\n const roadLines: LayerSelector = { ...baseMapLayerGroupSelector('roads'), layerTypes: ['line'] };\n const skyColor: StyleKnobDefinition<'color'> = { kind: 'color', mechanisms: [{ type: 'sky' }] };\n return {\n 'labels.sizeFactor': scaleKnob('text-size', baseMapSymbols),\n 'symbols.sizeFactor': scaleKnob('icon-size', baseMapSymbols),\n 'roads.widthFactor': scaleKnob('line-width', roadLines),\n ...baseMapColorKnobs(mapColor),\n 'colors.accent': { kind: 'color', mechanisms: [{ type: 'published' }] },\n 'view.projection': { kind: 'enum', mechanisms: [{ type: 'projection' }] },\n 'view.sky': { kind: 'toggle', mechanisms: [{ type: 'sky' }] },\n 'view.skyColor': skyColor,\n 'view.horizonColor': skyColor,\n 'view.spaceColor': { kind: 'color', mechanisms: [{ type: 'space' }] },\n } as const satisfies StyleKnobTable<'', StylingFoundationsKnobId>;\n})();\n\n/**\n * The {@link StylingFoundationsModule} knobs, with the map-look extension of the engine that draws the map\n * colours, the projection, the sky and the space around a globe.\n * @ignore\n */\nexport const stylingFoundationsLook = /* @__PURE__ */ styleKnobScope(\n 'StylingFoundationsModule',\n '',\n stylingFoundationsStyleKnobs,\n (map) => MapLookKnobs.for(map),\n);\n","import { omit } from 'lodash-es';\nimport type { MapGeoJSONFeature, StyleSpecification } from 'maplibre-gl';\nimport { AbstractStyleOwnedMapModule, type CombinedEvents, sharedInstance, validateKnobValue } from '../shared';\nimport { waitUntilMapIsReady } from '../shared/mapUtils';\nimport type { TomTomMap } from '../TomTomMap';\nimport { knobDefinitions, type StylingFoundationsKnobId } from './knobCatalogue';\nimport { LayerOverrides, type LayerQuery, LayerSelection } from './layerQuery';\nimport { MapLookKnobs } from './MapLookKnobs';\nimport { isMapColorName, type MapColors, mapColorKnobId, mapColorNames } from './mapColorCatalogue';\nimport type { StyleKnobs } from './StyleKnobs';\nimport { stylingFoundationsLook } from './stylingFoundationsStyleKnobs';\nimport type { StylingFoundationsModuleConfig } from './types/stylingTypes';\n\n/**\n * Event surface of {@link StylingFoundationsModule}: it owns no map features, so only the lifecycle half is\n * live — `config-change` fires with the full {@link StylingFoundationsModuleConfig} after every knob change.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsEvents = CombinedEvents<MapGeoJSONFeature, StylingFoundationsModuleConfig, never>;\n\n/**\n * The foundations of the whole map's look — the tier between showing or hiding a part of it and\n * hand-editing MapLibre layers: sizes across every part of the style, the ten map colours and the\n * accent, the projection and the sky.\n *\n * Every adjustable thing is a **knob** with a stable id, a kind, a range and a default read from the\n * loaded style. The ids form the {@link StylingFoundationsKnobId} union, and {@link stylingFoundationsKnobCatalogue} lists\n * each one's kind, range and description as data, with no map needed to read it. {@link setKnob},\n * {@link getKnob} and {@link resetKnob} set, read and unset a knob by its id, as on every module.\n *\n * @remarks\n * **Which styles a knob works on** is `appliesTo` in the catalogue. `any-style` knobs (sizes,\n * widths) wrap whatever the style already does — `text-size: E` becomes `[\"*\", 1.3, E]` — so they\n * work on a custom style too. `tomtom-styles` knobs (the map colours) reach their layers through\n * the TomTom styles this SDK version is verified against; on any other style setting one warns once\n * and does nothing, rather than failing silently.\n *\n * **Style changes.** Settings are re-applied after every `setStyle` that carries state over, on top of\n * whatever the other modules restored, and dropped on a clean switch (`resetState: true`).\n *\n * **Interplay with other modules.** One part of the map's look is that part's module's:\n * {@link POIsModule} sizes and colours the POIs, {@link TrafficFlowModule} and\n * {@link TrafficIncidentsModule} the traffic lines, {@link TerrainModule} the hillshade, and\n * {@link BaseMapModule} shows and hides the layer groups and the road markings within them. The\n * sizes here compose with theirs: `labels.sizeFactor` and the POIs' `sizeFactor` multiply on the\n * POI labels.\n *\n * @example\n * ```typescript\n * import { getKnob, setKnob, StylingFoundationsModule, stylingFoundationsKnobCatalogue } from '@tomtom-org/maps-sdk/map';\n *\n * const styling = await StylingFoundationsModule.get(map);\n * setKnob(styling, stylingFoundationsKnobCatalogue, 'labels.sizeFactor', 1.2);\n * setKnob(styling, stylingFoundationsKnobCatalogue, 'colors.water', '#7cc4e8');\n *\n * // The catalogue as data, and each knob's value in force:\n * for (const knob of stylingFoundationsKnobCatalogue) {\n * console.log(knob.id, knob.kind, knob.range, getKnob(styling, stylingFoundationsKnobCatalogue, knob.id));\n * }\n *\n * // Serializable, diffable, re-applicable:\n * const saved = styling.getConfig();\n * styling.resetConfig();\n * styling.applyConfig(saved);\n * ```\n *\n * @see [Map Styling Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/styling)\n *\n * @group Map Styling\n */\nexport class StylingFoundationsModule extends AbstractStyleOwnedMapModule<\n Record<string, never>,\n StylingFoundationsModuleConfig\n> {\n // Declared without initialisers on purpose: the base constructor already runs\n // `_initSourcesWithLayers`, and a field initialiser would wipe its work afterwards.\n private layerOverrides!: LayerOverrides;\n private warned!: Set<string>;\n\n /**\n * Retrieves the styling foundations module of the given map, creating it on first use.\n *\n * @param settings - Knob values to apply right away, e.g. restored from storage.\n * @returns A promise that resolves once the map style is loaded and the settings are applied.\n *\n * @remarks\n * **Instances:** the module styles the sources and layers the map style already provides, so\n * every map has exactly one — a second `get()` returns the same instance, with the given\n * settings applied on top of what it already holds.\n *\n * @example\n * ```typescript\n * const styling = await StylingFoundationsModule.get(map, { 'labels.sizeFactor': 1.2 });\n * ```\n */\n static async get(map: TomTomMap, settings?: StylingFoundationsModuleConfig): Promise<StylingFoundationsModule> {\n await waitUntilMapIsReady(map);\n return sharedInstance(\n map,\n StylingFoundationsModule,\n () => new StylingFoundationsModule(map, settings),\n settings && ((existing) => existing.updateConfig(settings)),\n );\n }\n\n private constructor(map: TomTomMap, settings?: StylingFoundationsModuleConfig) {\n super(map, settings);\n }\n\n // The map's style knob engine, which every module restyling the loaded style shares.\n private get styleKnobs(): StyleKnobs {\n return stylingFoundationsLook.engineOf(this.tomtomMap);\n }\n\n /**\n * @ignore\n */\n protected styleChangePriority(): number {\n // After the style knobs have landed on the new style, so the raw layer edits land on top.\n return 100;\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers(): Record<string, never> {\n this.warned = new Set();\n this.layerOverrides ??= new LayerOverrides(this.mapLibreMap, this.filterComposer, (key, message) =>\n this.warnOnce(key, message),\n );\n const { layers, sources } = this.styleKnobs.indexedStyle;\n this.layerOverrides.index(layers, sources);\n MapLookKnobs.for(this.tomtomMap).refreshSpace();\n return {};\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(settings: StylingFoundationsModuleConfig | undefined) {\n const next = withoutUndefined(settings);\n stylingFoundationsLook.apply(this.tomtomMap, next);\n this.tomtomMap._setMapColors(MapLookKnobs.for(this.tomtomMap).mapColorsInForce());\n // Raw layer overrides land on top of the knobs, as they did when they were made.\n if (this.tomtomMap.mapReady) this.layerOverrides.reapply();\n return Object.keys(next).length ? next : undefined;\n }\n\n /**\n * @ignore\n */\n protected discardShownData(): void {\n // A clean style switch drops the raw layer edits along with the settings.\n this.layerOverrides.clear();\n }\n\n /**\n * The advanced tier: raw MapLibre paint, layout and filter edits over a query of the style's own\n * layers, re-applied across style switches. Version-coupled by nature — see {@link LayerSelection} for\n * the terms — so prefer a knob or {@link setMapColors} wherever one covers the intent.\n *\n * @example\n * ```typescript\n * styling.layers.query({ group: 'roadLabels' }).setPaint({ 'text-color': '#93c5fd' });\n * ```\n */\n get layers(): { query: (query: LayerQuery) => LayerSelection } {\n return { query: (query) => new LayerSelection(this.layerOverrides, query) };\n }\n\n // The knob ids are keys of the settings rather than paths into them.\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n this.assertKnob(id);\n return this.config?.[id] ?? stylingFoundationsLook.valueOf(this.tomtomMap, id);\n }\n\n /**\n * @ignore\n */\n _setKnobValue(id: string, value: unknown): void {\n this.assertKnob(id);\n this.applyConfig({ ...this.config, [id]: value } as StylingFoundationsModuleConfig);\n }\n\n /**\n * @ignore\n */\n _resetKnobValue(id: string): void {\n this.assertKnob(id);\n this.applyConfig(omit(this.config, id));\n }\n\n /**\n * Recolours the whole base map with up to ten semantic colours in one call — the style themes'\n * Foundations palette, each listed with what it reaches under {@link MapColorName} — and sets\n * `accent`, the default colour of routes, geometries and places ({@link TomTomMap.mapColors}).\n *\n * Partial objects are fine; colours not given stay as they are, except on the mono styles, which\n * derive the other nine from `land` as their theme does: there the theme's own formulas re-derive\n * them from the `land` given.\n *\n * @remarks\n * Each colour re-derives every shade the style built from it — a trunk variant, an outline, a\n * tunnel — at the style's own HSL offset, so those relationships survive the recolour. The colours\n * are also the `colors.*` knobs, so `getKnob`, `resetKnob` and settings persistence apply.\n *\n * Those offsets come off the loaded style, which makes a palette a recolour and not an\n * inversion: dark land over `standardLight` pushes the derived shades to the ends of their\n * range, so switch to `standardDark` first.\n *\n * @param colors - The colours to set, as CSS colour strings.\n * @throws `RangeError` for a value that is not a colour; `Error` for an unknown colour name.\n *\n * @example\n * ```typescript\n * styling.setMapColors({ land: '#f3f5f7', water: '#accbe2', roadMajor: '#a1b8ce', label: '#364659' });\n * resetKnob(styling, stylingFoundationsKnobCatalogue, 'colors.roadMajor'); // one colour back to the style\n * styling.setMapColors(deriveMapColors(['#0b2a3a', '#0fa8c8', '#f6efe3'])); // map-theme plugin assigns the roles\n * ```\n */\n setMapColors(colors: MapColors): void {\n const settings: StylingFoundationsModuleConfig = {};\n for (const [name, value] of Object.entries(colors)) {\n if (!isMapColorName(name)) {\n throw new Error(`Unknown map colour '${name}'. Known colours: ${mapColorNames.join(', ')}.`);\n }\n if (value === undefined) continue;\n\n const id = mapColorKnobId(name);\n validate(id, value);\n settings[id] = value;\n }\n this.updateConfig(settings);\n }\n\n /**\n * The style as currently rendered — the loaded style with every knob applied — as a MapLibre\n * style specification, to save, diff, serve yourself, or load back as a custom style.\n *\n * @remarks\n * It includes whatever the SDK's data modules (places, routes) have on the map, and its tile\n * URLs carry the API key the map was created with: strip or rotate it before publishing.\n */\n exportStyle(): StyleSpecification {\n return structuredClone(this.mapLibreMap.getStyle());\n }\n\n /**\n * Lifecycle events of this module. The module owns no map features, so there are no user\n * interaction events; `config-change` fires with the full settings after every knob change.\n *\n * @example\n * ```typescript\n * styling.events.on('config-change', (settings) => localStorage.setItem('styling', JSON.stringify(settings)));\n * ```\n */\n get events(): StylingFoundationsEvents {\n return this.moduleEvents([]);\n }\n\n private assertKnob(id: string): asserts id is StylingFoundationsKnobId {\n if (!(id in knobDefinitions)) {\n throw new Error(`Unknown styling knob '${id}'. stylingFoundationsKnobIds lists every knob id.`);\n }\n }\n\n private warnOnce(key: string, message: string): void {\n if (this.warned.has(key)) return;\n\n this.warned.add(key);\n console.warn(`[StylingFoundationsModule] ${message}`);\n }\n}\n\nconst validate = (id: StylingFoundationsKnobId, value: unknown): void =>\n validateKnobValue({ id, ...knobDefinitions[id] }, value);\n\n// Settings without `undefined` entries (an explicit `undefined` means \"not set\", like an absent key).\nconst withoutUndefined = (settings: StylingFoundationsModuleConfig | undefined): StylingFoundationsModuleConfig =>\n Object.fromEntries(\n Object.entries(settings ?? {}).filter(([, value]) => value !== undefined),\n ) as StylingFoundationsModuleConfig;\n","import { BaseMapModule } from '../base/BaseMapModule';\nimport { baseMapKnobCatalogue } from '../base/knobCatalogue';\nimport type { BaseMapKnobId, BaseMapKnobValueOf } from '../base/types/baseMapKnobs';\nimport { poisKnobCatalogue } from '../pois/knobCatalogue';\nimport { POIsModule } from '../pois/POIsModule';\nimport type { POIsKnobId, POIsKnobValueOf } from '../pois/types/poisKnobs';\nimport { type KnobEntry, type KnobTarget, resetKnob, setKnob } from '../shared';\nimport type { TomTomMap } from '../TomTomMap';\nimport { type StylingFoundationsKnobId, stylingFoundationsKnobCatalogue } from './knobCatalogue';\nimport { StylingFoundationsModule } from './StylingFoundationsModule';\nimport type { StylingFoundationsModuleConfig } from './types/stylingTypes';\n\n/**\n * The knob values a map preset sets, per module, each by its id in that module's catalogue.\n *\n * @group Map Styling\n */\nexport type MapPresetSettings = {\n /** {@link StylingFoundationsModule} knobs, from {@link stylingFoundationsKnobCatalogue}. */\n stylingFoundations?: Omit<StylingFoundationsModuleConfig, 'events'>;\n /** {@link BaseMapModule} knobs, from {@link baseMapKnobCatalogue}. */\n baseMap?: { [ID in BaseMapKnobId]?: BaseMapKnobValueOf<ID> };\n /** {@link POIsModule} knobs, from {@link poisKnobCatalogue}. */\n pois?: { [ID in POIsKnobId]?: POIsKnobValueOf<ID> };\n};\n\n// A preset as authored: everything the catalogue lists about it but its id, which is the key it\n// sits under.\ntype PresetDefinition = { name: string; description: string; settings: MapPresetSettings };\n\n/**\n * The map presets the SDK ships: named bundles of knob settings for common intents, across the\n * modules that hold the look of the map. Each is data over the knob catalogues, so a preset is\n * exactly as durable as the knobs it sets, and an app can start from one and adjust.\n * @ignore\n */\nexport const mapPresets = {\n 'data-viz': {\n name: 'Data visualisation',\n description:\n 'A quiet base for your own data on top: fewer labels and markers, thinner roads, no badges competing with the overlay.',\n settings: {\n stylingFoundations: { 'labels.sizeFactor': 0.9, 'symbols.sizeFactor': 0.9, 'roads.widthFactor': 0.8 },\n baseMap: { 'roads.exitNumbers.visible': false, 'roads.arrows.visible': false },\n pois: { 'microMarkers.visible': false, minZoom: 14 },\n },\n },\n 'night-driving': {\n name: 'Night driving',\n description: 'Bigger labels and shields, wider roads, POIs out of the way until close up.',\n settings: {\n stylingFoundations: { 'labels.sizeFactor': 1.2, 'symbols.sizeFactor': 1.2, 'roads.widthFactor': 1.2 },\n baseMap: { 'roads.exitNumbers.visible': true, 'roads.arrows.visible': true },\n pois: { 'microMarkers.visible': false, minZoom: 13 },\n },\n },\n minimal: {\n name: 'Minimal',\n description:\n 'The bare map: smaller labels, no road markings, exit badges or POI dots until close up. Hide buildings and shields with BaseMapModule.',\n settings: {\n stylingFoundations: { 'labels.sizeFactor': 0.9 },\n baseMap: {\n 'roads.exitNumbers.visible': false,\n 'roads.arrows.visible': false,\n 'roads.restricted.visible': false,\n 'roads.underConstruction.visible': false,\n },\n pois: { 'microMarkers.visible': false, minZoom: 16 },\n },\n },\n globe: {\n name: 'Globe',\n description: 'A globe with an atmosphere for the zoomed-out view; flattens to Mercator as you zoom in.',\n settings: {\n stylingFoundations: { 'view.projection': 'globe', 'view.sky': true },\n },\n },\n} as const satisfies Record<string, PresetDefinition>;\n\n/**\n * The id of a map preset, as accepted by {@link applyMapPreset}.\n *\n * @group Map Styling\n */\nexport type MapPresetId = keyof typeof mapPresets;\n\n/**\n * A named bundle of knob settings across the modules that hold the look of the map — TomTom's\n * cartographic opinions, in code. Apply one with {@link applyMapPreset}.\n *\n * @group Map Styling\n */\nexport type MapPreset = PresetDefinition & { id: MapPresetId };\n\n/**\n * Every map preset id, in catalogue order.\n *\n * @group Map Styling\n */\nexport const mapPresetIds = /* @__PURE__ */ Object.keys(mapPresets) as MapPresetId[];\n\n/**\n * Every map preset with its name, use case and the knob values it sets per module, in catalogue\n * order. Static data: no map is needed to read it.\n *\n * @group Map Styling\n */\nexport const mapPresetCatalogue: readonly MapPreset[] = /* @__PURE__ */ mapPresetIds.map((id) => ({\n id,\n ...structuredClone(mapPresets[id]),\n}));\n\ntype PresetModule = keyof MapPresetSettings;\n\n// Every knob some preset sets, per module: what applying another preset takes back first.\nconst presetKnobIds = (module: PresetModule): string[] => [\n ...new Set(\n Object.values<PresetDefinition>(mapPresets).flatMap(({ settings }) => Object.keys(settings[module] ?? {})),\n ),\n];\n\nconst applyPresetPart = <ID extends string>(\n target: KnobTarget<unknown>,\n catalogue: readonly KnobEntry<ID>[],\n values: Partial<Record<ID, unknown>> | undefined,\n resetIds: readonly string[],\n): void => {\n for (const id of resetIds) resetKnob(target, catalogue, id as ID);\n // Each value was typed against its knob where the preset was written.\n for (const [id, value] of Object.entries(values ?? {})) setKnob(target, catalogue, id as ID, value as never);\n};\n\n/**\n * Applies a map preset: a named bundle of knob settings the SDK ships for a common intent\n * (`data-viz`, `night-driving`, `minimal`, `globe`), over the {@link StylingFoundationsModule},\n * {@link BaseMapModule} and {@link POIsModule} of the map. See {@link mapPresetCatalogue} for what\n * each one sets.\n *\n * @param map - The map to apply the preset to; the modules are created where they do not exist yet.\n * @param options - `merge: true` lays the preset over the current settings. By default, the knobs\n * the other presets set are reset first, so switching presets leaves nothing of the last one; every\n * other knob keeps its value either way.\n * @throws `Error` for an unknown preset id.\n *\n * @example\n * ```typescript\n * await applyMapPreset(map, 'data-viz');\n * setKnob(styling, stylingFoundationsKnobCatalogue, 'labels.sizeFactor', 1.1); // then adjust\n *\n * await applyMapPreset(map, 'globe', { merge: true }); // keep the rest, switch to a globe with a sky\n * ```\n *\n * @group Map Styling\n */\nexport const applyMapPreset = async (\n map: TomTomMap,\n id: MapPresetId,\n options: { merge?: boolean } = {},\n): Promise<void> => {\n const preset = mapPresets[id];\n if (!preset) {\n throw new Error(`Unknown map preset '${id}'. Known presets: ${mapPresetIds.join(', ')}.`);\n }\n const settings: MapPresetSettings = preset.settings;\n const [styling, baseMap, pois] = await Promise.all([\n StylingFoundationsModule.get(map),\n BaseMapModule.get(map),\n POIsModule.get(map),\n ]);\n const resetIds = (module: PresetModule) => (options.merge ? [] : presetKnobIds(module));\n applyPresetPart<StylingFoundationsKnobId>(\n styling,\n stylingFoundationsKnobCatalogue,\n settings.stylingFoundations,\n resetIds('stylingFoundations'),\n );\n applyPresetPart(baseMap, baseMapKnobCatalogue, settings.baseMap, resetIds('baseMap'));\n applyPresetPart(pois, poisKnobCatalogue, settings.pois, resetIds('pois'));\n};\n","import type { KnobKind } from '../shared';\nimport { styleKnobScope } from '../styling/StyleKnobs';\nimport {\n colorKnob,\n type LiteralProperty,\n type StyleKnobDefinition,\n type StyleKnobTable,\n} from '../styling/styleKnobDefinitions';\nimport { styleLayerGroupSelector } from '../styling/styleLayerGroups';\nimport type { TerrainKnobId } from './types/terrainKnobs';\n\n/**\n * How the {@link TerrainModule} knobs that restyle the hillshade reach the loaded style; its\n * visibility is the module's own.\n * @ignore\n */\nexport const hillshadeStyleKnobs = /* @__PURE__ */ (() => {\n const hillshadeLayers = styleLayerGroupSelector('hillshade');\n const hillshadeLiteral = <KIND extends KnobKind>(\n kind: KIND,\n property: LiteralProperty,\n ): StyleKnobDefinition<KIND> => ({\n kind,\n mechanisms: [{ type: 'paintLiteral', property, layers: [hillshadeLayers] }],\n });\n return {\n 'terrain.hillshade.method': hillshadeLiteral('enum', 'hillshade-method'),\n 'terrain.hillshade.lightDirection': hillshadeLiteral('number', 'hillshade-illumination-direction'),\n 'terrain.hillshade.exaggeration': hillshadeLiteral('number', 'hillshade-exaggeration'),\n 'terrain.hillshade.maxZoom': { kind: 'number', mechanisms: [{ type: 'maxZoom', layers: [hillshadeLayers] }] },\n 'terrain.hillshade.shadowColor': colorKnob('hillshade-shadow-color', hillshadeLayers),\n 'terrain.hillshade.highlightColor': colorKnob('hillshade-highlight-color', hillshadeLayers),\n 'terrain.hillshade.accentColor': colorKnob('hillshade-accent-color', hillshadeLayers),\n } as const satisfies StyleKnobTable<'terrain.', TerrainKnobId>;\n})();\n\n/**\n * The hillshade's look, which restyles the style's own hillshade layer.\n * @ignore\n */\nexport const hillshadeLook = /* @__PURE__ */ styleKnobScope('TerrainModule', 'terrain.', hillshadeStyleKnobs);\n","import type { LayerSelector } from '../shared/layers/layerSelector';\nimport { incidentColorKnobIds } from '../shared/styleKnobsApplied';\nimport { styleKnobScope } from '../styling/StyleKnobs';\nimport { colorKnob, type StyleKnobTable, scaleKnob } from '../styling/styleKnobDefinitions';\nimport { styleLayerGroupSelector } from '../styling/styleLayerGroups';\nimport type { TrafficFlowKnobId, TrafficIncidentsKnobId } from './types/trafficKnobs';\n\n// Flow and incidents draw the same way — one inner line plus one outline layer per class — so both\n// take the same selectors, told apart only by the tile source they come from. Built inside each\n// table, so a bundle that reads neither builds none.\nconst trafficLook = (group: 'trafficFlow' | 'trafficIncidents') => {\n const lines: LayerSelector = { ...styleLayerGroupSelector(group), layerTypes: ['line'] };\n const layers = (fragment: string, outline: boolean): LayerSelector => ({\n ...lines,\n idIncludes: [outline ? `${fragment} outline` : fragment],\n ...(outline ? {} : { idExcludes: ['outline'] }),\n });\n return {\n // A congestion class: the inner line is the knob, the outline is re-derived as the shade the\n // style made of it.\n levelColor: (fragment: string) => colorKnob('line-color', layers(fragment, false), layers(fragment, true)),\n closedColor: () => colorKnob('line-color', layers('closed road', true)),\n widthFactor: () => scaleKnob('line-width', lines),\n };\n};\n\n/**\n * How the {@link TrafficFlowModule} knobs that restyle the flow lines reach the loaded style.\n * @ignore\n */\nexport const trafficFlowStyleKnobs = /* @__PURE__ */ (() => {\n const flow = trafficLook('trafficFlow');\n return {\n 'traffic.flow.colors.free': flow.levelColor('free flow'),\n 'traffic.flow.colors.slow': flow.levelColor('slow flow'),\n 'traffic.flow.colors.queueing': flow.levelColor('queueing flow'),\n 'traffic.flow.colors.stationary': flow.levelColor('stationary flow'),\n 'traffic.flow.colors.closed': flow.closedColor(),\n 'traffic.flow.widthFactor': flow.widthFactor(),\n } as const satisfies StyleKnobTable<'traffic.flow.', TrafficFlowKnobId>;\n})();\n\n/**\n * How the {@link TrafficIncidentsModule} knobs that restyle the incident lines reach the loaded style.\n * @ignore\n */\nexport const trafficIncidentsStyleKnobs = /* @__PURE__ */ (() => {\n const incidents = trafficLook('trafficIncidents');\n return {\n [incidentColorKnobIds.minor]: incidents.levelColor('minor jam'),\n [incidentColorKnobIds.moderate]: incidents.levelColor('moderate jam'),\n [incidentColorKnobIds.major]: incidents.levelColor('major jam'),\n [incidentColorKnobIds.indefinite]: incidents.closedColor(),\n 'traffic.incidents.widthFactor': incidents.widthFactor(),\n } as const satisfies StyleKnobTable<'traffic.incidents.', TrafficIncidentsKnobId>;\n})();\n\n/** @ignore */\nexport const trafficFlowLook = /* @__PURE__ */ styleKnobScope(\n 'TrafficFlowModule',\n 'traffic.flow.',\n trafficFlowStyleKnobs,\n);\n\n/** @ignore */\nexport const trafficIncidentsLook = /* @__PURE__ */ styleKnobScope(\n 'TrafficIncidentsModule',\n 'traffic.incidents.',\n trafficIncidentsStyleKnobs,\n);\n","import { poisLook } from '../pois/poisStyleKnobs';\nimport type { TomTomMap } from '../TomTomMap';\nimport { hillshadeLook } from '../terrain/hillshadeStyleKnobs';\nimport { trafficFlowLook, trafficIncidentsLook } from '../traffic/trafficStyleKnobs';\nimport type { StyleColorKnobId } from './styleKnobDefinitions';\nimport { stylingFoundationsLook } from './stylingFoundationsStyleKnobs';\n\n// Every module's style knobs, in catalogue order. Only what reads a colour by its map-wide id comes\n// here, so a bundle with one of these modules carries no other module's knobs.\nexport const styleKnobScopes = [\n stylingFoundationsLook,\n poisLook,\n trafficFlowLook,\n trafficIncidentsLook,\n hillshadeLook,\n] as const;\n\n/**\n * Every style colour knob id, in catalogue order. Name a colour of the map through them rather than\n * hard-coding it, as the map-effects plugin's `bloom.only` does.\n *\n * @group Map Styling\n */\nexport const styleColorKnobIds: readonly StyleColorKnobId[] = /* @__PURE__ */ (() =>\n styleKnobScopes.flatMap((scope): StyleColorKnobId[] => scope.colorKnobIds))();\n\n/**\n * The colour a style colour knob is at on the map: the one its module was given, one the loaded style\n * derives from a set map colour, or the loaded style's own — whether or not that module exists.\n *\n * @param id - The colour's map-wide id, from {@link styleColorKnobIds}.\n * @returns The colour, or `undefined` where the style draws it with an expression no single colour\n * stands for, or has no layer for it.\n *\n * @example\n * ```typescript\n * const slow = getStyleColor(map, 'traffic.flow.colors.slow');\n * const water = getStyleColor(map, 'colors.water');\n * ```\n *\n * @group Map Styling\n */\nexport const getStyleColor = (map: TomTomMap, id: StyleColorKnobId): string | undefined => {\n const scope = styleKnobScopes.find((candidate) => candidate.has(id));\n const value = scope?.engineOf(map).valueOf(id);\n return typeof value === 'string' ? value : undefined;\n};\n","import type { KnobEntry, KnobKind, KnobRange, KnobValueOf, MapModuleCommonConfig } from '../../shared';\nimport type { StylingFoundationsKnobId, StylingFoundationsKnobValueOf } from '../knobCatalogue';\n\n/**\n * What kind of value a styling knob takes — a subset of the shared {@link KnobKind} vocabulary.\n *\n * - `factor`: a multiplier over what the style already does (`1` = unchanged), e.g. label size.\n * - `toggle`: `true`/`false`, e.g. whether the sky is drawn.\n * - `color`: a CSS colour string (`#rrggbb`, `hsl(…)`, `rgb(…)`, a named colour).\n * - `enum`: one of the knob's `options`, e.g. the projection.\n *\n * @group Map Styling\n */\nexport const stylingFoundationsKnobKinds = ['factor', 'toggle', 'color', 'enum'] as const satisfies readonly KnobKind[];\n\n/**\n * @group Map Styling\n */\nexport type StylingFoundationsKnobKind = (typeof stylingFoundationsKnobKinds)[number];\n\n/**\n * Which styles a knob works on.\n *\n * - `any-style`: every style, including a custom one of your own.\n * - `tomtom-styles`: the TomTom styles this SDK version is verified against. On any other style the\n * knob reports itself unavailable instead of doing nothing silently.\n *\n * @group Map Styling\n */\nexport const stylingFoundationsKnobAppliesTo = ['any-style', 'tomtom-styles'] as const;\n\n/**\n * @group Map Styling\n */\nexport type StylingFoundationsKnobAppliesTo = (typeof stylingFoundationsKnobAppliesTo)[number];\n\n/**\n * A value a styling knob can hold; which one depends on the knob's {@link StylingFoundationsKnobKind}.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsKnobValue = KnobValueOf<StylingFoundationsKnobKind>;\n\n/**\n * The valid range of a numeric knob, in the units of the knob (`factor` knobs are unitless).\n * Styling always ships a slider `step`; the shared {@link KnobRange} leaves it optional.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsKnobRange = KnobRange & { step: number };\n\n/**\n * One entry of {@link stylingFoundationsKnobCatalogue}: what a UI or a coding agent needs to offer the knob and\n * validate a value for it, with no map loaded. It carries no `default`: the loaded style decides,\n * and {@link getKnob} reads the value in force.\n *\n * @group Map Styling\n */\nexport type StylingFoundationsKnob = KnobEntry<StylingFoundationsKnobId, StylingFoundationsKnobKind> & {\n /** The knob's id, as accepted by {@link setKnob}. */\n id: StylingFoundationsKnobId;\n range?: StylingFoundationsKnobRange;\n default?: never;\n /** Which styles the knob works on; see {@link stylingFoundationsKnobAppliesTo}. */\n appliesTo: StylingFoundationsKnobAppliesTo;\n};\n\n/**\n * A serializable set of styling knob values — what {@link StylingFoundationsModule.getConfig} returns and\n * {@link StylingFoundationsModule.applyConfig} accepts. Persist it, put it in a pull request, or ship it in a\n * preset: re-applying it on a later SDK version keeps working for as long as the knob ids do.\n *\n * @example\n * ```typescript\n * const settings: StylingFoundationsModuleConfig = {\n * 'labels.sizeFactor': 1.2,\n * 'colors.water': '#7cc4e8',\n * 'view.projection': 'globe',\n * };\n * styling.applyConfig(settings);\n * ```\n *\n * @group Map Styling\n */\nexport type StylingFoundationsModuleConfig = MapModuleCommonConfig & {\n [K in StylingFoundationsKnobId]?: StylingFoundationsKnobValueOf<K>;\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 */\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","/**\n * Probes `.maplibregl-ctrl-top-left { pointer-events: none }`: computed style sees the rules of every\n * stylesheet, including a bundler's `<link>` and cross-origin sheets whose text cannot be read.\n */\nconst isMapLibreCSSApplied = (): boolean => {\n const probe = document.createElement('div');\n probe.className = 'maplibregl-ctrl-top-left';\n document.documentElement.append(probe);\n const applied = getComputedStyle(probe).pointerEvents === 'none';\n probe.remove();\n return applied;\n};\n\nconst applyLayered = (css: string): void => {\n const layeredCSS = `@layer maplibre-gl{${css}}`;\n if (Reflect.has(document, 'adoptedStyleSheets')) {\n const sheet = new CSSStyleSheet();\n sheet.replaceSync(layeredCSS);\n document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet];\n } else {\n const style = document.createElement('style');\n style.textContent = layeredCSS;\n document.head.append(style);\n }\n};\n\nlet isLoading = false;\n\n/**\n * Starts applying the MapLibre stylesheet bundled with the SDK unless the page already has it, so the map\n * renders correctly without the integrator importing `maplibre-gl/dist/maplibre-gl.css`. Until it lands,\n * the map's controls are unstyled.\n *\n * The CSS is a lazy chunk of the SDK bundle, served from wherever the app serves the SDK, and loaded only\n * when the page lacks it. It is wrapped in a cascade layer, so any unlayered page CSS (a later-loaded copy\n * of the MapLibre CSS, or overrides of it) wins. A constructed stylesheet is not governed by CSP `style-src`.\n */\nexport const ensureMapLibreCSS = (): void => {\n if (isLoading || typeof document === 'undefined' || isMapLibreCSSApplied()) {\n return;\n }\n\n isLoading = true;\n import('maplibre-gl/dist/maplibre-gl.css?raw')\n .then(({ default: css }) => applyLayered(css))\n .finally(() => {\n isLoading = false;\n })\n .catch((error) => console.error('Could not apply the MapLibre CSS', error));\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 GeopoliticalView, type Language, mergeFromGlobal } from '@tomtom-org/maps-sdk/core';\nimport { isEqual } from 'lodash-es';\nimport type { ErrorEvent, ExpressionSpecification, StyleSpecification } from 'maplibre-gl';\nimport { Map, setWorkerCount } from 'maplibre-gl';\nimport type { InternalTomTomMapParams, MapEventsConfig, MapLibreOptions, StyleInput, TomTomMapParams } from './init';\nimport { buildMapOptions } from './init/buildMapOptions';\nimport { ensureMapLibreCSS } from './init/mapLibreCSS';\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 { ModuleSourcesAndLayers } from './shared/ModuleSourcesAndLayers';\nimport {\n addPinCategoriesSpriteToStyle,\n detectStyleLightDarkTheme,\n getDeclaredLightDarkTheme,\n getStyleLightDarkTheme,\n isCustomStyle,\n reloadVectorTiles,\n transformRequest,\n} from './shared/mapUtils';\nimport type { MapColors, StyleKnobId } from './styling';\nimport type { StyleKnobSettings } from './styling/StyleKnobs';\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: the SDK awaits it before handing the new\n * style to MapLibre, so nothing of the old style is torn down while it runs.\n *\n * @param context - What kind of switch is happening; see {@link StyleChangeContext}.\n * @returns void or a Promise that resolves when preparation is complete\n */\n onStyleAboutToChange?: (context: StyleChangeContext) => 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 * handlers run one after the other, in registration order, and the promise returned by\n * {@link TomTomMap.setStyle} resolves only once the last one has finished.\n *\n * @param context - What kind of switch is happening; see {@link StyleChangeContext}.\n * @returns void or a Promise that resolves when reinitialization is complete\n */\n onStyleChanged?: (context: StyleChangeContext) => void | Promise<void>;\n /**\n * Where this handler runs relative to the others: lower values run first, equal values run in\n * registration order. Defaults to `0`, which is also what the SDK's data and visibility modules\n * use; the SDK's style knobs land at `90` and the {@link StylingFoundationsModule}'s raw layer edits at\n * `100`, so semantic styling always lands on top of whatever the modules restored.\n *\n * @default 0\n */\n priority?: number;\n};\n\n/**\n * What a {@link StyleChangeHandler} is told about the style switch it is reacting to.\n *\n * @remarks\n * The SDK's own modules read `resetState` to decide between restoring themselves (data, config,\n * filters) onto the new style, or re-binding to it with defaults and nothing shown. Your handlers\n * get the same information, so a custom overlay can make the same choice.\n *\n * @example\n * ```typescript\n * map.addStyleChangeHandler({\n * onStyleChanged: ({ resetState }) => {\n * if (resetState) dropCustomLayers();\n * else reattachCustomLayers();\n * },\n * });\n * ```\n *\n * @group Map Style\n */\nexport type StyleChangeContext = {\n /**\n * `true` for a clean switch (`setStyle(style, { resetState: true })`), `false` when the switch\n * carries SDK state over (`setStyle(style)` or `setStyle(style, { resetState: false })`).\n */\n resetState: boolean;\n};\n\n/**\n * Options accepted by {@link TomTomMap.setStyle}.\n *\n * @group Map Style\n */\nexport type SetStyleOptions = {\n /**\n * Whether to drop the SDK state instead of carrying it over to the new style.\n *\n * - `false` (default): module data and configuration, filters and the style parts of the\n * previous style (traffic, terrain) are restored onto the new style.\n * - `true`: a clean switch. Modules re-bind to the new style with default configuration and\n * nothing shown, and only the style parts named in the new style input are loaded.\n *\n * The map language and geopolitical view are not SDK state: both survive either case, and only\n * {@link TomTomMap.setLanguage} and {@link TomTomMap.setGeopoliticalView} change them.\n *\n * Registered {@link StyleChangeHandler}s run in both cases and receive the value in their\n * {@link StyleChangeContext}.\n *\n * @default false\n */\n resetState?: boolean;\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 * - Geopolitical views of disputed borders and names, switchable at runtime\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-org/maps-sdk/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/classes/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 /**\n * Whether the loaded style draws a light or a dark map.\n *\n * @remarks\n * The SDK's own overlays read it to pick text and halo colours that stay legible over the\n * base map, and it is the property to bind your own UI to rather than parsing the style ID.\n *\n * A standard style is classified from its ID. A custom style reads `'light'` until it has\n * loaded — nothing about it is known before that — and is then classified from the colour its\n * `background` layer paints the canvas with; a custom style with no readable background colour\n * stays `'light'`. So read it inside an\n * {@link TomTomMap.addStyleChangeHandler | onStyleChanged} handler, or after awaiting\n * {@link setStyle}, rather than right after calling it.\n *\n * @example\n * ```typescript\n * await map.setStyle({ type: 'custom', url: 'https://example.com/my-style.json' });\n * document.body.classList.toggle('dark-mode', map.styleLightDarkTheme === 'dark');\n * ```\n */\n get styleLightDarkTheme(): LightDark {\n return this._styleLightDarkTheme;\n }\n\n private _styleLightDarkTheme: LightDark;\n\n private _mapColors: Readonly<MapColors> = {};\n private readonly mapColorsHandlers: ((colors: Readonly<MapColors>) => void)[] = [];\n\n /**\n * The map's semantic colours as the {@link StylingFoundationsModule} last published them: the ten base-map\n * colours in effect (a `colors.*` knob, or the style's own value), plus `accent`, the colour for\n * what is drawn on top of the map. Empty until a styling foundations module exists.\n *\n * @remarks\n * This is how a theme reaches the whole map. Routes, geometries and places default to `accent`\n * when the caller chose no colour of their own, and follow it when it changes; a colour given\n * explicitly always wins. Without an accent, each module keeps its own default.\n *\n * `accent` is set by {@link StylingFoundationsModule.setMapColors} — typically fed by the map-theme plugin's\n * `deriveMapColors` — or the `colors.accent` knob.\n *\n * @example\n * ```typescript\n * styling.setMapColors(deriveMapColors(['#1d3f8a', '#e63312', '#f5d000', '#f4f1ea'])); // map-theme plugin\n * const routing = await RoutingModule.create(map); // routes draw in map.mapColors.accent\n * ```\n */\n get mapColors(): Readonly<MapColors> {\n return this._mapColors;\n }\n\n /**\n * Publishes the map colours; the styling foundations module calls this after every colour change.\n * @ignore\n */\n _setMapColors(colors: MapColors): void {\n if (isEqual(colors, this._mapColors)) return;\n\n this._mapColors = colors;\n for (const handler of this.mapColorsHandlers) handler(this._mapColors);\n }\n\n /**\n * Runs `handler` whenever {@link mapColors} changes, for as long as the map lives.\n * @ignore\n */\n _onMapColorsChange(handler: (colors: Readonly<MapColors>) => void): void {\n this.mapColorsHandlers.push(handler);\n }\n\n private styleKnobSettings: Readonly<StyleKnobSettings> = {};\n private readonly styleKnobsAppliedHandlers: ((knobIds: ReadonlySet<StyleKnobId>) => void)[] = [];\n\n /**\n * The style knobs in force, by map-wide id, as the modules that restyle the loaded style last\n * applied them. Empty until one of them sets a knob.\n * @ignore\n */\n get _styleKnobSettings(): Readonly<StyleKnobSettings> {\n return this.styleKnobSettings;\n }\n\n /**\n * Publishes the style knobs in force each time they have been applied to the loaded style, with\n * the ids of those rewritten or whose value moved.\n * @ignore\n */\n _setStyleKnobSettings(settings: StyleKnobSettings, knobIds: ReadonlySet<StyleKnobId>): void {\n this.styleKnobSettings = settings;\n if (!knobIds.size) return;\n\n for (const handler of this.styleKnobsAppliedHandlers) handler(knobIds);\n }\n\n /**\n * Runs `handler` each time style knobs have been applied to the loaded style — after any knob\n * change and after every style change — for as long as the map lives. It is how a module drawing\n * its own layers in a look borrowed from the style's follows the knobs that restyle it.\n * @ignore\n */\n _onStyleKnobsApplied(handler: (knobIds: ReadonlySet<StyleKnobId>) => void): void {\n this.styleKnobsAppliedHandlers.push(handler);\n }\n\n private readonly styleChangeHandlers: StyleChangeHandler[] = [];\n\n // The exact style value last handed to MapLibre, so `loadIntoMapLibre` can tell when it is\n // being asked for the style MapLibre already holds.\n private styleGivenToMapLibre: StyleSpecification | string;\n\n // Incremented by every setStyle call. A call whose number is no longer current has been\n // superseded by a newer one and steps aside instead of applying a stale style.\n private styleRequestSequence = 0;\n\n // The MapLibre style load currently in flight, so the next one can wait for it: see\n // `applyStyle` for why two of them must not overlap.\n private styleLoadInFlight: Promise<void> | undefined;\n\n // The ids of the layers the loaded style declares; see `_isStyleLayer`.\n private styleLayerIds: ReadonlySet<string> = new Set();\n\n private readonly moduleSourcesAndLayers: ModuleSourcesAndLayers;\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', 'terrain']\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 * maxZoom: 18,\n * minZoom: 8\n * }\n * });\n * ```\n *\n * @see {@link MapLibreOptions}\n * @see {@link TomTomMapParams}\n * @see {@link https://maplibre.org/maplibre-gl-js/docs/API/classes/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/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 ensureMapLibreCSS();\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.styleGivenToMapLibre = buildStyleInput(this._params);\n this.mapLibreMap = new Map(buildMapOptions(this._params));\n this.moduleSourcesAndLayers = ModuleSourcesAndLayers.for(this.mapLibreMap);\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.recordStyleLayers();\n void this.handleStyleData();\n });\n this._eventsProxy = new EventsProxy(this.mapLibreMap, this._params?.events);\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, see {@link SetStyleOptions}.\n * @returns A promise that resolves once the new style is loaded and every registered\n * {@link StyleChangeHandler} has finished — including the SDK modules restoring themselves.\n * Awaiting it is optional; it is the moment after which the map is safe to read and draw on again.\n * @throws If the style itself fails to load, leaving {@link mapReady} `false` and the map on\n * the style it had. A tile or source that fails to load does not fail the switch.\n *\n * @remarks\n * **Carried-over state (the default):**\n * - Merges style parts from the previous style with the new one\n * - Restores SDK module data and configuration (traffic, routes, POIs, etc.)\n *\n * **Clean switch (resetState: true):**\n * - Applies exactly the style you passed, without merging previous style parts\n * - SDK modules re-bind to the new style with default configuration and nothing shown\n * - Useful for complete style resets\n *\n * Both keep the map language: it is configuration, not state, and it survives every style\n * change until {@link setLanguage} changes it.\n *\n * **Behavior:**\n * - Sets {@link mapReady} to `false` for the duration of the transition\n * - Awaits every `onStyleAboutToChange` handler, then hands the new style to MapLibre\n * - Once the style has loaded, sets {@link mapReady} back to `true` and awaits every\n * `onStyleChanged` handler in registration order\n * - A `setStyle` call made while another one is still in flight supersedes it: the older\n * call's style is never applied and its promise resolves as soon as that is known\n * - Passing the style already loaded runs the whole lifecycle without reloading it, which is\n * how `resetState: true` doubles as a reset of the SDK modules over the current style\n * @example\n * Simple style change:\n * ```typescript\n * // Switch to dark mode\n * map.setStyle('standardDark');\n * ```\n *\n * @example\n * Wait for the switch to complete before drawing on the map:\n * ```typescript\n * await map.setStyle('standardDark');\n * // modules have restored themselves; map.mapReady is true again\n * ```\n *\n * @example\n * Style change with detailed configuration:\n * ```typescript\n * map.setStyle({\n * type: 'standard',\n * id: 'standardLight',\n * include: ['trafficFlow', 'terrain']\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', { resetState: true });\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 getStyleInput} - For retrieving the current style input\n * @see [Map Styles Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/styles)\n */\n setStyle = (style: StyleInput, options: SetStyleOptions = {}): Promise<void> => {\n const context: StyleChangeContext = { resetState: options.resetState ?? false };\n this.mapReady = false;\n const requestNumber = ++this.styleRequestSequence;\n\n // The new style is recorded synchronously so `getStyleInput()` answers with it right away, and\n // so a second call arriving while handlers run sees what it is superseding.\n const effectiveStyle = context.resetState ? style : withPreviousStyleParts(style, this._params.style);\n this._params = { ...this._params, style: effectiveStyle };\n this._styleLightDarkTheme = getStyleLightDarkTheme(effectiveStyle);\n return this.applyStyle(context, requestNumber);\n };\n\n private async applyStyle(context: StyleChangeContext, requestNumber: number): Promise<void> {\n // Modules mark themselves as not ready here; custom handlers save what they need to.\n await this.notifyHandlers('onStyleAboutToChange', context);\n if (this.isSuperseded(requestNumber)) return;\n\n // One load at a time. MapLibre aborts the request of a style it is asked to replace, but a\n // response that had already arrived is applied regardless, so two overlapping loads can\n // land in either order and leave the superseded style on the map. Waiting for the load in\n // flight — however it ends — keeps the last style asked for the last one applied.\n await this.styleLoadInFlight?.catch(() => undefined);\n if (this.isSuperseded(requestNumber)) return;\n\n const styleLoad = this.loadIntoMapLibre(!context.resetState);\n this.styleLoadInFlight = styleLoad;\n try {\n await styleLoad;\n } finally {\n if (this.styleLoadInFlight === styleLoad) {\n this.styleLoadInFlight = undefined;\n }\n }\n // A newer setStyle call owns the style that just loaded (or is about to load).\n if (this.isSuperseded(requestNumber)) return;\n\n try {\n await this.handleStyleData(context);\n } catch (error) {\n console.error(error);\n }\n // A newer switch has carried over what this one left unadopted, for its own modules to adopt.\n if (!this.isSuperseded(requestNumber)) this.moduleSourcesAndLayers.dropUnadopted();\n }\n\n // Hands the style built from the current params to MapLibre and resolves once it has loaded,\n // or rejects if the style itself fails to arrive.\n //\n // Resolves right away when that style is the one MapLibre already holds: MapLibre diffs it to\n // no operations, does no work and never fires `style.load`, so waiting for that event would\n // hang forever. The switch still has to run on the SDK's side — re-applying the loaded style\n // with `resetState: true` is how a caller asks the modules to reset.\n //\n // With `carryModuleLayers`, the module sources and layers stay through the switch for the modules\n // to adopt as they restore; see `ModuleSourcesAndLayers`.\n private async loadIntoMapLibre(carryModuleLayers: boolean): Promise<void> {\n const style = buildStyleInput(this._params);\n if (isEqual(style, this.styleGivenToMapLibre)) return;\n\n const previousStyle = this.styleGivenToMapLibre;\n this.styleGivenToMapLibre = style;\n\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 await new Promise<void>((resolve, reject) => {\n const onStyleLoad = () => {\n this.mapLibreMap.off('error', onError);\n this.recordStyleLayers();\n resolve();\n };\n const onError = (event: ErrorEvent) => {\n // A tile or source failure carries the source's id and is not fatal. One without\n // it, before `style.load`, is the style itself failing: no load will ever follow.\n if ('sourceId' in event) return;\n\n this.mapLibreMap.off('style.load', onStyleLoad);\n this.mapLibreMap.off('error', onError);\n // MapLibre still holds the previous style, so a retry is a switch, not a no-op.\n this.styleGivenToMapLibre = previousStyle;\n // MapLibre only promises an object with a message, so a `catch` gets a real Error.\n reject(event.error instanceof Error ? event.error : new Error(event.error.message));\n };\n this.mapLibreMap.once('style.load', onStyleLoad);\n this.mapLibreMap.on('error', onError);\n this.mapLibreMap.setStyle(style, {\n validate: false,\n transformStyle: (previous, next) =>\n this.moduleSourcesAndLayers.carryInto(carryModuleLayers ? previous : undefined, next),\n });\n });\n }\n\n private isSuperseded(requestNumber: number): boolean {\n return requestNumber !== this.styleRequestSequence;\n }\n\n // Read at `style.load`, before any module restores, so the only layers on the map besides the\n // style's own are the ones a switch carried over.\n private recordStyleLayers(): void {\n this.styleLayerIds = new Set(\n this.mapLibreMap\n .getStyle()\n .layers.map((layer) => layer.id)\n .filter((layerId) => !this.moduleSourcesAndLayers.isCarried(layerId)),\n );\n }\n\n /**\n * Whether the loaded style itself declares the layer, as opposed to a module or the caller adding it.\n * @ignore\n */\n _isStyleLayer(layerId: string): boolean {\n return this.styleLayerIds.has(layerId);\n }\n\n // Runs one lifecycle callback on every handler, one after the other, by ascending priority and\n // then registration order (sort is stable). Iterates a snapshot so a handler that unsubscribes\n // itself (or another) during the callback doesn't shift the live array out from under the\n // loop. A failing handler is logged and does not stop the others.\n private async notifyHandlers(\n callback: 'onStyleAboutToChange' | 'onStyleChanged',\n context: StyleChangeContext,\n ): Promise<void> {\n const handlers = [...this.styleChangeHandlers].sort((a, b) => (a.priority ?? 0) - (b.priority ?? 0));\n for (const handler of handlers) {\n try {\n await handler[callback]?.(context);\n } catch (error) {\n console.error(error);\n }\n }\n }\n\n /**\n * The {@link StyleInput} the map was last given, through its parameters or {@link setStyle}.\n *\n * The input as it was given, not the resolved style specification: that is\n * {@link StylingFoundationsModule.exportStyle}, or `mapLibreMap.getStyle()`.\n *\n * @example\n * ```typescript\n * const style = map.getStyleInput();\n * if (style) map.setStyle(style);\n * ```\n */\n getStyleInput = (): StyleInput | undefined => {\n return this._params.style;\n };\n\n /**\n * Re-tags the `tomtom-user-agent` header this map sends on its MapLibre requests (style, tiles,\n * sprites, glyphs), letting a product embedding the SDK count the traffic under its own name,\n * by convention as `<Name>/<version>`. Prefer setting it on the global config when the identity\n * is known before the map exists.\n *\n * Takes effect from the next request; anything already fetched or cached keeps its old tag.\n *\n * @ignore\n */\n _setTomTomUserAgent(userAgent: string): void {\n // Written through a cast: the key is deliberately absent from the params types.\n this._params = { ...this._params, 'tomtom-user-agent': userAgent } as InternalTomTomMapParams;\n // MapLibre only accepts a `transformRequest` at construction, so re-install it.\n this.mapLibreMap.setTransformRequest(transformRequest(this._params));\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: ExpressionSpecification = 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 * - The language persists across every style change, a clean switch\n * ({@link SetStyleOptions.resetState}) included: it is map configuration rather than SDK\n * state, and this method is the only thing that changes it\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 * Changes the geopolitical view of the map: the borders, and the territory and sea names, it\n * draws for disputed territories.\n *\n * @param geopoliticalView The view to draw, or `undefined` for the default view of the country\n * the map is viewed from. Set it at construction through {@link core!GlobalConfig.geopoliticalView}.\n *\n * @remarks\n * - The visible tiles reload in place, and the view persists across every style change.\n * - Applies to the TomTom Orbis vector tiles, those of a custom style included. The tiles do not\n * serve `CN` and `RU`, which leave the default view; see {@link core!GeopoliticalView}.\n * - Services keep the view of the global configuration, or of each call.\n */\n setGeopoliticalView(geopoliticalView: GeopoliticalView | undefined): void {\n this._params = { ...this._params, geopoliticalView };\n this.mapLibreMap.setTransformRequest(transformRequest(this._params));\n if (this.mapReady) {\n reloadVectorTiles(this.mapLibreMap);\n }\n }\n\n /**\n * Changes the map's user events configuration at runtime.\n *\n * @param config Merged into the current configuration, set at construction through\n * {@link TomTomMapParams.events}: what it leaves out, or sets `undefined`, keeps its value.\n *\n * @remarks\n * A module's own `events` config still overrides the cursor and `hoverGracePeriodMS` for its\n * features. A long hover or a grace period already running keeps the delay it started with.\n *\n * @example\n * ```typescript\n * map.updateEventsConfig({ longHoverDelayOnStillMapMS: 600, hoverGracePeriodMS: 1000 });\n * ```\n */\n updateEventsConfig(config: MapEventsConfig): void {\n this._eventsProxy.updateConfig(config);\n }\n\n /**\n * Retrieves the current visible map area as a GeoJSON bounding box.\n *\n * @returns A {@link https://datatracker.ietf.org/doc/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://datatracker.ietf.org/doc/html/rfc7946#section-5 | GeoJSON BBox Specification}\n * @see {@link https://maplibre.org/maplibre-gl-js/docs/API/classes/LngLatBounds/ | MapLibre LngLatBounds}\n */\n getBBox(): BBox {\n return this.mapLibreMap.getBounds().toArray().flat() as BBox;\n }\n\n // Finishes a style load: the initial one (no context) or a `setStyle` switch (its context).\n private async handleStyleData(context?: StyleChangeContext): Promise<void> {\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 // A custom style that declares no theme tells us nothing about it until it is loaded; now\n // that it is, read the theme off the style itself so overlays (places, routes) pick text\n // colours that stay legible. A declared theme is already in place and is left alone.\n if (isCustomStyle(this._params.style) && !getDeclaredLightDarkTheme(this._params.style)) {\n this._styleLightDarkTheme =\n detectStyleLightDarkTheme(this.mapLibreMap.getStyle()) ?? this._styleLightDarkTheme;\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._styleLightDarkTheme, this.mapLibreMap);\n // The language is map configuration, not carried-over state, so it is re-applied on every\n // style load, a clean switch included: a new style resets the `text-field` of every layer.\n // Only `setLanguage` changes it.\n this._params.language && this._setLanguage(this._params.language);\n\n this.mapReady = true;\n if (context) {\n // Modules restore (or reset) themselves here, then custom handlers run, all in order.\n await this.notifyHandlers('onStyleChanged', context);\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 * - Async callbacks are awaited one after the other; the promise returned by {@link setStyle}\n * resolves once the last one has finished\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 * - Handlers run for a clean switch as well as for one that carries state over, and receive\n * which one it is in their {@link StyleChangeContext}\n * - Handlers persist for the lifetime of the TomTomMap instance unless unsubscribed\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.getStyleInput());\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.getStyleInput();\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\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 { MapModuleCommonConfig } from '../../shared';\n\n/**\n * How the hillshade shades the relief: `standard` with one light, `basic`, `igor` (softer),\n * `combined`, or `multidirectional` with four lights.\n *\n * @group Terrain\n */\nexport const hillshadeMethods = ['standard', 'basic', 'igor', 'combined', 'multidirectional'] as const;\n\n/**\n * One of {@link hillshadeMethods}.\n *\n * @group Terrain\n */\nexport type HillshadeMethod = (typeof hillshadeMethods)[number];\n\n/**\n * The hillshade of the {@link TerrainModule}: light and shadow on the map surface that reveal\n * hills, valleys and mountains, at any camera pitch.\n *\n * @remarks\n * Everything but `visible` restyles the style's own hillshade layer, and reads the style's own value\n * while unset. Those settings only show once `visible` does.\n *\n * @group Terrain\n */\nexport type TerrainHillshadeConfig = {\n /**\n * Shows the hillshade.\n *\n * @default false\n */\n visible?: boolean;\n\n /**\n * How the relief is shaded.\n *\n * @default The style's own; `standard` on TomTom styles.\n */\n method?: HillshadeMethod;\n\n /**\n * Direction the light comes from, in degrees clockwise from north (335 is the cartographic default).\n */\n lightDirection?: number;\n\n /**\n * Strength of the shading (0 = flat), not the height of the 3D surface (`elevation.exaggeration`).\n * The standard styles ramp it to zero by zoom 13; setting it replaces the ramp with a constant.\n */\n exaggeration?: number;\n\n /**\n * Zoom level up to which the shading is drawn: the standard styles stop at 13, and 22 keeps it at\n * every zoom.\n */\n maxZoom?: number;\n\n /** Colour of the shaded slopes. */\n shadowColor?: string;\n\n /** Colour of the lit slopes. */\n highlightColor?: string;\n\n /** Colour accentuating the steepest terrain. */\n accentColor?: string;\n};\n\n/**\n * The 3D surface of the {@link TerrainModule}, raised to the elevation data. Shows best with a\n * pitched camera.\n *\n * @group Terrain\n */\nexport type TerrainElevationConfig = {\n /**\n * Raises the map surface in 3D, or flattens it.\n *\n * @default The map's own terrain, if its style or MapLibre code raised one; none on TomTom styles.\n */\n visible?: boolean;\n\n /**\n * Vertical scaling of the surface: `1` is true elevation, higher values emphasise the relief.\n * Kept while `visible` is off, so turning it back on restores it.\n *\n * `hillshade.exaggeration` is a different thing: how strongly the slopes are shaded, raising nothing.\n *\n * @default 1\n */\n exaggeration?: number;\n};\n\n/**\n * Configuration of the {@link TerrainModule}: the two ways it draws the relief of the style's\n * elevation data.\n *\n * @remarks\n * Unset, the hillshade is hidden like the other style parts the SDK adds (traffic flow and\n * incidents), and the surface is left as the map draws it: flat on TomTom styles. So\n * `resetConfig()` returns a TomTom style to flat and unshaded.\n *\n * @example\n * ```typescript\n * const config: TerrainModuleConfig = {\n * hillshade: { visible: true, method: 'multidirectional', shadowColor: '#3b2f2f' },\n * elevation: { visible: true, exaggeration: 1.5 },\n * };\n * ```\n *\n * @group Terrain\n */\nexport type TerrainModuleConfig = MapModuleCommonConfig & {\n /**\n * The hillshade shading.\n */\n hillshade?: TerrainHillshadeConfig;\n\n /**\n * The 3D surface.\n */\n elevation?: TerrainElevationConfig;\n};\n","import type { KnobRange } from '../shared';\nimport { type KnobDefinition, withIds } from '../shared/knobUtils';\nimport { DEFAULT_EXAGGERATION } from './constants';\nimport type { TerrainKnob, TerrainKnobId, TerrainKnobValueOf } from './types/terrainKnobs';\nimport { hillshadeMethods } from './types/terrainModuleConfig';\n\nconst ELEVATION_EXAGGERATION_RANGE: KnobRange = { min: 0.5, max: 3, step: 0.1, bounds: 'hard-min' };\n\nconst terrainKnobDefinitions = {\n 'hillshade.visible': {\n kind: 'toggle',\n description: 'The hillshade: light and shadow on the map surface that reveal the relief at any pitch.',\n default: false,\n },\n 'hillshade.method': {\n kind: 'enum',\n description:\n 'How the relief is shaded: standard (one light), basic, igor (softer), combined, or multidirectional (four lights).',\n options: hillshadeMethods,\n },\n 'hillshade.lightDirection': {\n kind: 'number',\n description:\n 'Direction the light comes from, in degrees clockwise from north (335 is the cartographic default).',\n range: { min: 0, max: 359, step: 1, bounds: 'hard' },\n },\n 'hillshade.exaggeration': {\n kind: 'number',\n description:\n 'Strength of the shading (0 = flat), not the 3D surface height (`elevation.exaggeration`). The standard styles ramp it to zero by zoom 13; setting it replaces the ramp with a constant.',\n range: { min: 0, max: 1, step: 0.05, bounds: 'hard' },\n },\n 'hillshade.maxZoom': {\n kind: 'number',\n description:\n 'Zoom level up to which the shading is drawn (the standard styles stop at 13; 22 keeps it at every zoom).',\n range: { min: 10, max: 22, step: 1, bounds: 'hard' },\n },\n 'hillshade.shadowColor': { kind: 'color', description: 'Colour of the shaded slopes.' },\n 'hillshade.highlightColor': { kind: 'color', description: 'Colour of the lit slopes.' },\n 'hillshade.accentColor': { kind: 'color', description: 'Colour accentuating the steepest terrain.' },\n 'elevation.visible': {\n kind: 'toggle',\n description:\n \"The 3D surface, raised to the elevation data or flattened. Unset: the map's own terrain, if its style or MapLibre code raised one; flat on TomTom styles.\",\n },\n 'elevation.exaggeration': {\n kind: 'number',\n description:\n 'Vertical scale of the 3D surface, `1` being true elevation; kept while `elevation.visible` is off.',\n range: ELEVATION_EXAGGERATION_RANGE,\n default: DEFAULT_EXAGGERATION,\n },\n} as const satisfies { [ID in TerrainKnobId]: KnobDefinition<TerrainKnobValueOf<ID>> };\n\n/**\n * Every terrain knob with its kind, description, range or options and, where one value holds\n * everywhere, its default. Static data, readable with no map loaded.\n *\n * @remarks\n * A knob is a plain display value in {@link TerrainModuleConfig}, at the path its id names. The\n * hillshade's look reads the loaded style's own value while unset, which {@link getKnob} reports.\n * Not knobs: `events`.\n *\n * @group Terrain\n */\nexport const terrainKnobCatalogue: readonly TerrainKnob[] = /* @__PURE__ */ withIds(terrainKnobDefinitions);\n\n/**\n * Every terrain knob id, in catalogue order.\n *\n * @group Terrain\n */\nexport const terrainKnobIds: readonly TerrainKnobId[] = /* @__PURE__ */ terrainKnobCatalogue.map(({ id }) => id);\n","import type { TerrainSpecification } from 'maplibre-gl';\nimport {\n AbstractStyleOwnedMapModule,\n HILLSHADE_SOURCE_ID,\n type ModuleEvents,\n type StyleSourceWithLayers,\n TERRAIN_SOURCE_ID,\n TomTomMapSource,\n} from '../shared';\nimport { getStyleOwnedModule, styleSourceWithLayers } from '../shared/styleOwnedModules';\nimport type { TomTomMap } from '../TomTomMap';\nimport { DEFAULT_EXAGGERATION } from './constants';\nimport { hillshadeLook } from './hillshadeStyleKnobs';\nimport type { TerrainModuleConfig } from './types/terrainModuleConfig';\n\n/**\n * The style source this module draws from: the raster-dem elevation source of the terrain\n * style part, with its hillshade layers.\n */\ntype TerrainSourcesWithLayers = {\n hillshade: StyleSourceWithLayers;\n};\n\n/**\n * Map module for the terrain relief: hillshade shading and the 3D surface, both drawn from the\n * elevation data of the map style.\n *\n * @remarks\n * **What this module owns:** the hillshade — whether it is shown, and its shading method, light\n * direction, strength, zoom range and colours — and whether and how high the surface is raised.\n * See [Who owns what](https://docs.tomtom.com/maps-sdk-js/guides/map/modules#who-owns-what).\n * {@link terrainKnobCatalogue} lists every setting of its configuration.\n *\n * - **Hillshade** shades slopes on the flat map, so the relief reads at any pitch.\n * - **Elevation** raises the map surface in 3D, which a pitched camera shows. Combined with the\n * hillshade, a slope stays readable where the camera flattens it.\n *\n * `get()` adds the elevation style part when the style lacks it, and the module restores both\n * after every style change. The 3D surface reads its own copy of the elevation source,\n * {@link TERRAIN_SOURCE_ID}.\n *\n * @example\n * ```typescript\n * const terrain = await TerrainModule.get(map, {\n * hillshade: { visible: true, method: 'multidirectional' },\n * elevation: { visible: true, exaggeration: 1.3 },\n * });\n * setKnob(terrain, terrainKnobCatalogue, 'elevation.exaggeration', 2);\n * terrain.setElevationVisible(false); // flat again, still shaded\n * ```\n *\n * @see [Terrain Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/terrain)\n *\n * @group Terrain\n */\nexport class TerrainModule extends AbstractStyleOwnedMapModule<TerrainSourcesWithLayers, TerrainModuleConfig> {\n // The terrain the loaded style, or the caller's own MapLibre code, raised before this module\n // did: an unset `elevation.visible` leaves it on screen, and a reset puts it back.\n private mapTerrain!: TerrainSpecification | null;\n // Whether an explicit `elevation.visible`, raised or flat, replaced `mapTerrain`. No initializer: the\n // base constructor applies the first config, and an initializer would run after it.\n private overridesMapTerrain!: boolean;\n\n /**\n * Retrieves the terrain module of the given map, adding the elevation style part first when\n * the style lacks it.\n *\n * @param config - Without it the hillshade is hidden, and the surface stays as the map draws it.\n *\n * @remarks\n * **Instances:** one per map — a second `get()` returns the same instance, with the given config\n * applied to it.\n *\n * @throws Error if the elevation source cannot be added to the style.\n */\n static get(map: TomTomMap, config?: TerrainModuleConfig): Promise<TerrainModule> {\n return getStyleOwnedModule(map, TerrainModule, () => new TerrainModule(map, config), config, {\n sourceId: HILLSHADE_SOURCE_ID,\n part: 'terrain',\n });\n }\n\n private constructor(map: TomTomMap, config?: TerrainModuleConfig) {\n super(map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n const hillshade = styleSourceWithLayers(this.mapLibreMap, HILLSHADE_SOURCE_ID, TerrainModule.name);\n // Runs on every style load before `_applyConfig`, which is what may replace this terrain.\n const mapTerrain = this.mapLibreMap.getTerrain();\n if (mapTerrain?.source !== TERRAIN_SOURCE_ID) {\n this.mapTerrain = mapTerrain;\n }\n return { hillshade };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: TerrainModuleConfig | undefined) {\n this.showHillshade(config?.hillshade?.visible ?? false);\n hillshadeLook.apply(this.tomtomMap, config);\n this.showElevation(config?.elevation?.visible, config?.elevation?.exaggeration ?? DEFAULT_EXAGGERATION);\n return config;\n }\n\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return super._knobValue(id) ?? hillshadeLook.valueOf(this.tomtomMap, id);\n }\n\n /**\n * Shows or hides the hillshade shading.\n */\n setHillshadeVisible(visible: boolean): void {\n this.showHillshade(visible);\n this.storeConfig({ hillshade: { ...this.config?.hillshade, visible } });\n }\n\n /**\n * Whether the hillshade shading is set to be shown.\n *\n * @returns The `hillshade.visible` setting; `false` while nothing has set it, as the hillshade is hidden until shown.\n */\n isHillshadeVisible(): boolean {\n return this.config?.hillshade?.visible ?? false;\n }\n\n /**\n * Raises the map surface in 3D, or flattens it, keeping the configured exaggeration.\n */\n setElevationVisible(visible: boolean): void {\n this.showElevation(visible, this.config?.elevation?.exaggeration ?? DEFAULT_EXAGGERATION);\n this.storeConfig({ elevation: { ...this.config?.elevation, visible } });\n }\n\n /**\n * Whether the map surface is set to be raised in 3D.\n *\n * @returns The `elevation.visible` setting; while nothing has set it, whether the map's own terrain raises it.\n */\n isElevationVisible(): boolean {\n return this.config?.elevation?.visible ?? this.mapLibreMap.getTerrain() !== null;\n }\n\n /**\n * Lifecycle events of this module. The hillshade is a raster layer with no features to\n * interact with, so there are no user interaction events; `config-change` fires after every\n * change.\n *\n * @example\n * ```typescript\n * const unsubscribe = terrain.events.on('config-change', (config) => console.log(config));\n * ```\n */\n get events(): ModuleEvents<TerrainModuleConfig> {\n return this.lifecycleEvents();\n }\n\n private showHillshade(visible: boolean): void {\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.hillshade.setLayersVisible(visible);\n }\n }\n\n private showElevation(visible: boolean | undefined, exaggeration: number): void {\n if (!this.tomtomMap.mapReady) return;\n\n if (visible === undefined) {\n if (this.overridesMapTerrain) {\n this.mapLibreMap.setTerrain(this.mapTerrain);\n }\n } else if (visible) {\n // Added lazily, so a map that only shows the hillshade loads each elevation tile once.\n new TomTomMapSource(TERRAIN_SOURCE_ID, this.sourcesWithLayers.hillshade.source.spec).ensureAddedToMap(\n this.mapLibreMap,\n );\n this.mapLibreMap.setTerrain({ source: TERRAIN_SOURCE_ID, exaggeration });\n } else {\n this.mapLibreMap.setTerrain(null);\n }\n this.overridesMapTerrain = visible !== undefined;\n }\n\n private storeConfig(change: Partial<TerrainModuleConfig>): void {\n this.config = { ...this.config, ...change };\n this.emitConfigChange();\n }\n}\n","import type { AreaAnalyticsMetricKey } from '@tomtom-org/maps-sdk/core';\nimport type {\n FillExtrusionLayerSpecification,\n FillLayerSpecification,\n HeatmapLayerSpecification,\n LineLayerSpecification,\n} from 'maplibre-gl';\nimport type { GeometryFillStyle } from '../../geometry';\nimport type { MapModuleCommonConfig, PartsBeforeLayerConfig, ToBeAddedLayerSpecTemplate } from '../../shared';\n\n/**\n * A single stop in an analytics color ramp.\n *\n * How `value` is interpreted depends on the surrounding `AreaAnalyticsColorStopsConfig.scaleMode`.\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 * Every {@link AreaAnalyticsPalette} name.\n *\n * @group Traffic Area Analytics\n */\nexport const areaAnalyticsPalettes = ['trafficLight', 'heat', 'monochrome', 'viridis', 'plasma'] as const;\n\n/**\n * A named colour ramp for area analytics cells, listed from good to bad traffic.\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'` — Yellow → green → blue → purple (perceptually uniform, colorblind-safe)\n * - `'plasma'` — Yellow → orange → magenta → purple (vivid, perceptually uniform)\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsPalette = (typeof areaAnalyticsPalettes)[number];\n\n/**\n * What a metric value is scaled against, for the colour stops and the cell height alike.\n *\n * - `'predefinedRange'` — the SDK's predefined range for the metric (congestion 0–100, speed\n * 0–120 km/h, travelTime 0–20 s/km): consistent across datasets. Colour stop values are 0–100 %\n * of that range.\n * - `'currentRange'` — the range of the data in the loaded tiles: maximum contrast. Colour stop\n * values are 0–100 % of that range, 0 % being the loaded minimum.\n * - `'raw'` — the metric value as it is. Colour stop values are metric values.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsScaleMode = 'predefinedRange' | 'currentRange' | 'raw';\n\n/**\n * Custom color stops configuration, with what their values are scaled against.\n *\n * @example\n * ```typescript\n * // Stops whose values are actual congestion-level values (0–100)\n * const colorStops: AreaAnalyticsColorStopsConfig = {\n * scaleMode: '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 colorStops: AreaAnalyticsColorStopsConfig = {\n * scaleMode: 'predefinedRange',\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 colorStops: AreaAnalyticsColorStopsConfig = {\n * scaleMode: 'currentRange',\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 * What the stop values are scaled against.\n *\n * @defaultValue 'raw'\n */\n scaleMode?: AreaAnalyticsScaleMode;\n\n /**\n * The color stops, in any order. Of stops that share a value, the last given wins.\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 `metersPerUnit` to turn the raw metric value into metres of height.\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?: Exclude<AreaAnalyticsScaleMode, 'raw'>;\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: the height is the raw metric value times `metersPerUnit`. */\n scaleMode: 'raw';\n\n /**\n * Metres of extrusion height per unit of the metric.\n *\n * @defaultValue 1\n */\n metersPerUnit?: 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 * Palette the cells of this metric are coloured with, spread evenly over the metric's range.\n *\n * @remarks\n * On `speed` and `freeFlowSpeed`, where higher is better, the palette runs the other way, so slow\n * traffic takes its bad end. On `networkLength` it spans the loaded data's range, on the other\n * metrics their predefined range. `colorStops` wins over it.\n *\n * @defaultValue 'trafficLight'\n */\n palette?: AreaAnalyticsPalette;\n\n /**\n * Explicit colour stops for this metric's cells, used as given. Wins over `palette`.\n */\n colorStops?: 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 * Where the area analytics layers sit in the map's layer stack.\n *\n * @remarks\n * A single {@link BeforeLayerConfig} positions every display mode's layer; the record form positions\n * them independently, keyed by {@link AreaAnalyticsDisplayMode}, `all` covering any it doesn't name.\n * A mode neither names keeps its default: `'lowestLabel'` for `heatmap`, `hexgrid-2d` and\n * `square-2d`, `'lowestPlaceLabel'` for `hexgrid-3d` and `square-3d`. The region boundary always\n * sits below the map's labels.\n *\n * @example\n * ```typescript\n * // Every mode on top of the map\n * const config: TrafficAreaAnalyticsModuleConfig = { beforeLayerConfig: 'top' };\n *\n * // Flat modes below labels, extrusions below place labels\n * const config: TrafficAreaAnalyticsModuleConfig = {\n * beforeLayerConfig: { all: 'lowestLabel', 'hexgrid-3d': 'lowestPlaceLabel', 'square-3d': 'lowestPlaceLabel' },\n * };\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsBeforeLayerConfig = PartsBeforeLayerConfig<AreaAnalyticsDisplayMode>;\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 * CSS colour used for both the fill and line of the region boundary.\n *\n * When omitted, the colour follows {@link TomTomMap.styleLightDarkTheme}, which says how a custom\n * style is classified.\n *\n * @defaultValue Black (`#000000`) on light styles, white (`#FFFFFF`) on dark styles\n */\n color?: string;\n\n /**\n * The region's fill.\n */\n fill?: AreaAnalyticsRegionFillConfig;\n\n /**\n * The region's boundary line.\n */\n line?: AreaAnalyticsRegionLineConfig;\n\n /**\n * `'filled'` fills the region itself; `'inverted'` fills everything *outside* it instead,\n * tinting the world around the analytics region, as the `GeometriesModule` fill style of the\n * same name does.\n *\n * @defaultValue 'filled'\n */\n fillStyle?: Extract<GeometryFillStyle, 'filled' | 'inverted'>;\n};\n\n/**\n * The fill of the area analytics region.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsRegionFillConfig = {\n /**\n * Opacity of the fill.\n *\n * @defaultValue 0\n */\n opacity?: number;\n};\n\n/**\n * The boundary line of the area analytics region.\n *\n * @group Traffic Area Analytics\n */\nexport type AreaAnalyticsRegionLineConfig = {\n /**\n * Opacity of the line.\n *\n * @defaultValue 0.5\n */\n opacity?: number;\n\n /**\n * Width of the line in pixels.\n *\n * @defaultValue 2\n */\n width?: number;\n};\n\n// ── Default values ───────────────────────────────────────────────────\n\n/**\n * Default values for all {@link TrafficAreaAnalyticsModuleConfig} 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 TrafficAreaAnalyticsModuleConfig.visible} */\n visible: true,\n /** @see {@link TrafficAreaAnalyticsModuleConfig.displayMode} */\n displayMode: 'hexgrid-3d',\n /** @see {@link TrafficAreaAnalyticsModuleConfig.activeMetric} */\n activeMetric: 'congestionLevel',\n /** @see {@link TrafficAreaAnalyticsModuleConfig.metrics} */\n metrics: {\n congestionLevel: {\n palette: 'trafficLight',\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n speed: {\n palette: 'trafficLight',\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n travelTime: {\n palette: 'trafficLight',\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n freeFlowSpeed: {\n palette: 'trafficLight',\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'predefinedRange' },\n },\n // Per-tile road lengths vary too widely for a predefined range, so the height scales to the\n // loaded data, as the palette does.\n networkLength: {\n palette: 'trafficLight',\n height: { maxHeightMeters: 1000, minHeightMeters: 0, scaleMode: 'currentRange' },\n },\n },\n /**\n * @see {@link AreaAnalyticsRegionPolygonConfig}\n * `color` is omitted — the layer builders apply a theme-adaptive default.\n */\n regionPolygon: {\n fill: { opacity: 0 },\n line: { opacity: 0.5, width: 2 },\n fillStyle: 'filled',\n },\n} satisfies TrafficAreaAnalyticsModuleConfig;\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 (palette or colour stops, height, filters) are nested under `metrics` keyed by\n * `AreaAnalyticsMetricKey`, making it easy to pre-configure multiple metrics independently and switch\n * between them at runtime with `updateConfig({ activeMetric })`.\n *\n * @example\n * ```typescript\n * const config: TrafficAreaAnalyticsModuleConfig = {\n * displayMode: 'hexgrid-3d',\n * activeMetric: 'congestionLevel',\n * metrics: {\n * congestionLevel: {\n * palette: 'heat',\n * height: { maxHeightMeters: 80, scaleMode: 'predefinedRange' },\n * filters: { min: 10 },\n * },\n * speed: {\n * colorStops: {\n * scaleMode: '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 TrafficAreaAnalyticsModuleConfig = 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 (palette or colour stops, height, filters).\n *\n * Metrics not listed here fall back to defaults (the `trafficLight` palette, metric-dependent height scale).\n * `updateConfig` replaces this whole record, so to change one metric and keep the others' settings,\n * use `setPalette`, `setColorStops`, `setHeight` or `setFilters` with that metric.\n */\n metrics?: Partial<Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>>;\n\n /**\n * Where the analytics layers sit in the map's layer stack, for all display modes or per mode.\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 /**\n * MapLibre overrides for the heatmap, the cells of each grid and the region boundary. A layer\n * hidden through its entry's `layout.visibility` stays hidden in its display mode.\n */\n layers?: TrafficAreaAnalyticsLayersConfig;\n};\n\n/**\n * MapLibre overrides for the layers {@link TrafficAreaAnalyticsModule} draws, for what its\n * configuration doesn't cover. Each display mode draws its own: `heatmap` the heatmap, `hexgrid-2d`\n * the hexagon fill and outline, `hexgrid-3d` the hexagon extrusion and its highlight, and the same for\n * the square grid; the region boundary is drawn in every mode.\n *\n * @remarks\n * Each entry is a partial layer spec merged onto the module's own: top-level fields replace, `layout`\n * and `paint` merge per property, and what is set here takes precedence over the module's own\n * settings, the active metric's colours and heights included. A cell layer's `filter` applies\n * together with the active metric's `filters`. Where the layers sit is `beforeLayerConfig`'s,\n * so a `beforeID` here is ignored.\n *\n * @example\n * ```typescript\n * layers: {\n * hexgridFill: { paint: { 'fill-opacity': 0.85 } },\n * hexgridExtrusion: { paint: { 'fill-extrusion-vertical-gradient': false } },\n * regionLine: { paint: { 'line-dasharray': [2, 2] } },\n * }\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport type TrafficAreaAnalyticsLayersConfig = {\n /** The heatmap. */\n heatmap?: Partial<ToBeAddedLayerSpecTemplate<HeatmapLayerSpecification>>;\n /** The flat hexagons. */\n hexgridFill?: Partial<ToBeAddedLayerSpecTemplate<FillLayerSpecification>>;\n /** The outline of a hovered or clicked flat hexagon. */\n hexgridOutline?: Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>;\n /** The extruded hexagons. */\n hexgridExtrusion?: Partial<ToBeAddedLayerSpecTemplate<FillExtrusionLayerSpecification>>;\n /** The overlay of a hovered or clicked extruded hexagon, drawn over its `hexgridExtrusion`. */\n hexgridExtrusionHighlight?: Partial<ToBeAddedLayerSpecTemplate<FillExtrusionLayerSpecification>>;\n /** The flat squares. */\n squareFill?: Partial<ToBeAddedLayerSpecTemplate<FillLayerSpecification>>;\n /** The outline of a hovered or clicked flat square. */\n squareOutline?: Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>;\n /** The extruded squares. */\n squareExtrusion?: Partial<ToBeAddedLayerSpecTemplate<FillExtrusionLayerSpecification>>;\n /** The overlay of a hovered or clicked extruded square, drawn over its `squareExtrusion`. */\n squareExtrusionHighlight?: Partial<ToBeAddedLayerSpecTemplate<FillExtrusionLayerSpecification>>;\n /** The region's fill. */\n regionFill?: Partial<ToBeAddedLayerSpecTemplate<FillLayerSpecification>>;\n /** The region's boundary. */\n regionLine?: Partial<ToBeAddedLayerSpecTemplate<LineLayerSpecification>>;\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, LightDark, ToBeAddedLayerSpecWithoutSource } from '../../shared';\nimport { mapStyleLayerIDs } from '../../shared';\nimport { beforeLayerIDOf, beforeLayerTargetOf } from '../../shared/layers/beforeLayer';\nimport { hasActiveEventState, isClickEventState } from '../../shared/layers/eventState';\nimport { mergeLayerOverride } from '../../shared/layers/utils';\nimport type {\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsDisplayMode,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsMetricConfig,\n AreaAnalyticsPalette,\n AreaAnalyticsRegionPolygonConfig,\n AreaAnalyticsScaleMode,\n TrafficAreaAnalyticsLayersConfig,\n TrafficAreaAnalyticsModuleConfig,\n} from '../types/trafficAreaAnalyticsConfig';\nimport { AREA_ANALYTICS_DEFAULTS } from '../types/trafficAreaAnalyticsConfig';\n\n// ── Internal types ───────────────────────────────────────────────────\n\ntype MetricRange = { min: number; max: number };\n\n// White outline / prism for the cell hover / click highlight.\nconst HIGHLIGHT_COLOR = '#ffffff';\n\n// Default region polygon colour, resolved for the active map theme: black on light, white on dark.\nconst getThemeAdaptiveRegionPolygonColor = (lightDark: LightDark) => (lightDark === 'dark' ? '#FFFFFF' : '#000000');\n\n// ── Palettes ─────────────────────────────────────────────────────────\n\n// Each palette's colours, from good to bad traffic.\nconst PALETTE_COLORS: Record<AreaAnalyticsPalette, 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 the currentRange scale mode to normalize to the live data range.\nexport const 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/**\n * Metres of height per unit of the metric under the `raw` scale mode, when `metersPerUnit` is unset.\n * @ignore\n */\nexport const DEFAULT_RAW_HEIGHT_METERS_PER_UNIT = 1;\n\n// Speed-related metrics are \"higher = better\", so palettes are applied in reverse.\nconst isSpeedMetric = (metric: AreaAnalyticsMetricKey): boolean => metric === 'speed' || metric === 'freeFlowSpeed';\n\n// Per-tile networkLength values vary too widely for its predefined range to tell the cells apart, so a\n// palette spans the loaded data's range there.\nconst paletteScaleMode = (metric: AreaAnalyticsMetricKey): AreaAnalyticsScaleMode =>\n metric === 'networkLength' ? 'currentRange' : 'predefinedRange';\n\n// ── Config resolution helpers ────────────────────────────────────────\n\n/** @ignore */\nexport const getActiveMetric = (config?: TrafficAreaAnalyticsModuleConfig): AreaAnalyticsMetricKey => {\n return config?.activeMetric ?? AREA_ANALYTICS_DEFAULTS.activeMetric;\n};\n\nexport const getDisplayMode = (config?: TrafficAreaAnalyticsModuleConfig): AreaAnalyticsDisplayMode =>\n config?.displayMode ?? AREA_ANALYTICS_DEFAULTS.displayMode;\n\n/**\n * The colour stops a metric's cells are painted with: its `colorStops` when set, else its `palette`\n * (`trafficLight` when unset) spread evenly from 0 to 100 % of the metric's range, as\n * {@link AreaAnalyticsMetricConfig.palette} describes.\n * @ignore\n */\nexport const resolveColorStops = (\n metric: AreaAnalyticsMetricKey,\n metricConfig?: Pick<AreaAnalyticsMetricConfig, 'palette' | 'colorStops'>,\n): AreaAnalyticsColorStopsConfig => {\n if (metricConfig?.colorStops) return metricConfig.colorStops;\n\n const colors = PALETTE_COLORS[metricConfig?.palette ?? 'trafficLight'];\n const ordered = isSpeedMetric(metric) ? [...colors].reverse() : colors;\n return {\n scaleMode: paletteScaleMode(metric),\n stops: ordered.map((color, index) => ({ value: (index / (ordered.length - 1)) * 100, color })),\n };\n};\n\n// ── Layer positioning ────────────────────────────────────────────────\n\nconst DEFAULT_BEFORE_LAYER: Record<AreaAnalyticsDisplayMode, BeforeLayerConfig> = {\n heatmap: 'lowestLabel',\n 'hexgrid-2d': 'lowestLabel',\n 'hexgrid-3d': 'lowestPlaceLabel',\n 'square-2d': 'lowestLabel',\n 'square-3d': 'lowestPlaceLabel',\n};\n\nconst beforeIDOf = (\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n mode: AreaAnalyticsDisplayMode,\n): string | undefined =>\n beforeLayerIDOf(beforeLayerTargetOf(config?.beforeLayerConfig, mode) ?? DEFAULT_BEFORE_LAYER[mode]);\n\n// ── Color / height expression builders ──────────────────────────────\n\n// MapLibre rejects an `interpolate` whose inputs are not strictly ascending, so the stops are sorted and, of\n// stops on one input — given so, or collapsed onto it by a live range whose min is its max — the last given wins.\nconst interpolationStops = (stops: ReadonlyArray<readonly [number, string]>): (number | string)[] =>\n [...stops]\n .sort(([a], [b]) => a - b)\n .filter(([value], index, sorted) => sorted[index + 1]?.[0] !== value)\n .flat();\n\n// Builds a MapLibre interpolation expression mapping a metric property to a color scale.\nconst buildColorExpression = (\n metric: AreaAnalyticsMetricKey,\n metricConfig: AreaAnalyticsMetricConfig | undefined,\n computedRange: MetricRange | undefined,\n): ExpressionSpecification => {\n const { scaleMode, stops } = resolveColorStops(metric, metricConfig);\n\n let scaled: Array<readonly [number, string]>;\n\n if (scaleMode === 'predefinedRange') {\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 (scaleMode === 'currentRange') {\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], ...interpolationStops(scaled)];\n};\n\n// Builds a MapLibre heatmap-color expression from the metric's palette or colour stops.\nconst buildHeatmapColorExpression = (\n metric: AreaAnalyticsMetricKey,\n metricConfig: AreaAnalyticsMetricConfig | undefined,\n): ExpressionSpecification => {\n const { scaleMode, stops } = resolveColorStops(metric, metricConfig);\n // Map stop values to density range [0.3, 1.0]\n let densityStops: Array<readonly [number, string]>;\n\n if (scaleMode === 'predefinedRange' || scaleMode === 'currentRange') {\n // Both percentage modes: 0-100 → normalize to 0-1 then map to density range [0.3, 1.0]\n densityStops = stops.map(({ value, color }) => [0.3 + (value / 100) * 0.7, color] as const);\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.map(({ value, color }) => [0.3 + ((value - minValue) / range) * 0.7, color] as const);\n }\n\n return ['interpolate', ['linear'], ['heatmap-density'], 0, 'rgba(0,0,0,0)', ...interpolationStops(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.metrics[metric].height;\n const minHeight = heightConfig?.minHeightMeters ?? defaultHeight.minHeightMeters ?? 0;\n\n if (heightConfig?.scaleMode === 'raw') {\n const metersPerUnit = heightConfig.metersPerUnit ?? DEFAULT_RAW_HEIGHT_METERS_PER_UNIT;\n if (metric === 'speed') {\n const { max } = DEFAULT_METRIC_RANGES[metric];\n return ['max', minHeight, ['*', ['-', max, ['coalesce', ['get', 'speed'], 0]], metersPerUnit]];\n }\n return ['max', minHeight, ['*', ['coalesce', ['get', metric], 0], metersPerUnit]];\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/**\n * Builds the heatmap layer spec for a given source and config.\n * @ignore\n */\nexport const buildHeatmapLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<HeatmapLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metrics?.[metric];\n const { min, max } = computedRange ?? DEFAULT_METRIC_RANGES[metric];\n\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<HeatmapLayerSpecification>>(\n {\n id: layerId,\n type: 'heatmap',\n beforeID: beforeIDOf(config, '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),\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 config?.layers?.heatmap,\n );\n};\n\n// The flat cells of either grid, coloured by the active metric.\nconst buildCellFillLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange: MetricRange | undefined,\n mode: 'hexgrid-2d' | 'square-2d',\n override: TrafficAreaAnalyticsLayersConfig['hexgridFill'],\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const metric = getActiveMetric(config);\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<FillLayerSpecification>>(\n {\n id: layerId,\n type: 'fill',\n beforeID: beforeIDOf(config, mode),\n paint: {\n 'fill-color': buildColorExpression(metric, config?.metrics?.[metric], computedRange),\n 'fill-opacity': 0.6,\n },\n },\n override,\n );\n};\n\n// The extruded cells of either grid, coloured and raised by the active metric.\nconst buildCellExtrusionLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange: MetricRange | undefined,\n mode: 'hexgrid-3d' | 'square-3d',\n override: TrafficAreaAnalyticsLayersConfig['hexgridExtrusion'],\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> => {\n const metric = getActiveMetric(config);\n const metricConfig = config?.metrics?.[metric];\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification>>(\n {\n id: layerId,\n type: 'fill-extrusion',\n beforeID: beforeIDOf(config, mode),\n paint: {\n 'fill-extrusion-color': buildColorExpression(metric, metricConfig, computedRange),\n 'fill-extrusion-height': buildHeightExpression(metric, metricConfig?.height, computedRange),\n 'fill-extrusion-base': 0,\n 'fill-extrusion-opacity': 0.75,\n },\n },\n override,\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: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> =>\n buildCellFillLayerSpec(layerId, config, computedRange, 'hexgrid-2d', config?.layers?.hexgridFill);\n\n/**\n * Builds the extruded hex layer spec.\n * @ignore\n */\nexport const buildHexExtrusionLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildCellExtrusionLayerSpec(layerId, config, computedRange, 'hexgrid-3d', config?.layers?.hexgridExtrusion);\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: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> =>\n buildCellFillLayerSpec(layerId, config, computedRange, 'square-2d', config?.layers?.squareFill);\n\n/**\n * Builds the extruded square layer spec.\n * @ignore\n */\nexport const buildSquareExtrusionLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildCellExtrusionLayerSpec(layerId, config, computedRange, 'square-3d', config?.layers?.squareExtrusion);\n\n// ── Region boundary layer builders ───────────────────────────────────\n\n// Resolves the region display config with defaults applied, per part.\nconst resolveRegionConfig = (region?: AreaAnalyticsRegionPolygonConfig) => {\n const defaults = AREA_ANALYTICS_DEFAULTS.regionPolygon;\n return {\n color: region?.color,\n fill: { ...defaults.fill, ...region?.fill },\n line: { ...defaults.line, ...region?.line },\n };\n};\n\n/**\n * Builds the region boundary fill layer spec.\n * @ignore\n */\nexport const buildRegionFillLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n lightDark: LightDark,\n): ToBeAddedLayerSpecWithoutSource<FillLayerSpecification> => {\n const { color, fill } = resolveRegionConfig(config?.regionPolygon);\n\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<FillLayerSpecification>>(\n {\n id: layerId,\n type: 'fill',\n beforeID: mapStyleLayerIDs.lowestLabel,\n paint: {\n 'fill-color': color ?? getThemeAdaptiveRegionPolygonColor(lightDark),\n 'fill-opacity': fill.opacity,\n },\n },\n config?.layers?.regionFill,\n );\n};\n\n/**\n * Builds the region boundary outline layer spec.\n * @ignore\n */\nexport const buildRegionLineLayerSpec = (\n layerId: string,\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n lightDark: LightDark,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> => {\n const { color, line } = resolveRegionConfig(config?.regionPolygon);\n\n return mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>>(\n {\n id: layerId,\n type: 'line',\n beforeID: mapStyleLayerIDs.lowestLabel,\n paint: {\n 'line-color': color ?? getThemeAdaptiveRegionPolygonColor(lightDark),\n 'line-opacity': line.opacity,\n 'line-width': line.width,\n },\n },\n config?.layers?.regionLine,\n );\n};\n\n// ── Event-state highlight layer builders ──────────────────────────────\n//\n// These layers render only while a feature is hovered or clicked, through the `eventState`\n// the EventsProxy writes (`recently-hovered` does not count). 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 override: TrafficAreaAnalyticsLayersConfig['hexgridOutline'],\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<LineLayerSpecification>>(\n {\n id: layerId,\n type: 'line',\n beforeID,\n filter: hasActiveEventState,\n paint: {\n 'line-color': HIGHLIGHT_COLOR,\n 'line-width': ['case', isClickEventState, 3, 2],\n 'line-opacity': ['case', isClickEventState, 1, 0.8],\n },\n },\n override,\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: TrafficAreaAnalyticsModuleConfig | undefined,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, beforeIDOf(config, 'hexgrid-2d'), config?.layers?.hexgridOutline);\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: TrafficAreaAnalyticsModuleConfig | undefined,\n): ToBeAddedLayerSpecWithoutSource<LineLayerSpecification> =>\n buildPolygonOutlineLayerSpec(layerId, beforeIDOf(config, 'square-2d'), config?.layers?.squareOutline);\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 override: TrafficAreaAnalyticsLayersConfig['hexgridExtrusionHighlight'],\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n mergeLayerOverride<ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification>>(\n {\n ...base,\n filter: hasActiveEventState,\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, HIGHLIGHT_COLOR, HIGHLIGHT_COLOR],\n 'fill-extrusion-height': ['+', base.paint?.['fill-extrusion-height'] as ExpressionSpecification, 0.5],\n 'fill-extrusion-opacity': 0.6,\n },\n },\n override,\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: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(\n buildHexExtrusionLayerSpec(layerId, config, computedRange),\n config?.layers?.hexgridExtrusionHighlight,\n );\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: TrafficAreaAnalyticsModuleConfig | undefined,\n computedRange?: MetricRange,\n): ToBeAddedLayerSpecWithoutSource<FillExtrusionLayerSpecification> =>\n buildExtrusionHighlightLayerSpec(\n buildSquareExtrusionLayerSpec(layerId, config, computedRange),\n config?.layers?.squareExtrusionHighlight,\n );\n","/**\n * Direction chevron sprite for {@link TrafficIncidentDetailsModule}.\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\n cachedImage ??= svgToImg(parseSvg(svgMarkup));\n return cachedImage;\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 cachedImage: HTMLImageElement | undefined;\n","import type { ExpressionSpecification, LineLayerSpecification, SymbolLayerSpecification } from 'maplibre-gl';\nimport type { KnobRange, LightDark } from '../../shared';\nimport { clampFactor } from '../../shared/knobUtils';\nimport { hasActiveEventState } from '../../shared/layers/eventState';\nimport { scaledWhen } from '../../shared/layers/expressionTransforms';\nimport { mergeLayerOverride } from '../../shared/layers/utils';\nimport type {\n IncidentHighlightConfig,\n TrafficIncidentDetailsLayersConfig,\n} from '../types/trafficIncidentDetailsConfig';\nimport { INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from '../util/incidentDirectionChevron';\nimport type { IncidentPalette } from '../util/readIncidentPalette';\n\n/**\n * Incident rendering for {@link TrafficIncidentDetailsModule}. 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\n/**\n * The values {@link IncidentHighlightConfig.widthFactor} is clamped to.\n *\n * @remarks\n * `1` leaves highlighted incidents at their normal width; above `1.8` the widened stripe would cover\n * the highlight outline at high zoom.\n * @ignore\n */\nexport const INCIDENT_HIGHLIGHT_WIDTH_FACTOR_RANGE: KnobRange = { min: 1, max: 1.8, step: 0.1, bounds: 'hard' };\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// Highlight: pop focused, hovered and clicked features, leave everything else as-is.\n// - `focused=null` (no focus active): every feature renders at full strength.\n// - `focused=true`, or hovered or clicked: wider stripe + outline painted beneath (unless\n// `highlight: false`).\n// - `focused=false`: identical to `focused=null` — unfocused features are not dimmed.\n//\n// Defaults are applied via `resolveHighlightStyle` and can be overridden / disabled via the\n// module's `highlight` config.\n// Default highlight-outline colour, resolved for the active map theme: black on light, white on dark.\nconst getThemeAdaptiveHighlightOutlineColor = (lightDark: LightDark) => (lightDark === 'dark' ? '#FFF' : '#000');\nexport const DEFAULT_HIGHLIGHT_WIDTH_FACTOR = 1.6;\n\ntype ResolvedHighlight = Required<IncidentHighlightConfig>;\n\n/**\n * Resolve the user's `highlight` config into an internal shape:\n * `false` → `null` (no halo layer, no width pop, no state expressions on width)\n * `undefined` → defaults\n * object → defaults merged with overrides, `widthFactor` clamped to its range\n * @ignore\n */\nexport const resolveHighlightStyle = (\n highlight: false | IncidentHighlightConfig | undefined,\n lightDark: LightDark,\n): ResolvedHighlight | null => {\n if (highlight === false) return null;\n\n return {\n outlineColor: highlight?.outlineColor ?? getThemeAdaptiveHighlightOutlineColor(lightDark),\n widthFactor: clampFactor(\n highlight?.widthFactor,\n INCIDENT_HIGHLIGHT_WIDTH_FACTOR_RANGE,\n DEFAULT_HIGHLIGHT_WIDTH_FACTOR,\n ),\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// Focused through `setFocus` (MapLibre feature-state), or hovered or clicked (`eventState`): both\n// draw the same treatment.\nconst isFocusedOrActive: ExpressionSpecification = [\n 'any',\n ['==', ['feature-state', 'focused'], true],\n hasActiveEventState,\n];\n\n// Halo opacity: full on focused, hovered or clicked 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', isFocusedOrActive, HALO_OPACITY, 0];\n\n// `widthFactor === null` returns the original expression unmodified — used when the highlight\n// is disabled, so widths don't carry a state expression at all.\nconst highlightWidth = (width: ExpressionSpecification, widthFactor: number | null): ExpressionSpecification =>\n widthFactor === null ? width : scaledWhen(isFocusedOrActive, width, widthFactor);\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 highlight halo:\n * - highlight-halo (only emitted when `highlight !== null`; visible only on focused, hovered or clicked features)\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 * `highlight === null` (caller passed `highlight: false` in the module config) drops the halo layer\n * entirely and leaves widths free of any state expression — `setFocus()` still writes\n * `feature-state.focused`, and user events `eventState`, but no layer reads them.\n * @ignore\n */\nexport const buildCanonicalIncidentLineLayers = (\n sourceId: string,\n beforeID: string | undefined,\n palette: IncidentPalette,\n highlight: ResolvedHighlight | null,\n overrides?: TrafficIncidentDetailsLayersConfig,\n): LineLayerWithBefore[] => {\n const widthFactor = highlight?.widthFactor ?? null;\n const layers: LineLayerWithBefore[] = [];\n if (highlight !== null) {\n layers.push(\n mergeLayerOverride<LineLayerWithBefore>(\n {\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}-highlight-halo`,\n type: 'line',\n minzoom: 6,\n layout: { 'line-cap': 'round', 'line-join': 'round' },\n paint: {\n 'line-color': highlight.outlineColor,\n 'line-width': HALO_WIDTH,\n 'line-blur': HALO_BLUR,\n 'line-opacity': haloOpacity,\n },\n beforeID,\n },\n overrides?.highlightHalo,\n ),\n );\n }\n layers.push(\n mergeLayerOverride<LineLayerWithBefore>(\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': highlightWidth(OUTLINE_WIDTH, widthFactor),\n },\n beforeID,\n },\n overrides?.outline,\n ),\n mergeLayerOverride<LineLayerWithBefore>(\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': highlightWidth(INNER_WIDTH, widthFactor),\n },\n beforeID,\n },\n overrides?.innerSolid,\n ),\n mergeLayerOverride<LineLayerWithBefore>(\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': highlightWidth(INNER_WIDTH, widthFactor),\n },\n beforeID,\n },\n overrides?.innerChevron,\n ),\n mergeLayerOverride<LineLayerWithBefore>(\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': highlightWidth(INNER_WIDTH, widthFactor) },\n beforeID,\n },\n overrides?.innerPattern,\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 overrides?: TrafficIncidentDetailsLayersConfig,\n): SymbolLayerWithBefore[] => [\n mergeLayerOverride<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 overrides?.incidentMarker,\n ),\n mergeLayerOverride<SymbolLayerWithBefore>(\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 overrides?.jamMarker,\n ),\n mergeLayerOverride<SymbolLayerWithBefore>(\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 overrides?.closedRoadMarker,\n ),\n];\n","import type { 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\n/**\n * Traffic filters: the one its fields set, and in `any` the further ones a feature may match instead.\n *\n * @group Traffic\n */\nexport type FiltersWithAlternatives<FILTER> = FILTER & {\n /**\n * Further filters, each an alternative a feature may match instead of the one set beside them.\n */\n any?: FILTER[];\n};\n","import {\n type AreaAnalyticsMetricKey,\n areaAnalyticsMetricKeys,\n type DelayMagnitude,\n fullTrafficIncidentCategories,\n} from '@tomtom-org/maps-sdk/core';\nimport { filterShowModes, type KnobRange } from '../shared';\nimport {\n everyOf,\n type KnobDefinition,\n OPACITY_RANGE,\n type ValueAt,\n valuesFilterKnobs,\n WIDE_FACTOR_RANGE,\n withIds,\n} from '../shared/knobUtils';\nimport { DEFAULT_METRIC_RANGES, DEFAULT_RAW_HEIGHT_METERS_PER_UNIT } from './layers/areaAnalyticsLayers';\nimport { DEFAULT_HIGHLIGHT_WIDTH_FACTOR, INCIDENT_HIGHLIGHT_WIDTH_FACTOR_RANGE } from './layers/incidentDetailsLayers';\nimport {\n AREA_ANALYTICS_DEFAULTS,\n type AreaAnalyticsDisplayMode,\n type AreaAnalyticsHeightConfig,\n type AreaAnalyticsMetricConfig,\n type AreaAnalyticsRegionPolygonConfig,\n areaAnalyticsPalettes,\n} from './types/trafficAreaAnalyticsConfig';\nimport { roadCategories, serviceRoadSubCategories, streetRoadSubCategories } from './types/trafficCommonConfig';\nimport type {\n TrafficAreaAnalyticsKnob,\n TrafficAreaAnalyticsKnobId,\n TrafficAreaAnalyticsKnobValueOf,\n TrafficFlowKnob,\n TrafficFlowKnobId,\n TrafficFlowKnobValueOf,\n TrafficIncidentDetailsKnob,\n TrafficIncidentDetailsKnobId,\n TrafficIncidentDetailsKnobValueOf,\n TrafficIncidentsKnob,\n TrafficIncidentsKnobId,\n TrafficIncidentsKnobValueOf,\n} from './types/trafficKnobs';\n\nconst HEIGHT_METERS_RANGE: KnobRange = { min: 0, max: 5000, step: 50, bounds: 'hard-min' };\nconst RAW_HEIGHT_METERS_PER_UNIT_RANGE: KnobRange = { min: 0, max: 100, step: 0.1, bounds: 'hard-min' };\nconst OUTLINE_WIDTH_RANGE: KnobRange = { min: 0, max: 10, step: 0.5, bounds: 'hard-min' };\nconst DELAY_MINUTES_RANGE: KnobRange = { min: 0, max: 120, step: 5, bounds: 'hard-min' };\n\nconst roadCategoriesFilter = valuesFilterKnobs('filters.roadCategories', 'road categories', roadCategories);\nconst roadSubCategoriesFilter = valuesFilterKnobs('filters.roadSubCategories', 'street and service road kinds', [\n ...streetRoadSubCategories,\n ...serviceRoadSubCategories,\n]);\n\n// The road filters flow and incidents share.\nconst roadFilterKnobDefinitions = {\n 'filters.roadCategories.show': roadCategoriesFilter.show,\n 'filters.roadCategories.values': roadCategoriesFilter.values,\n 'filters.roadSubCategories.show': roadSubCategoriesFilter.show,\n 'filters.roadSubCategories.values': roadSubCategoriesFilter.values,\n} as const;\n\nconst incidentCategoriesFilter = valuesFilterKnobs(\n 'filters.incidentCategories',\n 'incident categories',\n fullTrafficIncidentCategories,\n);\nconst magnitudesFilter = valuesFilterKnobs(\n 'filters.magnitudes',\n 'delay magnitudes',\n everyOf<DelayMagnitude>()(['unknown', 'minor', 'moderate', 'major', 'indefinite']),\n);\n\nconst flowKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'The traffic flow lines; hidden until shown.',\n default: false,\n },\n ...roadFilterKnobDefinitions,\n 'filters.roadClosures': {\n kind: 'enum',\n description: '`only` draws just the closed roads, `all-except` the flow on every road but them.',\n options: filterShowModes,\n },\n 'colors.free': { kind: 'color', description: 'Flow colour of free-flowing roads; its outline follows.' },\n 'colors.slow': { kind: 'color', description: 'Flow colour of slow traffic; its outline follows.' },\n 'colors.queueing': { kind: 'color', description: 'Flow colour of queueing traffic; its outline follows.' },\n 'colors.stationary': { kind: 'color', description: 'Flow colour of stationary traffic; its outline follows.' },\n 'colors.closed': { kind: 'color', description: 'Outline colour of closed roads.' },\n widthFactor: {\n kind: 'factor',\n description: 'Width of the flow lines.',\n range: WIDE_FACTOR_RANGE,\n default: 1,\n },\n} as const satisfies { [ID in TrafficFlowKnobId]: KnobDefinition<TrafficFlowKnobValueOf<ID>> };\n\nconst incidentsKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'The traffic incident lines and their icons; hidden until shown.',\n default: false,\n },\n 'icons.visible': {\n kind: 'toggle',\n description: 'The incident icons, apart from the lines. Unset follows `visible`.',\n },\n ...roadFilterKnobDefinitions,\n 'filters.incidentCategories.show': incidentCategoriesFilter.show,\n 'filters.incidentCategories.values': incidentCategoriesFilter.values,\n 'filters.magnitudes.show': magnitudesFilter.show,\n 'filters.magnitudes.values': magnitudesFilter.values,\n 'filters.delays.mustHaveDelay': {\n kind: 'toggle',\n description: 'Drop the incidents that report no delay.',\n default: false,\n },\n 'filters.delays.minDelayMinutes': {\n kind: 'number',\n description:\n 'Drop the incidents delaying traffic by less than this many minutes; those that report no delay stay unless `filters.delays.mustHaveDelay`.',\n range: DELAY_MINUTES_RANGE,\n },\n 'colors.minor': { kind: 'color', description: 'Incident line colour for minor delays; its outline follows.' },\n 'colors.moderate': {\n kind: 'color',\n description: 'Incident line colour for moderate delays; its outline follows.',\n },\n 'colors.major': { kind: 'color', description: 'Incident line colour for major delays; its outline follows.' },\n 'colors.closed': { kind: 'color', description: 'Incident outline colour for closed roads.' },\n widthFactor: {\n kind: 'factor',\n description: 'Width of the incident lines.',\n range: WIDE_FACTOR_RANGE,\n default: 1,\n },\n} as const satisfies { [ID in TrafficIncidentsKnobId]: KnobDefinition<TrafficIncidentsKnobValueOf<ID>> };\n\nconst incidentDetailsKnobDefinitions = {\n visible: {\n kind: 'toggle',\n description: 'The incident layers; hiding them keeps the shown incidents.',\n default: true,\n },\n 'highlight.outlineColor': {\n kind: 'color',\n description:\n 'Colour of the outline beneath the focused, hovered or clicked incidents. Unset: black on light styles, white on dark.',\n },\n 'highlight.widthFactor': {\n kind: 'factor',\n description:\n 'Multiplier on the line width of the focused, hovered or clicked incidents; `1` keeps the outline alone.',\n range: INCIDENT_HIGHLIGHT_WIDTH_FACTOR_RANGE,\n default: DEFAULT_HIGHLIGHT_WIDTH_FACTOR,\n },\n} as const satisfies {\n [ID in TrafficIncidentDetailsKnobId]: KnobDefinition<TrafficIncidentDetailsKnobValueOf<ID>>;\n};\n\ntype AreaAnalyticsFixedKnobId = Exclude<TrafficAreaAnalyticsKnobId, `metrics.${string}`>;\n\n// A metric knob's path below `metrics.<metric>.`, whichever metric takes it.\ntype AreaAnalyticsMetricKnobLeaf<ID = TrafficAreaAnalyticsKnobId> = ID extends `metrics.${string}.${infer LEAF}`\n ? LEAF\n : never;\n\n// A pure IIFE: the property reads of AREA_ANALYTICS_DEFAULTS would otherwise keep this object, and\n// the defaults, in every bundle of the package.\nconst areaAnalyticsKnobDefinitions = /* @__PURE__ */ (() =>\n ({\n visible: {\n kind: 'toggle',\n description: 'The analytics layers, region boundary included.',\n default: AREA_ANALYTICS_DEFAULTS.visible,\n },\n displayMode: {\n kind: 'enum',\n description: 'How the cells are drawn: hexagons or squares, flat or raised by the metric, or a heatmap.',\n options: everyOf<AreaAnalyticsDisplayMode>()([\n 'hexgrid-3d',\n 'hexgrid-2d',\n 'square-3d',\n 'square-2d',\n 'heatmap',\n ]),\n default: AREA_ANALYTICS_DEFAULTS.displayMode,\n },\n activeMetric: {\n kind: 'enum',\n description: 'The metric that colours the cells and raises them in the 3D modes.',\n options: areaAnalyticsMetricKeys,\n default: AREA_ANALYTICS_DEFAULTS.activeMetric,\n },\n 'regionPolygon.color': {\n kind: 'color',\n description: 'Fill and line colour of the region boundary. Unset: black on light styles, white on dark.',\n },\n 'regionPolygon.fill.opacity': {\n kind: 'number',\n description: 'Fill opacity of the region.',\n range: OPACITY_RANGE,\n default: AREA_ANALYTICS_DEFAULTS.regionPolygon.fill.opacity,\n },\n 'regionPolygon.line.opacity': {\n kind: 'number',\n description: 'Opacity of the region boundary line.',\n range: OPACITY_RANGE,\n default: AREA_ANALYTICS_DEFAULTS.regionPolygon.line.opacity,\n },\n 'regionPolygon.line.width': {\n kind: 'number',\n description: 'Width of the region boundary line, in pixels.',\n range: OUTLINE_WIDTH_RANGE,\n default: AREA_ANALYTICS_DEFAULTS.regionPolygon.line.width,\n },\n 'regionPolygon.fillStyle': {\n kind: 'enum',\n description: '`filled` fills the region; `inverted` fills the world outside it instead.',\n options: everyOf<NonNullable<AreaAnalyticsRegionPolygonConfig['fillStyle']>>()(['filled', 'inverted']),\n default: AREA_ANALYTICS_DEFAULTS.regionPolygon.fillStyle,\n },\n }) as const satisfies { [ID in AreaAnalyticsFixedKnobId]: KnobDefinition<TrafficAreaAnalyticsKnobValueOf<ID>> })();\n\nconst metricKnobDefinitions: {\n [LEAF in AreaAnalyticsMetricKnobLeaf]: (\n metric: AreaAnalyticsMetricKey,\n ) => KnobDefinition<ValueAt<AreaAnalyticsMetricConfig, LEAF>>;\n} = {\n palette: (metric) => ({\n kind: 'enum',\n description: `Palette of the \\`${metric}\\` cells, its bad end where traffic is worst; \\`colorStops\\`, set in the config, wins over it.`,\n options: areaAnalyticsPalettes,\n default: AREA_ANALYTICS_DEFAULTS.metrics[metric].palette,\n }),\n 'height.scaleMode': (metric) => ({\n kind: 'enum',\n description: `What the \\`${metric}\\` cells' height scales against: the SDK's range for the metric, the loaded data's range, or the raw value.`,\n options: everyOf<NonNullable<AreaAnalyticsHeightConfig['scaleMode']>>()([\n 'predefinedRange',\n 'currentRange',\n 'raw',\n ]),\n default: AREA_ANALYTICS_DEFAULTS.metrics[metric].height.scaleMode,\n }),\n 'height.maxHeightMeters': (metric) => ({\n kind: 'number',\n description: `Height of the tallest \\`${metric}\\` cell, in metres, under the \\`predefinedRange\\` and \\`currentRange\\` scale modes.`,\n range: HEIGHT_METERS_RANGE,\n default: AREA_ANALYTICS_DEFAULTS.metrics[metric].height.maxHeightMeters,\n }),\n 'height.minHeightMeters': (metric) => ({\n kind: 'number',\n description: `Height of the lowest \\`${metric}\\` cells, in metres.`,\n range: HEIGHT_METERS_RANGE,\n default: AREA_ANALYTICS_DEFAULTS.metrics[metric].height.minHeightMeters,\n }),\n 'height.metersPerUnit': (metric) => ({\n kind: 'number',\n description: `Metres of height per unit of \\`${metric}\\`, under the \\`raw\\` scale mode.`,\n range: RAW_HEIGHT_METERS_PER_UNIT_RANGE,\n default: DEFAULT_RAW_HEIGHT_METERS_PER_UNIT,\n }),\n 'filters.min': (metric) => ({\n kind: 'number',\n description: `Hide the cells whose \\`${metric}\\` is below this value, while \\`${metric}\\` is the active metric.`,\n range: { ...DEFAULT_METRIC_RANGES[metric], bounds: 'hard-min' },\n }),\n 'filters.max': (metric) => ({\n kind: 'number',\n description: `Hide the cells whose \\`${metric}\\` is above this value, while \\`${metric}\\` is the active metric.`,\n range: { ...DEFAULT_METRIC_RANGES[metric], bounds: 'hard-min' },\n }),\n};\n\nconst buildAreaAnalyticsCatalogue = (): readonly TrafficAreaAnalyticsKnob[] => {\n const metricKnobLeaves = Object.keys(metricKnobDefinitions) as AreaAnalyticsMetricKnobLeaf[];\n return [\n ...withIds(areaAnalyticsKnobDefinitions),\n ...areaAnalyticsMetricKeys.flatMap((metric) =>\n metricKnobLeaves.map(\n (leaf): TrafficAreaAnalyticsKnob => ({\n id: `metrics.${metric}.${leaf}`,\n ...metricKnobDefinitions[leaf](metric),\n }),\n ),\n ),\n ];\n};\n\n/**\n * Every {@link TrafficFlowModule} knob with its kind, description and default. Static data,\n * readable with no map loaded.\n *\n * @remarks\n * The module owns whether flow is shown, which roads show it and how the flow lines look\n * (`colors.*`, `widthFactor`). The `filters.*` knobs set the fields of `filters`; its `any`, the\n * further filters a road may match instead, is config only, and so are `events`.\n *\n * @group Traffic Flow\n */\nexport const trafficFlowKnobCatalogue: readonly TrafficFlowKnob[] = /* @__PURE__ */ withIds(flowKnobDefinitions);\n\n/**\n * Every traffic flow knob id, in catalogue order.\n *\n * @group Traffic Flow\n */\nexport const trafficFlowKnobIds: readonly TrafficFlowKnobId[] = /* @__PURE__ */ trafficFlowKnobCatalogue.map(\n ({ id }) => id,\n);\n\n/**\n * Every {@link TrafficIncidentsModule} knob with its kind, description and, where one value holds\n * everywhere, its default. Static data, readable with no map loaded.\n *\n * @remarks\n * The module owns whether incidents are shown, which, and how their lines look (`colors.*`,\n * `widthFactor`). The `filters.*` knobs set the fields of `filters`; its `any`,\n * the further filters an incident may match instead, is config only, and so are `events`.\n *\n * @group Traffic Incidents\n */\nexport const trafficIncidentsKnobCatalogue: readonly TrafficIncidentsKnob[] =\n /* @__PURE__ */ withIds(incidentsKnobDefinitions);\n\n/**\n * Every traffic incidents knob id, in catalogue order.\n *\n * @group Traffic Incidents\n */\nexport const trafficIncidentsKnobIds: readonly TrafficIncidentsKnobId[] =\n /* @__PURE__ */ trafficIncidentsKnobCatalogue.map(({ id }) => id);\n\n/**\n * Every {@link TrafficIncidentDetailsModule} knob with its kind, description, range and, where one\n * value holds everywhere, its default. Static data, readable with no map loaded.\n *\n * @remarks\n * The incident lines take the colours of the style's own incident layers, which are\n * {@link TrafficIncidentsModule}'s `colors.*` knobs. Not knobs: `beforeLayerConfig`, `layers`, which\n * takes MapLibre layer overrides, `highlight: false`, which turns the highlight off, and `events`.\n *\n * @group Traffic Incident Details\n */\nexport const trafficIncidentDetailsKnobCatalogue: readonly TrafficIncidentDetailsKnob[] =\n /* @__PURE__ */ withIds(incidentDetailsKnobDefinitions);\n\n/**\n * Every incident details knob id, in catalogue order.\n *\n * @group Traffic Incident Details\n */\nexport const trafficIncidentDetailsKnobIds: readonly TrafficIncidentDetailsKnobId[] =\n /* @__PURE__ */ trafficIncidentDetailsKnobCatalogue.map(({ id }) => id);\n\n/**\n * Every {@link TrafficAreaAnalyticsModule} knob with its kind, description, range or options and,\n * where one value holds everywhere, its default: the module-wide knobs first, then each metric's.\n * Static data, readable with no map loaded.\n *\n * @remarks\n * Not knobs: `colorStops`, `beforeLayerConfig`, `layers` and `events`, which take structured values.\n * The module bounds no height: a height knob's range is the span a UI offers.\n *\n * @group Traffic Area Analytics\n */\nexport const trafficAreaAnalyticsKnobCatalogue: readonly TrafficAreaAnalyticsKnob[] =\n /* @__PURE__ */ buildAreaAnalyticsCatalogue();\n\n/**\n * Every area analytics knob id, in catalogue order.\n *\n * @group Traffic Area Analytics\n */\nexport const trafficAreaAnalyticsKnobIds: readonly TrafficAreaAnalyticsKnobId[] =\n /* @__PURE__ */ trafficAreaAnalyticsKnobCatalogue.map(({ id }) => id);\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 {\n type AreaAnalyticsMetricKey,\n areaAnalyticsMetricKeys,\n type TrafficAreaAnalytics,\n} from '@tomtom-org/maps-sdk/core';\nimport { mask } from '@turf/turf';\nimport type { FeatureCollection, Point, Polygon } from 'geojson';\nimport { isEqual } from 'lodash-es';\nimport type { ExpressionFilterSpecification } from 'maplibre-gl';\nimport {\n AbstractDataOwnedMapModule,\n type CombinedEvents,\n GeoJSONSourceWithLayers,\n type LightDark,\n type ToBeAddedLayerSpec,\n type ToBeAddedLayerSpecWithoutSource,\n} from '../shared';\nimport { getMergedFilter, toExpressionFilter } from '../shared/mapLibreFilterUtils';\nimport {\n addLayers,\n applyLayersVisibility,\n changeLayerProps,\n moveLayerBefore,\n waitUntilMapIsReady,\n} 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 getActiveMetric,\n getDisplayMode,\n} from './layers/areaAnalyticsLayers';\nimport type {\n AreaAnalyticsColorStopsConfig,\n AreaAnalyticsHeightConfig,\n AreaAnalyticsMetricConfig,\n AreaAnalyticsMetricFilter,\n AreaAnalyticsPalette,\n TrafficAreaAnalyticsModuleConfig,\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\nconst isRegionInverted = (config: TrafficAreaAnalyticsModuleConfig | undefined): boolean =>\n (config?.regionPolygon?.fillStyle ?? AREA_ANALYTICS_DEFAULTS.regionPolygon.fillStyle) === 'inverted';\n\n// One surface over three sources, for events and event states alike: hexgrid, square and heatmap\n// are display modes of the same dataset, not three things a caller would scope between.\nconst dataSources: (keyof AreaAnalyticsSourcesWithLayers)[] = ['hexgrid', 'square', 'heatmap'];\n\n// What the sources draw, by source.\ntype AreaAnalyticsShown = {\n [K in keyof AreaAnalyticsSourcesWithLayers]: AreaAnalyticsSourcesWithLayers[K]['shownFeatures'];\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.create(map, {\n * displayMode: 'hexgrid-3d',\n * activeMetric: 'congestionLevel',\n * });\n * const analytics = await trafficAreaAnalytics({ ... });\n * await module.show(analytics);\n * ```\n *\n * @group Traffic Area Analytics\n */\nexport class TrafficAreaAnalyticsModule extends AbstractDataOwnedMapModule<\n AreaAnalyticsSourcesWithLayers,\n TrafficAreaAnalyticsModuleConfig\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 `create()` resolves, {@link TrafficAreaAnalyticsModule.getConfig} always returns a\n * fully-populated configuration — there is no need to reference any default\n * values yourself.\n *\n * `TrafficAreaAnalyticsModule` owns the sources, layers and images it adds, all suffixed per\n * instance. Every call therefore returns a **new, independent** instance, and stacking several\n * of them on one map is a supported thing to do.\n *\n * {@link trafficAreaAnalyticsKnobCatalogue} lists the plain-valued settings of `config` as data.\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.create(map);\n * await module.show(analytics);\n *\n * // Or override specific properties while keeping everything else at default:\n * const module = await TrafficAreaAnalyticsModule.create(map, {\n * displayMode: 'heatmap',\n * metrics: {\n * congestionLevel: { palette: 'heat' },\n * },\n * });\n * ```\n */\n static async create(\n tomtomMap: TomTomMap,\n config?: TrafficAreaAnalyticsModuleConfig,\n ): 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?: TrafficAreaAnalyticsModuleConfig) {\n super(map, config);\n }\n\n // ── AbstractMapModule hooks ──────────────────────────────────────\n\n /** @ignore */\n protected _initSourcesWithLayers(\n config?: TrafficAreaAnalyticsModuleConfig,\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 — omit computedRange\n const lightDark = this.lightDark;\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, lightDark);\n this.regionLineLayerSpec = buildRegionLineLayerSpec(regionLineLayerId, config, lightDark);\n\n // Highlight/outline specs — added beside the GeoJSONSourceWithLayers rather than through them,\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(\n config: TrafficAreaAnalyticsModuleConfig | undefined,\n ): TrafficAreaAnalyticsModuleConfig | undefined {\n const previous = this.config;\n const given = config ?? {};\n\n // Each metric's entry is laid over that metric's defaults, so a config naming one metric\n // keeps every other metric at its defaults and getConfig() always holds a palette and height.\n const defaultKeys = Object.keys(AREA_ANALYTICS_DEFAULTS.metrics) as AreaAnalyticsMetricKey[];\n const filledMetricConfig = Object.fromEntries(\n defaultKeys.map((metric) => [\n metric,\n { ...AREA_ANALYTICS_DEFAULTS.metrics[metric], ...given.metrics?.[metric] },\n ]),\n ) as Record<AreaAnalyticsMetricKey, AreaAnalyticsMetricConfig>;\n\n const merged: TrafficAreaAnalyticsModuleConfig = {\n activeMetric: AREA_ANALYTICS_DEFAULTS.activeMetric,\n displayMode: AREA_ANALYTICS_DEFAULTS.displayMode,\n ...given,\n metrics: filledMetricConfig,\n };\n\n // Set before anything below reads `this.config` — the active metric and its filters. The\n // parent's applyConfig assigns the same value after we return, and emits config-change\n // exactly once.\n this.config = merged;\n this.applyLayerConfig(merged);\n\n if (\n !isEqual(merged.regionPolygon, previous?.regionPolygon) ||\n !isEqual(merged.layers?.regionFill, previous?.layers?.regionFill) ||\n !isEqual(merged.layers?.regionLine, previous?.layers?.regionLine)\n ) {\n this.applyRegionConfig(merged, previous);\n }\n\n if (merged.beforeLayerConfig || previous?.beforeLayerConfig) {\n this.moveLayersToSpecPositions();\n }\n\n this.applyVisibility(merged);\n return merged;\n }\n\n // To where each display mode's data layer spec places it. The specs are built from the config: a\n // mode its before-layer config names no target for goes back to the default position.\n private moveLayersToSpecPositions(): void {\n for (const spec of [\n this.heatmapLayerSpec,\n this.hexFillLayerSpec,\n this.hexExtrusionLayerSpec,\n this.squareFillLayerSpec,\n this.squareExtrusionLayerSpec,\n ]) {\n moveLayerBefore(this.mapLibreMap, spec.id, spec.beforeID);\n }\n }\n\n /** @ignore */\n protected discardShownData(): void {\n this.lastAnalytics = null;\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n const { heatmap, hexgrid, square, region } = this.sourcesWithLayers;\n const shown: AreaAnalyticsShown = {\n heatmap: heatmap.shownFeatures,\n hexgrid: hexgrid.shownFeatures,\n square: square.shownFeatures,\n region: region.shownFeatures,\n };\n this.initSourcesWithLayers(this.config, true);\n\n if (this.lastAnalytics) {\n // Skip _applyConfig — display() applies metrics + visibility over the data.\n this.display(shown);\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.display({\n heatmap: tilesToPointFeatures(analytics),\n hexgrid: tilesToHexFeatures(analytics),\n square: tilesToSquareFeatures(analytics),\n region: this.buildRegionFeatureCollection(analytics.features, isRegionInverted(this.config)),\n });\n for (const handler of this.shownFeaturesHandlers) {\n handler(analytics);\n }\n }\n\n private display(shown: AreaAnalyticsShown): void {\n this.sourcesWithLayers.heatmap.show(shown.heatmap, { automaticVisibility: false });\n this.sourcesWithLayers.hexgrid.show(shown.hexgrid, { automaticVisibility: false });\n this.sourcesWithLayers.square.show(shown.square, { automaticVisibility: false });\n this.sourcesWithLayers.region.show(shown.region, { automaticVisibility: false });\n this.applyLayerConfig(this.config);\n this.applyVisibility(this.config);\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 * Sets the palette of one or more metrics, as {@link AreaAnalyticsMetricConfig.palette} describes.\n *\n * A metric whose `colorStops` are set keeps painting with them; clear those with\n * `setColorStops(undefined)` for the palette to show.\n *\n * @param palette - The palette, or `undefined` to clear it back to `trafficLight`.\n * @param metrics - The metrics to set it on. When omitted, **all** metrics.\n *\n * @example\n * ```typescript\n * module.setPalette('heat'); // every metric\n * module.setPalette('viridis', ['speed', 'freeFlowSpeed']);\n * module.setPalette(undefined, ['speed']); // back to 'trafficLight'\n * ```\n */\n setPalette(palette: AreaAnalyticsPalette | undefined, metrics?: AreaAnalyticsMetricKey[]): void {\n this.setMetricField('palette', palette, metrics);\n this.applyLayerConfig(this.config);\n this.emitConfigChange();\n }\n\n /**\n * Sets explicit colour stops on one or more metrics, which win over their palette.\n *\n * @param colorStops - The stops, or `undefined` to clear them so the palette paints again.\n * @param metrics - The metrics to set them on. When omitted, **all** metrics.\n *\n * @example\n * ```typescript\n * module.setColorStops(\n * { scaleMode: 'raw', stops: [{ value: 0, color: '#2dc653' }, { value: 100, color: '#e03030' }] },\n * ['congestionLevel'],\n * );\n * module.setColorStops(undefined); // every metric back to its palette\n * ```\n */\n setColorStops(colorStops: AreaAnalyticsColorStopsConfig | undefined, metrics?: AreaAnalyticsMetricKey[]): void {\n this.setMetricField('colorStops', colorStops, metrics);\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 this.setMetricField('height', heightConfig, metrics);\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.setFilters({ min: 20, max: 80 });\n *\n * // Filter only a specific metric\n * module.setFilters({ min: 50 }, ['congestionLevel']);\n *\n * // Clear the filters of every metric\n * module.setFilters(undefined);\n * ```\n */\n setFilters(filters: AreaAnalyticsMetricFilter | undefined, metrics?: AreaAnalyticsMetricKey[]): void {\n this.setMetricField('filters', filters, metrics);\n this.applyFiltersToLayers();\n this.emitConfigChange();\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 /** @ignore */\n protected eventStateSources(): (keyof AreaAnalyticsSourcesWithLayers)[] {\n return dataSources;\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, TrafficAreaAnalyticsModuleConfig, TrafficAreaAnalytics> {\n return this.moduleEventsWithShown<AreaAnalyticsTileFeature, TrafficAreaAnalytics>(\n dataSources,\n this.shownFeaturesHandlers,\n );\n }\n\n // ── Private helpers ──────────────────────────────────────────────\n\n // Writes one field of the given metrics' config, every metric's when omitted; `undefined` removes it.\n private setMetricField<FIELD extends keyof AreaAnalyticsMetricConfig>(\n field: FIELD,\n value: AreaAnalyticsMetricConfig[FIELD],\n targets: readonly AreaAnalyticsMetricKey[] = areaAnalyticsMetricKeys,\n ): void {\n const metrics = { ...this.config?.metrics };\n for (const metric of targets) {\n const updated: AreaAnalyticsMetricConfig = { ...metrics[metric] };\n if (value === undefined) {\n delete updated[field];\n } else {\n updated[field] = value;\n }\n metrics[metric] = updated;\n }\n this.config = { ...this.config, metrics };\n }\n\n private get activeMetric(): AreaAnalyticsMetricKey {\n return getActiveMetric(this.config);\n }\n\n private get lightDark(): LightDark {\n return this.tomtomMap.styleLightDarkTheme;\n }\n\n /** @ignore */\n protected applyVisibility(config: TrafficAreaAnalyticsModuleConfig | undefined): void {\n if (config?.visible === false) {\n this.hideAllDataLayers();\n return;\n }\n\n const mode = getDisplayMode(config);\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 // Each mode draws its own data layer and highlight. Outlines are 2D-only — line layers render in\n // the 2D pass and appear behind fill-extrusion prisms. Extrusion highlights are 3D-only.\n this.setLayersVisible([this.heatmapLayerSpec], mode === 'heatmap' && hasHeatmapData);\n this.setLayersVisible(\n [this.hexExtrusionLayerSpec, this.hexExtrusionHighlightLayerSpec],\n mode === 'hexgrid-3d' && hasHexData,\n );\n this.setLayersVisible([this.hexFillLayerSpec, this.hexOutlineLayerSpec], mode === 'hexgrid-2d' && hasHexData);\n this.setLayersVisible(\n [this.squareExtrusionLayerSpec, this.squareExtrusionHighlightLayerSpec],\n mode === 'square-3d' && hasSquareData,\n );\n this.setLayersVisible(\n [this.squareFillLayerSpec, this.squareOutlineLayerSpec],\n mode === 'square-2d' && hasSquareData,\n );\n this.setLayersVisible([this.regionFillLayerSpec, this.regionLineLayerSpec], hasRegionData);\n }\n\n private hideAllDataLayers(): void {\n this.setLayersVisible(\n [\n this.heatmapLayerSpec,\n this.hexFillLayerSpec,\n this.hexExtrusionLayerSpec,\n this.hexOutlineLayerSpec,\n this.hexExtrusionHighlightLayerSpec,\n this.squareFillLayerSpec,\n this.squareExtrusionLayerSpec,\n this.squareOutlineLayerSpec,\n this.squareExtrusionHighlightLayerSpec,\n this.regionFillLayerSpec,\n this.regionLineLayerSpec,\n ],\n false,\n );\n }\n\n // Leaves hidden a layer its `layers` entry hides.\n private setLayersVisible(specs: ToBeAddedLayerSpecWithoutSource[], visible: boolean): void {\n applyLayersVisibility(specs, visible, this.mapLibreMap);\n }\n\n private addHighlightLayersToMap(hexgridSourceId: string, squareSourceId: string): void {\n // Attaching the source completes a spec, but not in a way TS can prove across the union of\n // layer kinds it may be.\n const withSource = (spec: ToBeAddedLayerSpecWithoutSource, source: string) =>\n ({ ...spec, source }) as ToBeAddedLayerSpec;\n addLayers(\n [\n withSource(this.hexOutlineLayerSpec, hexgridSourceId),\n withSource(this.hexExtrusionHighlightLayerSpec, hexgridSourceId),\n withSource(this.squareOutlineLayerSpec, squareSourceId),\n withSource(this.squareExtrusionHighlightLayerSpec, squareSourceId),\n ],\n this.mapLibreMap,\n );\n }\n\n private applyLayerConfig(config: TrafficAreaAnalyticsModuleConfig | 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 const newHexOutlineSpec = buildHexOutlineLayerSpec(this.hexOutlineLayerSpec.id, config);\n const newSquareOutlineSpec = buildSquareOutlineLayerSpec(this.squareOutlineLayerSpec.id, config);\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(newHexOutlineSpec, this.hexOutlineLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareFillSpec, this.squareFillLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionSpec, this.squareExtrusionLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareExtrusionHighlightSpec, this.squareExtrusionHighlightLayerSpec, this.mapLibreMap);\n changeLayerProps(newSquareOutlineSpec, this.squareOutlineLayerSpec, this.mapLibreMap);\n\n this.heatmapLayerSpec = newHeatmapSpec;\n this.hexFillLayerSpec = newHexFillSpec;\n this.hexExtrusionLayerSpec = newHexExtrusionSpec;\n this.hexExtrusionHighlightLayerSpec = newHexExtrusionHighlightSpec;\n this.hexOutlineLayerSpec = newHexOutlineSpec;\n this.squareFillLayerSpec = newSquareFillSpec;\n this.squareExtrusionLayerSpec = newSquareExtrusionSpec;\n this.squareExtrusionHighlightLayerSpec = newSquareExtrusionHighlightSpec;\n this.squareOutlineLayerSpec = newSquareOutlineSpec;\n\n this.applyFiltersToLayers();\n }\n\n private applyRegionConfig(\n config: TrafficAreaAnalyticsModuleConfig,\n previous: TrafficAreaAnalyticsModuleConfig | undefined,\n ): void {\n const lightDark = this.lightDark;\n const newRegionFillSpec = buildRegionFillLayerSpec(this.regionFillLayerSpec.id, config, lightDark);\n const newRegionLineSpec = buildRegionLineLayerSpec(this.regionLineLayerSpec.id, config, lightDark);\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 // Inverting masks every region polygon, so the shown region is rebuilt only when that changes.\n const inverted = isRegionInverted(config);\n if (this.lastAnalytics && inverted !== isRegionInverted(previous)) {\n this.sourcesWithLayers.region.show(\n this.buildRegionFeatureCollection(this.lastAnalytics.features, inverted),\n );\n }\n }\n\n // The active metric's range, together with any filter the cell layers' `layers` entries set.\n private applyFiltersToLayers(): void {\n const metric = this.activeMetric;\n const filter = this.config?.metrics?.[metric]?.filters;\n\n const conditions: ExpressionFilterSpecification[] = [];\n if (filter?.min !== undefined) conditions.push(['>=', ['get', metric], filter.min]);\n if (filter?.max !== undefined) conditions.push(['<=', ['get', metric], filter.max]);\n\n for (const spec of [\n this.hexFillLayerSpec,\n this.hexExtrusionLayerSpec,\n this.squareFillLayerSpec,\n this.squareExtrusionLayerSpec,\n ]) {\n const own = spec.filter ? [toExpressionFilter(spec.filter)] : [];\n this.mapLibreMap.setFilter(spec.id, getMergedFilter('all', [...own, ...conditions]));\n }\n }\n\n private buildRegionFeatureCollection(\n features: TrafficAreaAnalytics['features'],\n inverted: boolean,\n ): 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).geometry : feature.geometry;\n return { ...feature, id, geometry, properties: { id } };\n }),\n };\n }\n}\n","import { indexedMagnitudes, trafficIncidentToIconCategory } from '@tomtom-org/maps-sdk/core';\nimport { isNil } from 'lodash-es';\nimport type { ExpressionFilterSpecification, LayerSpecification } from 'maplibre-gl';\nimport type { ValuesFilter } from '../../shared';\nimport { LayerFilterComposer } from '../../shared/layers/layerFilterComposer';\nimport { buildValuesFilter, getMergedFilter, isCompleteValuesFilter } from '../../shared/mapLibreFilterUtils';\nimport { filterLayersBySources } from '../../shared/SourceWithLayers';\nimport type { TomTomMap } from '../../TomTomMap';\nimport type { FiltersWithAlternatives, TrafficCommonFilter } from '../types/trafficCommonConfig';\nimport type { TrafficFlowFilter, TrafficFlowFilters } from '../types/trafficFlowConfig';\nimport type { DelayFilter, TrafficIncidentsFilter, TrafficIncidentsFilters } from '../types/trafficIncidentsConfig';\n\ntype MapLibreFilter = ExpressionFilterSpecification | null;\n\nconst delayFilterToMapLibre = ({ mustHaveDelay, minDelayMinutes }: DelayFilter): MapLibreFilter => {\n if (minDelayMinutes) {\n const atLeastMinDelay: ExpressionFilterSpecification = ['>=', ['get', 'delay'], minDelayMinutes * 60];\n // Without `mustHaveDelay`, the incidents that report no delay stay.\n return mustHaveDelay\n ? atLeastMinDelay\n : ['any', ['!', ['has', 'delay']], ['==', ['get', 'delay'], 0], atLeastMinDelay];\n }\n return mustHaveDelay ? ['>', ['get', 'delay'], 0] : null;\n};\n\nconst addValuesFilter = <VALUE>(\n valuesFilter: ValuesFilter<VALUE> | undefined,\n propertyName: string,\n expressions: ExpressionFilterSpecification[],\n valuesMapping?: (value: VALUE) => unknown,\n) => {\n if (isCompleteValuesFilter(valuesFilter)) {\n expressions.push(buildValuesFilter(propertyName, valuesFilter, valuesMapping));\n }\n};\n\nconst addCommonFilterExpressions = (sdkFilter: TrafficCommonFilter, expressions: ExpressionFilterSpecification[]) => {\n addValuesFilter(sdkFilter.roadCategories, 'road_category', expressions);\n addValuesFilter(sdkFilter.roadSubCategories, 'road_subcategory', expressions);\n};\n\nconst buildMapLibreIncidentsFilter = (sdkFilter: TrafficIncidentsFilter): MapLibreFilter => {\n const expressions: ExpressionFilterSpecification[] = [];\n\n addCommonFilterExpressions(sdkFilter, expressions);\n addValuesFilter(sdkFilter.incidentCategories, 'icon_category_0', expressions, trafficIncidentToIconCategory);\n addValuesFilter(sdkFilter.magnitudes, 'magnitude_of_delay', expressions, (magnitude) =>\n indexedMagnitudes.indexOf(magnitude),\n );\n const delayFilter = sdkFilter.delays && delayFilterToMapLibre(sdkFilter.delays);\n if (delayFilter) expressions.push(delayFilter);\n\n return getMergedFilter('all', expressions);\n};\n\n// The filter the fields of `filters` set and each of its `any` are alternatives: a feature shows when\n// it matches one of them, so the per-filter expressions are merged under `'any'`. No filters at all\n// means no narrowing, not nothing shown, hence `null` rather than an expression that matches nothing.\nconst buildMapLibreAnyFilters = <FILTER>(\n { any, ...own }: FiltersWithAlternatives<FILTER>,\n buildOne: (filter: FILTER) => MapLibreFilter,\n): MapLibreFilter =>\n getMergedFilter(\n 'any',\n [own as FILTER, ...(any ?? [])].map(buildOne).filter((mapLibreFilter) => !isNil(mapLibreFilter)),\n );\n\n/**\n * @ignore\n */\nexport const buildMapLibreIncidentFilters = (incidentFilters: TrafficIncidentsFilters): MapLibreFilter =>\n buildMapLibreAnyFilters(incidentFilters, buildMapLibreIncidentsFilter);\n\nconst buildMapLibreFlowFilter = (sdkFilter: TrafficFlowFilter): MapLibreFilter => {\n const expressions: ExpressionFilterSpecification[] = [];\n\n addCommonFilterExpressions(sdkFilter, expressions);\n if (sdkFilter.roadClosures) {\n const operator = sdkFilter.roadClosures === 'only' ? '==' : '!=';\n expressions.push([operator, ['get', 'road_closure'], true]);\n }\n\n return getMergedFilter('all', expressions);\n};\n\n/**\n * @ignore\n */\nexport const buildMapLibreFlowFilters = (flowFilters: TrafficFlowFilters): MapLibreFilter =>\n buildMapLibreAnyFilters(flowFilters, buildMapLibreFlowFilter);\n\n/**\n * Narrows the given style layers by `filter`, or with `undefined` leaves them to whatever the style\n * and the other contributors filter them by.\n *\n * One key per module, not per call: a later call is meant to replace what the earlier one put there.\n *\n * @ignore\n */\nexport const applyFilter = (\n filter: ExpressionFilterSpecification | undefined,\n layers: LayerSpecification[],\n filterComposer: LayerFilterComposer,\n contributor: string,\n) => {\n for (const layer of layers) {\n filterComposer.setClause(layer.id, contributor, filter);\n }\n};\n\n/**\n * Where a style-owned traffic module's filters go: every layer of its tile source, under its key\n * with the filter composer.\n * @ignore\n */\nexport type TrafficFiltersTarget<FILTERS extends object> = {\n sourceId: string;\n contributor: string;\n build: (filters: FILTERS) => MapLibreFilter;\n};\n\n/**\n * Narrows the target's layers by `filters`, or without them hands the layers back — written only\n * when there are filters now or were before, so an unfiltered module leaves the composer alone.\n * A no-op until the map is ready.\n * @ignore\n */\nexport const applyTrafficFilters = <FILTERS extends object>(\n tomtomMap: TomTomMap,\n { sourceId, contributor, build }: TrafficFiltersTarget<FILTERS>,\n filters: FILTERS | undefined,\n previousFilters: FILTERS | undefined,\n): void => {\n if (!tomtomMap.mapReady) return;\n\n const filter = filters && build(filters);\n if (filter || previousFilters) {\n applyFilter(\n filter ?? undefined,\n filterLayersBySources(tomtomMap.mapLibreMap, [sourceId]),\n LayerFilterComposer.for(tomtomMap),\n contributor,\n );\n }\n};\n","import {\n AbstractStyleOwnedMapModule,\n type CombinedEvents,\n type StyleSourceWithLayers,\n TRAFFIC_FLOW_SOURCE_ID,\n} from '../shared';\nimport { getStyleOwnedModule, styleSourceWithLayers } from '../shared/styleOwnedModules';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyTrafficFilters, buildMapLibreFlowFilters, type TrafficFiltersTarget } from './filters/trafficFilters';\nimport { trafficFlowLook } from './trafficStyleKnobs';\nimport type { TrafficFlowFilters, TrafficFlowModuleConfig } from './types/trafficFlowConfig';\nimport type { TrafficFlowModuleFeature } from './types/trafficFlowFeature';\nimport { trafficFlowMapping } from './util/trafficFlowMapping';\n\nconst FLOW_FILTERS: TrafficFiltersTarget<TrafficFlowFilters> = {\n sourceId: TRAFFIC_FLOW_SOURCE_ID,\n contributor: 'traffic.flow',\n build: buildMapLibreFlowFilters,\n};\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 * **What this module owns:** whether the style's traffic-flow layers are shown, for which roads,\n * and how they look: each flow level's colour (`colors`) and the line width (`widthFactor`). See [Who owns what](https://docs.tomtom.com/maps-sdk-js/guides/map/modules#who-owns-what).\n *\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-org/maps-sdk/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.updateConfig({\n * filters: { roadCategories: { show: 'only', values: ['motorway', 'trunk'] } },\n * });\n *\n * // Hide local streets\n * trafficFlow.updateConfig({\n * filters: { roadCategories: { show: 'all-except', values: ['street'] } },\n * });\n *\n * // Show only road closures\n * trafficFlow.updateConfig({ filters: { roadClosures: 'only' } });\n *\n * // Show all traffic again\n * trafficFlow.updateConfig({ filters: undefined });\n * ```\n *\n * @example\n * Restyle the flow:\n * ```typescript\n * trafficFlow.updateConfig({ colors: { slow: '#ffb000', stationary: '#b00020' }, widthFactor: 1.5 });\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 AbstractStyleOwnedMapModule<\n TrafficFlowSourcesWithLayers,\n TrafficFlowModuleConfig\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 * {@link trafficFlowKnobCatalogue} lists the plain-valued settings as data.\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 * @remarks\n * **Instances:**\n * `TrafficFlowModule` controls the traffic-flow source and layers the map style already\n * provides, under fixed global IDs. Every instance is another handle on that same shared state,\n * so visibility and filters applied through one are visible through all of them.\n */\n static get(map: TomTomMap, config?: TrafficFlowModuleConfig): Promise<TrafficFlowModule> {\n return getStyleOwnedModule(map, TrafficFlowModule, () => new TrafficFlowModule(map, config), config, {\n sourceId: TRAFFIC_FLOW_SOURCE_ID,\n part: 'trafficFlow',\n });\n }\n\n private constructor(map: TomTomMap, config?: TrafficFlowModuleConfig) {\n super(map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n return {\n trafficFlow: styleSourceWithLayers(this.mapLibreMap, TRAFFIC_FLOW_SOURCE_ID, TrafficFlowModule.name),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: TrafficFlowModuleConfig | undefined) {\n this._setVisible(config?.visible ?? false, false);\n applyTrafficFilters(this.tomtomMap, FLOW_FILTERS, config?.filters, this.config?.filters);\n trafficFlowLook.apply(this.tomtomMap, config);\n return config;\n }\n\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return super._knobValue(id) ?? trafficFlowLook.valueOf(this.tomtomMap, id);\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 * Whether traffic flow is set to be shown.\n *\n * @returns The `visible` setting; `false` while nothing has set it, as traffic is hidden until shown.\n */\n isVisible(): boolean {\n return this.config?.visible ?? false;\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 * The only way to read live flow values: Orbis traffic flow is vector tiles, with no service for a\n * road, a point or an area. Results cover the current viewport and zoom, and a segment crossing a\n * tile boundary appears once per tile. See {@link TrafficFlowModuleFeature} for what each carries.\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.relativeSpeed < 0.5\n * );\n * console.log(`Congested segments: ${congested.length}`);\n * ```\n */\n getShown(): { trafficFlow: TrafficFlowModuleFeature[] } {\n return {\n trafficFlow: this.sourcesWithLayers.trafficFlow.queryRenderedFeatures().map(trafficFlowMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficFlowModuleFeature, TrafficFlowModuleConfig, never> {\n return this.moduleEvents<TrafficFlowModuleFeature>(['trafficFlow'], { mapping: trafficFlowMapping });\n }\n}\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 DelayMagnitude, indexedMagnitudes } from '@tomtom-org/maps-sdk/core';\nimport type { Map as MapLibreMap } from 'maplibre-gl';\n\n/** @ignore */\nexport type IncidentPalette = {\n outline: Record<DelayMagnitude, string>;\n inner: Record<DelayMagnitude, string>;\n};\n\ntype LayerIdPair = { outline: string; inner: string };\n\n// Layer IDs we pull colours from, matched with surrounding whitespace ignored: the style ships\n// `'TrafficIncidents - Minor jam '`. Update when the loaded style renames them.\nconst LAYER_IDS: Record<DelayMagnitude, 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/**\n * Fallback used only when the loaded style's incident layers can't be read as plain colour strings.\n * @ignore\n */\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.trim(), 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 indexedMagnitudes) {\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 '[TrafficIncidentDetailsModule] 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\n const paint = (layer as { paint?: unknown }).paint;\n if (!paint || typeof paint !== 'object') return undefined;\n\n const color = (paint as { 'line-color'?: unknown })['line-color'];\n if (typeof color === 'string') return color;\n\n if (color !== undefined) nonLiteralSink.push(layerId);\n return undefined;\n};\n","import type { TrafficIncident, TrafficIncidentDetails } from '@tomtom-org/maps-sdk/core';\nimport {\n AbstractDataOwnedMapModule,\n type BeforeLayerConfig,\n type CombinedEvents,\n GeoJSONSourceWithLayers,\n} from '../shared';\nimport { beforeLayerIDOf } from '../shared/layers/beforeLayer';\nimport {\n addOrUpdateImage,\n applyLayersVisibility,\n moveLayerBefore,\n updateLayersAndSource,\n waitUntilMapIsReady,\n} from '../shared/mapUtils';\nimport { incidentColorKnobIds, onStyleKnobsApplied } from '../shared/styleKnobsApplied';\nimport type { TomTomMap } from '../TomTomMap';\nimport {\n buildCanonicalIncidentLineLayers,\n buildCanonicalIncidentSymbolLayers,\n resolveHighlightStyle,\n} from './layers/incidentDetailsLayers';\nimport type { TrafficIncidentDetailsModuleConfig } from './types/trafficIncidentDetailsConfig';\nimport { getIncidentDirectionChevronImage, INCIDENT_DIRECTION_CHEVRON_IMAGE_ID } from './util/incidentDirectionChevron';\nimport { readIncidentPalette } from './util/readIncidentPalette';\n\ntype Sources = {\n incidents: GeoJSONSourceWithLayers<TrafficIncidentDetails>;\n};\n\nconst DEFAULT_BEFORE_LAYER: BeforeLayerConfig = 'lowestLabel';\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 this module renders\n * on its own without collision; if you've enabled it elsewhere in your app, hide it\n * again before using this module to avoid double-rendering.\n *\n * ## Rendering approach\n *\n * 7–8 layers — 4 to 5 line (optional highlight-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's\n * incident layers (see {@link ./util/readIncidentPalette}), falling back to built-in defaults\n * when absent, and read again whenever a `traffic.incidents.*Color` styling knob recolours\n * them. 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 and highlight\n *\n * {@link setFocus} writes MapLibre feature-state `focused=true|false` on each rendered\n * incident. The default highlight widens the focused subset and paints an\n * outline beneath it (black on light map styles, white on dark); unfocused incidents\n * are unchanged. A hovered or clicked incident (its `eventState`, see\n * {@link putEventState}) draws the same highlight. Override or disable it via the `highlight`\n * config — `highlight: false` keeps `setFocus` writing feature-state but emits no halo layer\n * and no width pop, leaving callers free to drive their own styling off\n * `feature-state.focused` and `eventState`.\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, TrafficIncidentDetailsModule } 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 incidentDetailsModule = await TrafficIncidentDetailsModule.create(map);\n * const result = await trafficIncidentDetails({\n * bbox: [4.85, 52.34, 4.95, 52.40],\n * timeValidityFilter: ['present'],\n * });\n * await incidentDetailsModule.show(result);\n *\n * // Highlight a subset.\n * incidentDetailsModule.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 this module to avoid double-rendering.\n * @see [Traffic Incident Details Guide](https://docs.tomtom.com/maps-sdk-js/guides/map/traffic-incident-details)\n * @see [Traffic Incident Details Service](https://docs.tomtom.com/maps-sdk-js/guides/services/traffic/traffic-incident-details)\n *\n * @group Traffic Incident Details\n */\nexport class TrafficIncidentDetailsModule extends AbstractDataOwnedMapModule<\n Sources,\n TrafficIncidentDetailsModuleConfig\n> {\n private lastFeatureIds: string[] = [];\n private lastFocusIds: readonly string[] | null = null;\n private readonly shownFeaturesHandlers: ((result: TrafficIncidentDetails) => void)[] = [];\n\n /**\n * Creates a new module instance on the given map, waiting until the map is ready first.\n *\n * `TrafficIncidentDetailsModule` owns the sources, layers and images it adds, all suffixed per\n * instance. Every call therefore returns a **new, independent** instance, and stacking several\n * of them on one map is a supported thing to do.\n *\n * {@link trafficIncidentDetailsKnobCatalogue} lists the plain-valued settings of `config` as data.\n *\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 create(\n tomtomMap: TomTomMap,\n config?: TrafficIncidentDetailsModuleConfig,\n ): Promise<TrafficIncidentDetailsModule> {\n await waitUntilMapIsReady(tomtomMap);\n return new TrafficIncidentDetailsModule(tomtomMap, config ?? {});\n }\n\n private constructor(map: TomTomMap, config?: TrafficIncidentDetailsModuleConfig) {\n super(map, config);\n onStyleKnobsApplied(map, Object.values(incidentColorKnobIds), () => this.updateLayers(this.config));\n }\n\n // The layers in stacking order, the lines in the incident colours of the loaded style.\n private buildLayerSpecs(sourceId: string, config: TrafficIncidentDetailsModuleConfig | undefined) {\n const beforeID = beforeLayerIDOf(config?.beforeLayerConfig ?? DEFAULT_BEFORE_LAYER);\n const highlight = resolveHighlightStyle(config?.highlight, this.tomtomMap.styleLightDarkTheme);\n return [\n ...buildCanonicalIncidentLineLayers(\n sourceId,\n beforeID,\n readIncidentPalette(this.mapLibreMap),\n highlight,\n config?.layers,\n ),\n ...buildCanonicalIncidentSymbolLayers(sourceId, beforeID, config?.layers),\n ];\n }\n\n // Brings the layers on the map to the config, the highlight halo coming and going with `highlight: false`.\n // `updateLayersAndSource` records an added layer last, while `applyBeforeLayer` stacks the layers\n // in recorded order and the halo belongs beneath the rest.\n private updateLayers(config: TrafficIncidentDetailsModuleConfig | undefined): void {\n const { incidents } = this.sourcesWithLayers;\n const layerSpecs = this.buildLayerSpecs(incidents.sourceAndLayerIDs.sourceID, config);\n updateLayersAndSource(layerSpecs, incidents._layerSpecs, incidents, this.mapLibreMap);\n const stackingOrder = layerSpecs.map(({ id }) => id);\n incidents._layerSpecs.sort((a, b) => stackingOrder.indexOf(a.id) - stackingOrder.indexOf(b.id));\n incidents._updateSourceAndLayerIDs();\n }\n\n /** @ignore */\n protected _initSourcesWithLayers(config?: TrafficIncidentDetailsModuleConfig, _restore?: boolean): Sources {\n const sourceId = `traffic-incident-details-${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 return {\n incidents: new GeoJSONSourceWithLayers<TrafficIncidentDetails>(\n this.mapLibreMap,\n sourceId,\n this.buildLayerSpecs(sourceId, config),\n ),\n };\n }\n\n /** @ignore */\n protected _applyConfig(\n config: TrafficIncidentDetailsModuleConfig | undefined,\n ): TrafficIncidentDetailsModuleConfig | undefined {\n this.updateLayers(config);\n this.applyVisibility(config);\n this.applyBeforeLayer(config?.beforeLayerConfig);\n return config;\n }\n\n /** @ignore */\n protected applyVisibility(config: TrafficIncidentDetailsModuleConfig | undefined): void {\n applyLayersVisibility(\n this.sourcesWithLayers.incidents._layerSpecs,\n config?.visible !== false,\n this.mapLibreMap,\n );\n }\n\n private applyBeforeLayer(beforeLayerConfig?: BeforeLayerConfig): void {\n const beforeId = beforeLayerIDOf(beforeLayerConfig ?? DEFAULT_BEFORE_LAYER);\n for (const lid of this.sourcesWithLayers.incidents.sourceAndLayerIDs.layerIDs) {\n moveLayerBefore(this.mapLibreMap, lid, beforeId);\n }\n }\n\n /** @ignore */\n protected discardShownData(): void {\n this.lastFeatureIds = [];\n this.lastFocusIds = null;\n }\n\n /** @ignore */\n protected restoreDataAndConfigImpl(): void {\n const shown = this.sourcesWithLayers.incidents.shownFeatures;\n this.initSourcesWithLayers(this.config, true);\n this.sourcesWithLayers.incidents.show(shown, { automaticVisibility: false });\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.lastFeatureIds = result.features\n .map((f) => f.properties.id)\n .filter((id): id is string => typeof id === 'string');\n this.sourcesWithLayers.incidents.show(result, { automaticVisibility: false });\n this.sourcesWithLayers.incidents.setLayersVisible(this.isVisible());\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.lastFeatureIds = [];\n this.lastFocusIds = null;\n this.sourcesWithLayers.incidents.clear();\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 /** @ignore */\n protected eventStateSources(): (keyof Sources)[] {\n return ['incidents'];\n }\n\n get events(): CombinedEvents<TrafficIncident, TrafficIncidentDetailsModuleConfig, TrafficIncidentDetails> {\n return this.moduleEventsWithShown<TrafficIncident, TrafficIncidentDetails>(\n ['incidents'],\n 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 + a theme-aware outline, the `highlight` config). Unfocused\n * features are unchanged — the highlight 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\n this.lastFocusIds = ids;\n const focusSet = new Set<string>(ids);\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featureId of this.lastFeatureIds) {\n this.mapLibreMap.setFeatureState({ source: sourceId, id: featureId }, { focused: focusSet.has(featureId) });\n }\n }\n\n private clearFeatureState(): void {\n if (this.lastFeatureIds.length === 0) return;\n\n const sourceId = this.sourcesWithLayers.incidents.sourceAndLayerIDs.sourceID;\n for (const featureId of this.lastFeatureIds) {\n this.mapLibreMap.removeFeatureState({ source: sourceId, id: featureId });\n }\n }\n}\n","import { isEmpty, isNil, omit } from 'lodash-es';\nimport {\n AbstractStyleOwnedMapModule,\n type CombinedEvents,\n type StyleSourceWithLayers,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n} from '../shared';\nimport { getStyleOwnedModule, styleSourceWithLayers } from '../shared/styleOwnedModules';\nimport type { TomTomMap } from '../TomTomMap';\nimport { applyTrafficFilters, buildMapLibreIncidentFilters, type TrafficFiltersTarget } from './filters/trafficFilters';\nimport { trafficIncidentsLook } from './trafficStyleKnobs';\nimport type { TrafficIncidentsFilters, TrafficIncidentsModuleConfig } from './types/trafficIncidentsConfig';\nimport type { TrafficIncidentsModuleFeature } from './types/trafficIncidentsFeature';\nimport { trafficIncidentMapping } from './util/trafficIncidentMapping';\n\nconst INCIDENTS_FILTERS: TrafficFiltersTarget<TrafficIncidentsFilters> = {\n sourceId: TRAFFIC_INCIDENTS_SOURCE_ID,\n contributor: 'traffic.incidents',\n build: buildMapLibreIncidentFilters,\n};\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 * **What this module owns:** whether the style's incident layers are shown, which incidents, and\n * how their lines look: each magnitude's colour (`colors`) and the line width (`widthFactor`). See [Who owns what](https://docs.tomtom.com/maps-sdk-js/guides/map/modules#who-owns-what).\n *\n * **Features:**\n * - Toggle incidents visibility on/off\n * - Separate visibility 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-org/maps-sdk/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.updateConfig({\n * filters: { incidentCategories: { show: 'only', values: ['accident', 'road-closed'] } },\n * });\n *\n * // Hide construction\n * trafficIncidentsModule.updateConfig({\n * filters: { incidentCategories: { show: 'all-except', values: ['roadworks'] } },\n * });\n * ```\n *\n * @example\n * Filter by severity:\n * ```typescript\n * // Show only major delays\n * incidents.updateConfig({ filters: { magnitudes: { show: 'only', values: ['major'] } } });\n *\n * // Show incidents with at least 10 minutes delay\n * incidents.updateConfig({ filters: { delays: { mustHaveDelay: true, minDelayMinutes: 10 } } });\n *\n * // Show every incident again\n * incidents.updateConfig({ filters: undefined });\n * ```\n *\n * @example\n * Restyle the incident lines:\n * ```typescript\n * incidents.updateConfig({ colors: { major: '#d0021b' }, widthFactor: 1.5 });\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 TrafficIncidentDetailsModule} — for rendering incidents fetched via the\n * `trafficIncidentDetails()` REST service as a developer-controlled overlay (use the\n * details module 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 AbstractStyleOwnedMapModule<\n TrafficIncidentsSourcesWithLayers,\n TrafficIncidentsModuleConfig\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, for lines and icons alike\n *\n * {@link trafficIncidentsKnobCatalogue} lists the plain-valued settings as data.\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 * @remarks\n * **Instances:**\n * `TrafficIncidentsModule` controls the traffic-incident source and layers the map style already\n * provides, under fixed global IDs. Every instance is another handle on that same shared state,\n * so visibility and filters applied through one are visible through all of them.\n */\n static get(map: TomTomMap, config?: TrafficIncidentsModuleConfig): Promise<TrafficIncidentsModule> {\n return getStyleOwnedModule(map, TrafficIncidentsModule, () => new TrafficIncidentsModule(map, config), config, {\n sourceId: TRAFFIC_INCIDENTS_SOURCE_ID,\n part: 'trafficIncidents',\n });\n }\n\n private constructor(map: TomTomMap, config?: TrafficIncidentsModuleConfig) {\n super(map, config);\n }\n\n /**\n * @ignore\n */\n protected _initSourcesWithLayers() {\n return {\n trafficIncidents: styleSourceWithLayers(\n this.mapLibreMap,\n TRAFFIC_INCIDENTS_SOURCE_ID,\n TrafficIncidentsModule.name,\n ),\n };\n }\n\n /**\n * @ignore\n */\n protected _applyConfig(config: TrafficIncidentsModuleConfig | 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 applyTrafficFilters(this.tomtomMap, INCIDENTS_FILTERS, config?.filters, this.config?.filters);\n trafficIncidentsLook.apply(this.tomtomMap, config);\n return config;\n }\n\n /**\n * @ignore\n */\n _knobValue(id: string): unknown {\n return super._knobValue(id) ?? trafficIncidentsLook.valueOf(this.tomtomMap, id);\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. It stores\n * `icons.visible`, which overrides `visible` for the icons until the next {@link setVisible}:\n * `setVisible(false)` then `setIconsVisible(true)` shows the icons alone.\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, and clears\n * `icons.visible`, so the icons follow `visible` again.\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 const { icons, ...config } = this.config ?? {};\n const otherIconSettings = omit(icons, 'visible');\n this.config = { ...config, ...(!isEmpty(otherIconSettings) && { icons: otherIconSettings }), visible };\n this.emitConfigChange();\n }\n if (this.tomtomMap.mapReady) {\n this.sourcesWithLayers.trafficIncidents.setLayersVisible(visible);\n }\n }\n\n /**\n * Whether traffic incidents are set to be shown.\n *\n * @returns The `visible` setting; `false` while nothing has set it, as traffic is hidden until\n * shown. {@link isIconsVisible} reads the icons setting.\n *\n * @example\n * ```typescript\n * if (incidents.isVisible()) {\n * console.log('Incidents are displayed');\n * }\n * ```\n */\n isVisible(): boolean {\n return this.config?.visible ?? false;\n }\n\n /**\n * Whether the incident icons are set to be shown.\n *\n * @returns The `icons.visible` setting and, while it is unset, {@link isVisible}. It is `true`\n * for the icons-only state, `{ visible: false, icons: { visible: true } }`.\n *\n * @example\n * ```typescript\n * incidents.setVisible(false);\n * incidents.setIconsVisible(true);\n * incidents.isIconsVisible(); // true: the icons show alone\n * ```\n */\n isIconsVisible(): boolean {\n return this.config?.icons?.visible ?? this.isVisible();\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.sourcesWithLayers.trafficIncidents\n .queryRenderedFeatures()\n .map(trafficIncidentMapping),\n };\n }\n\n get events(): CombinedEvents<TrafficIncidentsModuleFeature, TrafficIncidentsModuleConfig, never> {\n return this.moduleEvents<TrafficIncidentsModuleFeature>(['trafficIncidents'], {\n mapping: trafficIncidentMapping,\n });\n }\n}\n","import type { BBox } from '@tomtom-org/maps-sdk/core';\nimport { bboxCenter } 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\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 reference - Either an HTMLElement or a DOM selector string\n * @returns The resolved HTMLElement, or null if not found\n */\nconst toElement = (reference: HTMLElement | string): HTMLElement | null => {\n if (typeof reference === 'string') {\n return document.querySelector(reference);\n }\n return reference;\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 elementReference of surroundingElements) {\n const element = toElement(elementReference);\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 southWest = mapLibreMap.unproject([visibleAreaBounds.left, visibleAreaBounds.bottom]);\n const northEast = mapLibreMap.unproject([visibleAreaBounds.right, visibleAreaBounds.top]);\n\n return [southWest.lng, southWest.lat, northEast.lng, northEast.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 return bboxCenter(bbox);\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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B,UAChEuD,UAAW,CAAC,aACZtD,SAAU,CACN,OACA,MACA,SACA,OACA,OACA,MACA,OACA,SACA,SACA,QACA,WACA,QACA,cACA,UACA,gBACA,UACA,MACA,SACA,OACA,YACA,iBACA,QACA,SACA,QACA,SACA,SACA,aACA,QAEJE,uBAAwB,CAAC,2BAA4B,yBACrDE,YAAa,CAAC,iBAAkB,cAAe,kBAAmB,iBAClEmD,kBAAmB,CAAC,oBACpBC,kBAAmB,CAAC,kCACpB/C,UAAW,CACP,kBACA,kBACA,kBACA,aACA,aACA,YACA,eACA,gBAEJC,yBAA0B,CACtB,0BACA,2BACA,cACA,eACA,kBACA,qBACA,sBACA,OACA,cACA,kBACA,sBACA,mBAEJE,iBAAkB,CAAC,yBACnB6C,sBAAuB,CACnB,eACA,cACA,aACA,yBACA,mBACA,0BAEJ5C,SAAU,CACN,aACA,gBACA,wCACA,iCACA,qCAEJ6C,YAAa,CAAC,eACdC,WAAY,CAAC,oBACbvC,sBAAuB,CACnB,YACA,oCACA,iBACA,iBACA,mBAEJC,gBAAiB,CAAC,iCAAkC,4BAA6B,iBAAkB,iBACnGK,WAAY,CACR,oBACA,qBACA,sBACA,qBACA,sBACA,mBACA,wBACA,sBACA,gBACA,sBACA,oBACA,qBACA,SACA,sBACA,uBACA,qBACA,oBACA,uBACA,qBACA,yBACA,uBACA,sBACA,uBACA,oBACA,qBACA,qBACA,qBACA,uBACA,sBACA,oBACA,WACA,mBACA,mBACA,oBACA,iBACA,qBACA,sBACA,mBACA,sBACA,qBACA,uBACA,qBACA,oBACA,oBACA,oBACA,oBACA,mBACA,mBACA,uBACA,sBACA,qBACA,mBACA,uBACA,oBACA,wBACA,2BACA,qBACA,mBACA,qBACA,qBACA,sBACA,sBACA,oBACA,oBACA,oBACA,4BACA,sBACA,2BACA,qBACA,4BACA,uBACA,sBACA,sBACA,uBACA,0BACA,sBACA,uBACA,sBACA,wBACA,WACA,oBACA,wBACA,qBACA,YACA,sBACA,0BACA,UACA,sBACA,qBACA,oBACA,YACA,kBACA,qBACA,cACA,mBACA,qBACA,mBACA,oBACA,uBACA,eACA,mBACA,wBACA,kBACA,qBACA,sBACA,qBACA,wBACA,wBACA,wBACA,sBAEJC,uBAAwB,CACpB,aACA,0BACA,iBACA,cACA,kBACA,gBACA,iBACA,qBACA,qBAEJC,cAAe,CACX,wBACA,mBACA,mBACA,SACA,kBACA,oCACA,YACA,UACA,0BACA,yBACA,4BACA,iCACA,sCACA,uBACA,oBACA,oBACA,kBACA,eACA,yBACA,4BACA,cACA,6BACA,iBACA,aACA,WACA,wBACA,6BACA,0BACA,kCACA,yBACA,sBACA,cACA,gBACA,iBACA,aACA,0CACA,yBACA,qBACA,UACA,iBACA,0BACA,eACA,6BACA,oBACA,0BACA,0BACA,mBACA,WACA,uBACA,sBACA,YACA,eACA,iBACA,2BACA,iBACA,gBACA,oBACA,mBACA,kBACA,gCACA,cACA,kBACA,gBACA,oBACA,iBACA,gBAEJC,eAAgB,CACZ,kBACA,gBACA,mBACA,iBACA,iBACA,cACA,qBACA,aACA,eACA,eACA,yBAEJC,QAAS,CAAC,mBAAoB,uBAAwB,wBAAyB,kBAAmB,kBAClG8B,eAAgB,CAAC,kBACjB3B,WAAY,CAAC,sCACb4B,UAAW,CAAC,aACZ3B,QAAS,CAAC,gBACVE,mBAAoB,CAAC,OAAQ,yBAA0B,8BACvD0B,iBAAkB,CAAC,sBAAuB,SAAU,eACpDvB,eAAgB,CACZ,aACA,cACA,aACA,aACA,oBACA,8BACA,eACA,cAEJE,eAAgB,CACZ,WACA,6BACA,yBACA,wBACA,oBACA,eACA,4BAEJE,WAAY,CAAC,kBACbC,UAAW,CAAC,SACZmB,cAAe,CAAC,cAChBjB,gBAAiB,CAAC,gBAAiB,QAwBjCkB,iCAA0EjyC,OAAOqP,YACnF,CAAC67B,iBAAkB8F,mBAAmB/3B,QAASi5B,GAC3ClyC,OAAOsD,QAAQ4uC,GAAOj5B,QAAA,EAAUk5B,EAAiBC,KAC7CA,EAAWlyC,IAAK4U,GAAa,CAACA,EAAUq9B,OAQvCE,qBAAwBv9B,GAA8Cm9B,kBAAkBn9B,GAQxFw9B,yBAA8D,mBACpE,IAAIthC,IAAI,IAAIhR,OAAOC,KAAKirC,qBAAsBlrC,OAAOC,KAAK+wC,sBA6G3DuB,qCAAgEvyC,OAAOqP,YACzErP,OAAOsD,QAAQ,CAxGfkvC,cAAe,CACX,MACA,OACA,YACA,cACA,YACA,iBACA,YACA,MACA,aACA,aAEJC,SAAU,CACN,gBACA,mBACA,uBACA,OACA,cACA,gBACA,eAEJC,QAAS,CAAC,MAAO,OAAQ,mBAAoB,gBAAiB,kBAC9DC,QAAS,CAAC,iBAAkB,eAAgB,kBAC5CC,UAAW,CACP,UACA,cACA,iBACA,sBACA,SACA,wBACA,kBACA,aACA,oBAEJC,QAAS,CACL,iBACA,aACA,oBACA,eACA,wBACA,aACA,aACA,iBACA,qBACA,iBACA,eACA,eAEJC,QAAS,CAAC,mBAAoB,sBAC9BC,WAAY,CAAC,SAAU,UAAW,SAAU,0BAA2B,WAAY,WAAY,cAC/FC,UAAW,CAAC,YAAa,wBAAyB,oBAAqB,0BACvEC,SAAU,CAAC,kBAAmB,gBAAiB,SAAU,cAAe,WACxEC,QAAS,CACL,iBACA,eACA,sBACA,SACA,cACA,YACA,+BACA,qBACA,sBACA,mBAEJC,MAAO,CACH,iBACA,cACA,eACA,WACA,iBACA,UACA,gBACA,iBAEJC,QAAS,CAAC,kBAAmB,WAAY,gBAAiB,2BAA4B,aACtFC,UAAW,CAAC,kBACZC,OAAQ,CACJ,WACA,aACA,eACA,oBACA,iBACA,aACA,cACA,SACA,kBAEJC,SAAU,CAAC,qBACXC,SAAU,CAAC,kBACXC,SAAU,CACN,iBACA,kBACA,oBACA,mBACA,kBACA,yBACA,iBACA,oBACA,aACA,2BAK4Bx6B,QAAA,EAAU6f,EAAOsZ,KAAgBA,EAAWlyC,IAAK4U,GAAa,CAACA,EAAUgkB,MCxoBhG4a,MAAQ,QAuCRC,kBAAwD,CACjE,aAAc,CAAC,MAnCI,UAoCnB,cAAe,SACf,YAAa1c,cACb,sBAAsB,EACtB,eAAgB,GAQP2c,cAAkC,CAAC,EAAG,GAKtCC,iBAAsD,CAC/D,iBAAkBD,cAClB,wBAAyB,YAMhBE,kBAAwD,CACjE,iBAAiB,EACjB,YAAa,C9BlDY,a8BmDzB,aAAc,CAAC,MAAOJ,OACtB,eAAgB,OAChB,uBAAwB,CAAC,MAAO,OAAQ,SAExC,8BAA+B,CAC3B,MACA,CAAC,EA9D4B,IA+D7B,OACA,CAzD6B,KAyDL,KACxB,QACA,EAAC,KAAwB,MAE7B,YAAa3c,kBACb,eAAgB,GAUPgd,iBAAsD,CAC/D,kBAAmB,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,EAAG,EAAG,GAAI,KACnE,wBAAyB,YAOhBC,iBAAgE,CACzEj3C,KAAM,SACNqb,OAAQ,IAAKu7B,qBAAsBG,mBACnC/1B,MAAO,IAAK81B,oBAAqBE,mBCpG/BE,gBAA2C,CAAC,MAAO,eAI5CC,mBAA8C,CAAC,IAAKD,iBAYpDE,mCAAqC,wBAgB5CC,8BAAyD,CAC3D,MACAH,gBACA,CAAC,KAAM,CAAC,WAAY,IAAK,CAAC,MAnBoB,2BAmByB,IAKrEI,6BAAwD,CAC1D,MACAJ,gBACA,CAAC,KAAM,CAAC,WAAY,IAAK,CAAC,MA3BoB,0BA2ByB,IA2C9DK,4BAAgD,CAAC,IAAI,IAiB5DC,kBAAqBC,GAAoCA,GAAOlQ,QAAUgQ,4BAE1EG,iBAAA,CACFD,EACAE,KAAA,CAEA,gBAAiBA,EACjB,eAAgBF,GAAOpqC,OAAA,UACvB,sBAAuBoqC,GAAOG,aAAA,UAC9B,sBAAuBH,GAAOI,aAAA,MAG5BC,gBAAmBL,IAAA,CACrB,aAAc,CAAC,MAAO,2BACtB,YAAa,C/BvGY,a+BwGzB,YAAaA,GAAO7lC,OAAO8b,MAAA,GAC3B,sBAAsB,EACtB,yBAAyB,IA+GhBqqB,mCAAqC,CAAAX,CAC7CA,qCAxBsCxqC,eAtLM,gBAsLkBC,eAhMjB,wBAgM4C,CAC1F,CACI,OACA,CAAC,KAAM,CAAC,MAAOA,gBAAiB,MAChC,CAAC,eACD,CAAC,KAAM,CAAC,eAAgB,MACxB,CAAC,MAAOA,gBACR,CAAC,KAAM,CAAC,WAAY,CAAC,SAAU,IAAK,CAAC,MAAOA,gBAAiB,KAAM,CAAC,SAAU,IAAK,CAAC,eAAgB,MAAO,GAC3G,CAAC,eACD,CAAC,SAAU,CAAC,eAAgB,IAAK,CAAC,MAAOA,kBAE7C,CAAC,MAAOD,mBA6BNorC,iCAAoCC,IACtC,MAAMC,EAA6C,CAAC,QAASD,GAC7D,IAAK,MAAMlgC,KAAYw9B,yBAA0B,CAC7C,MAAM4C,EAAW,OAAOpgC,IACxBmgC,EAAK5vC,KAAK6vC,EAAU,CAAC,QAASA,GAClC,CAEA,OADAD,EAAK5vC,KAAK,CAAC,QD/N+B,sBCgOnC4vC,GChLEE,mBAAA,CACT5yB,EACA6yB,KAEA,IAAK7yB,EACD,OAGJ,MAAM8yB,EApEG,CAA0B9yB,IACnC,IACI,GAAqB,iBAAVA,EAAoB,CAC3B,GAAIA,EAAMnkB,SAAS,QAAS,CAExB,MAAMk3C,EAAaj8B,SAASkJ,GAGtBgzB,EAAUD,EAAWE,aAAa,WACxC,GAAID,EAAS,CACT,MAAME,EAAQF,EAAQG,MAAM,OAC5B,GAAqB,IAAjBD,EAAMx0C,OACN,MAAO,CACHwgB,MAAO7iB,OAAO+2C,WAAWF,EAAM,IAC/B/zB,OAAQ9iB,OAAO+2C,WAAWF,EAAM,IAG5C,CAGA,MAAMh0B,EAAQ6zB,EAAWE,aAAa,SAChC9zB,EAAS4zB,EAAWE,aAAa,UACvC,GAAI/zB,GAASC,EACT,MAAO,CACHD,MAAO7iB,OAAO+2C,WAAWl0B,GACzBC,OAAQ9iB,OAAO+2C,WAAWj0B,GAGtC,CAEA,OAAO,IACX,CAEI,OAAIa,EAAMR,UAAYQ,EAAMP,aAAe,EAChC,CACHP,MAAOc,EAAMP,aACbN,OAAQa,EAAMqzB,eAGf,IAEf,CAAA,MAASj1B,GAEL,OADA5Y,QAAQC,KAAK,sCAAuC2Y,GAC7C,IACX,GAwBmBk1B,CAAuBtzB,GAC1C,IAAK8yB,EACD,OAGJ,MAAMS,EAAsC,aAAfV,GAA4C,gBAAfA,EAIpDW,EAAYD,EAtFY,GANJ,IA6FpBE,EAAWF,EAtFY,GANJ,IA8FzB,MAAO,CAAEG,YAAaZ,EAAW3zB,OAASq0B,EAAWG,WAAYb,EAAW5zB,MAAQu0B,IC5F3EG,uBAAA,CAA0BC,EAAuB,MAC1D,MAAMC,iBAAc,IAAIvzC,IAExB,IAAK,MAAMwzC,KAAQF,EAAO,CACtB,IAAKE,EAAK/zB,MACN,SAGJ,MAAM+hB,EAAS,CAAE7S,EAAG6kB,EAAKC,SAAW,EAAG7kB,EAAG4kB,EAAKE,SAAW,GACzC,IAAblS,EAAO7S,GAAwB,IAAb6S,EAAO5S,GAI7B2kB,EAAY3zC,IAAI4zC,EAAKn2C,GAAImkC,EAC7B,CAEA,OAAO+R,GAaEI,0BAAA,CACTJ,EACAK,EHpCmB,SGqCnB/3C,EAAmE,CAAC,EAAG,MAEvE,MAAMg4C,EAAkC,CAAC,QACzC,IAAK,MAAOC,EAAQtS,KAAW+R,EACV,IAAb/R,EAAO7S,GAAwB,IAAb6S,EAAO5S,GAG7BilB,EAAqBtxC,KAAK,CAAC,KAAM,CAAC,MAAOqxC,GAAkBE,GAAS,CAAC,UAAW,CAACtS,EAAO7S,EAAG6S,EAAO5S,KAGtG,GAAoC,IAAhCilB,EAAqB11C,OAQzB,OAHA01C,EAAqBtxC,KACjBtE,MAAMC,QAAQrC,IAAoC,iBAAhBA,EAAS,GAAkB,CAAC,UAAWA,GAAYA,GAElFg4C,GCxDEE,wBACTC,GAEAljC,QAAQkjC,GAAgB,iBAAkBA,GAAgBA,EAAaC,cAM9DC,4BAA+BC,GAEpB,sBADHA,EAAMpqC,WAAWyqB,KAAK8a,aAAa,IACTyE,wBAAwBI,EAAMpqC,WAAWiqC,cAO3EI,6BACTD,IAEA,MAAMH,EAAeG,EAAMpqC,WAAWiqC,aACtC,GAAID,wBAAwBC,GAAe,CACvC,MAAMC,EAAeD,EAAaC,aAAaI,0BACzCC,EAAYL,EAAaM,aAAaC,WAAa,EACzD,MAAO,CACHC,eAAgBH,EAChBI,WAAYT,EAAapM,MACzB8M,MAAOL,EAAYL,EAAapM,MAExC,GAcS+M,8BAAA,CAAiCN,EAAmBO,IAA0B,GAAGP,KAAaO,IAoB9FC,qBAAwBX,GACZC,6BAA6BD,IAC7BQ,OAAS,EAOrBI,+BAAkCh7C,GAGpC,CAAC,OAAQ,CAAC,KAAM,CAAC,MAAO,uBAFbA,GAAQi7C,WAAA,IAEyC,QAAS,OC1B1EC,OAAUC,GAAsB1vC,KAAK9M,MAAU,IAAJw8C,GAAa,IAMxDC,mBAAA,CACFC,EACAC,EACAC,EACAC,KAEA,MAAMC,OAAuC,IAArBD,EAGxB,MAAO,CACHE,IAAKD,EAAkB,CAAC,EAAGD,GAAoB,CAAC,EAAGN,OAAOG,IAC1DM,KAAMF,EACA,CAACD,EAAkBN,OAAOK,IAC1B,CAACL,OAAOI,GAAaJ,OAAOK,IAClCK,MAAOH,EACD,EAAED,EAAkBN,OAAOK,IAC3B,EAAEL,OAAOI,GAAaJ,OAAOK,MAc9BM,cAAA,CACTC,EACAC,EACAxD,EACAiD,KAGA,MAAMQ,EAxFJ,CAAuBC,IACzB,IAAKA,EACD,MnCuK6B,GmCnKjC,GAA0B,iBAAfA,EACP,OAAOA,EAIX,GAAI/3C,MAAMC,QAAQ83C,GAAa,CAC3B,MAAMC,EAAYD,EAAWlnC,IAAG,GAChC,GAAyB,iBAAdmnC,EACP,OAAOA,CAEf,CAGA,GAA0B,iBAAfD,GAA2B,UAAWA,EAAY,CACzD,MAAM1wC,EAAS0wC,EAA6C1wC,MAC5D,GAAIrH,MAAMC,QAAQoH,IAAUA,EAAMnH,OAAS,EAAG,CAC1C,MAAMmN,EAAWhG,EAAMwJ,IAAG,GAC1B,GAAI7Q,MAAMC,QAAQoN,IAAoC,iBAAhBA,EAAS,GAC3C,OAAOA,EAAS,EAExB,CACJ,CAEA,MnC4IiC,ImClFZ4qC,CAAoBL,GnCkFR,GmC/E3B7C,EAAsC,aAAfV,GAA4C,gBAAfA,EAI1D,GAAIU,QAHyC,IAArBuC,EAIpB,MAAO,CACH,cAAe,CAACA,EAAkBA,IAK1C,MACMY,EL5F2B,IK4FkBJ,EAE7CK,EAAkBjB,mBLrGS,GKkGiBY,EAK9CI,EAH+BnD,EAAuB,GAAKmD,EAK3DZ,GAEEc,EAAuB,CACzB,MACAD,EAAgBX,IAChB,OACAW,EAAgBV,KAChB,QACAU,EAAgBT,OAIpB,GAAkC,IAA9BG,EAAqBnuB,KACrB,MAAO,CACH,8BAA+B0uB,GAKvC,MAAMxC,EAAsE,CAAC,QAE7E,IAAK,MAAOC,EAAQwC,KAAWR,EAAqBt1C,UAAW,CAM3D,MAAM40C,GAJgBpC,EAAuBuD,ILhIhB,IKoIKD,EAAOnD,YAAc4C,EACjDV,EL9HuB,IK8HciB,EAAOlD,WAAa2C,EAMzDS,EAAUrB,mBAAmBC,EAAWC,EAFhBrC,EAAuB,GAAKqC,EAEuBE,GACjF1B,EAAqBtxC,KACjB,CAAC,KAAM,CAAC,MLpJG,UKoJcuxC,GACzB,CAAC,UAAW,CAAC,MAAO0C,EAAQf,IAAK,OAAQe,EAAQd,KAAM,QAASc,EAAQb,QAEhF,CAKA,OAFA9B,EAAqBtxC,KAAK,CAAC,UAAW8zC,IAE/B,CACH,8BAA+BxC,ICpH1B4C,kBAAA,CACTl6B,EACAxiB,EACA2lC,EACAgX,EACAZ,KAEA,MAAMa,EAAc58C,GAAQ8R,MACtB+qC,EAAc78C,GAAQ8P,SAAS61B,GAC/BmX,EAAiBf,GAAwBA,EAAqBnuB,KAAO,EAGrEmvB,EAAa,IAAKv6B,EAAUjH,QAK5ByhC,EACFF,QAA0C,IAAxBF,GAAanV,QAA+C,gBAAvBznC,GAAQu4C,WAE/DyE,WACOD,EAAW,sBACXA,EAAW,sCACXA,EAAW,uBAGtB,MAAMxhC,EAAS,IACRwhC,KACA3Y,sBAAsBwY,WACP,IAAdD,GAA2B,CAAE,aAAcA,MAC5CE,GAAathC,QAMpB,GAAIyhC,EAA0B,CAC1B,MAAMC,EAAuB,IACtB1hC,KACAsgC,cACCtgC,EAAO,aACPwgC,kBAAwB,IAAI91C,IAC5BjG,GAAQu4C,WACRqE,GAAanV,SAQfyV,EACY,UAAdvX,GACAoW,GACAnC,0BAA0B,IAAImC,GAAsB14C,IAAA,EAAM02C,GAAUtS,aAAc,CAACsS,EAAQtS,KAE/F,OAAOyV,EAAa,IAAKD,EAAsB,cAAeC,GAAeD,CACjF,CAEA,OAAO1hC,GAOE4hC,iBAAA,CACT36B,EACAxiB,EACA2lC,EACA5B,KAEA,MAAM8Y,EAAc78C,GAAQ8P,SAAS61B,IAC7B3B,UAAWoZ,EAAenZ,UAAWoZ,GAAkBvZ,yBAAyBC,GAClFuZ,EAAY96B,EAAUtB,OAAS,CAAC,EAKtC,MAAO,IACAo8B,UAC6B,IAA5BA,EAAU,eAA+B,CAAE,aAAcF,WACxB,IAAjCE,EAAU,oBAAoC,CAAE,kBAAmBD,MACpE/Y,qBAAqBtkC,GAAQ8R,UAC7B+qC,GAAa37B,QC7HXq8B,eAAiB,CAC1B,oBACA,qBACA,kBACA,mBACA,wBAOSC,gBACTC,GAC+C5xC,KAAKnN,MAAMmN,KAAKC,UAAU2xC,IAAWC,WAAW,OAAQ7G,QAErG8G,eAAA,CAAkBt6C,EAAkB4f,IACrC5f,EAAIya,WAAWhO,OAAOzO,KAAM2a,GAAUA,EAAM1Y,KAAO2f,IAAyC,CAAC,EAQ5F26B,+BACFC,IAAA,CAEA98C,OAAQ,CAAC,IAAKskC,mBACdnlC,KAAM,SACNghB,MAAO28B,EAAS38B,MAChB3F,OAAQsiC,EAAStiC,OAASlf,KAAKwhD,EAAStiC,OAAQ,mBAAgB,IAWvDuiC,yBAA4Bz6C,IACrC,MAAM06C,EAAaH,+BAA+BD,eAAet6C,EAAK,QAChEo6C,EAAWM,EAAWxiC,SAAS,aACrC,MAAO,IACAwiC,EACHxiC,OAAQ,IACDwiC,EAAWxiC,OACd,aAAc,CAAC,MPjDJ,UOkDX,aAAc,CAAC,MAAOs7B,UAClB4G,GAAY,CAAE,YAAaD,gBAAgBC,OAa9CO,2BAA8B36C,IACvC,MAAM06C,EAAaH,+BAA+BD,eAAet6C,EAAK,gBACtE,MAAO,IACA06C,EACHl9B,QAAS,EACTtF,OAAQ,IACDwiC,EAAWxiC,OACd,sBAAsB,EACtB,yBAAyB,GAE7B2F,MAAO,IACA68B,EAAW78B,MACd,eAAgB,KASf+8B,yBAA4B56C,IACrC,MAAM66C,EAAYP,eAAet6C,EAAK,OAAOkY,QAAU,CAAC,EACxD,MAAO,IACA47B,iBACH57B,OAAQ,IACD47B,iBAAiB57B,OACpB,cAAe,iBACgB,IAA3B2iC,EAAU,cAA8B,CAAE,YAAaA,EAAU,sBACnC,IAA9BA,EAAU,iBAAiC,CAAE,eAAgBA,EAAU,kBAE/Eh9B,MAAO,IACAi2B,iBAAiBj2B,MACpB,iBAAkB,CAAC,EAAG,MCvE5Bi9B,kBAAiE,CACnEj+C,KAAM,SACNqb,OAAQ,CAAEC,WAAY,SAOpB4iC,yBAAwE,CAC1El+C,KAAM,SACNqb,OAAQ,CAAEC,WAAY,SAGpB6iC,sBAAiD,CAAC,IAAK/Y,qBAWhDgZ,kCAAoD,IAAInqC,IAAI,CAAC,WAAY,cAAe,kBAKxFoqC,aAA4D,IAClEpH,iBACHp2C,OAAQs9C,uBAKNG,eAAA,CACF12C,EACA22C,KAAA,IAEG32C,EACH/G,OAAQukC,oBACR/pB,OAAQ,IACDzT,EAAKyT,OACR,sBAAsB,GAE1B2F,MAAO,IACApZ,EAAKoZ,MACR,aAAcu9B,KASTC,qBAAA,CACTD,EAxC0B,YA0C1BD,eACI,IAAKrH,iBAAkB57B,OAAQ,IAAK47B,iBAAiB57B,OAAQ,YAAa8e,yBAC1EokB,GAoCFE,WAAA,CACFn8B,EACAxiB,EACA2lC,EACA5B,EACAgY,KAEA,MAAMa,EAAc58C,GAAQ8R,MACtB+qC,EAAc78C,GAAQ8P,SAAS61B,GAC/BiZ,GAA4D,IAApC5+C,GAAQ6+C,gBAAgBz7B,QAEhDu5B,EAAY/X,eACdgY,GAAalY,MACbka,EFlIK,EACT5+C,EACA4+C,IAEKA,EAIE,CACH,OACA,CAAC,MAAO,sBAER,CACI,SACA,CAAC,MAAO/H,OACR,CAAC,EACD,KACA,CAAC,EACD,CAAC,MAAO,sBACR,CACI,aAAc,IACd,aAAcmE,+BAA+Bh7C,GAAQ6+C,kBAI7D,CAAC,MAAOhI,QApBD,CAAC,MAAOA,OE6HSiI,CAAyB9+C,EAAQ4+C,QAAyB,GAGtF,MAAO,IACAp8B,KACAq6B,EACHthC,OAAQmhC,kBAAkBl6B,EAAWxiB,EAAQ2lC,EAAWgX,EAAWZ,GACnE76B,MAAOi8B,iBAAiB36B,EAAWxiB,EAAQ2lC,EAAW5B,KAwBjDgb,sBAAA,CACT/+C,EACAkG,EACA2iC,EACA9gB,EACA02B,EAjI0B,aAmI1B,MAAM1C,ENtEG,EACT/7C,EACA+nB,KAEA,MAAMi3B,iBAAiC,IAAI/4C,IACrCg5C,EAAcj/C,GAAQy5C,MAAMyF,eAAiB,GAEnD,IAAK,MAAMzF,KAAQwF,EACf,GAAIxF,EAAK/zB,MAAO,CACZ,MAAM62B,EAASjE,mBAAmBmB,EAAK/zB,MAAO1lB,GAAQu4C,YAChD4G,OAA6B,IAAjB1F,EAAKC,cAA0C,IAAjBD,EAAKE,QAErD,QAAe,IAAX4C,IAAyB4C,EACzB,SAEJ,MAAMrxB,EAAQ,CACVsrB,YAAamD,GAAQnD,aAAe,EACpCC,WAAYkD,GAAQlD,YAAc,EAClC5R,OAAQ,CAAE7S,EAAG6kB,EAAKC,SAAW,EAAG7kB,EAAG4kB,EAAKE,SAAW,IAGjDyF,EAAiB7Z,aAAakU,EAAKn2C,GAAIykB,GAK7C,GAJAi3B,EAAWn5C,IAAIu5C,EAAgBtxB,GAI3B2rB,EAAK4F,kBAAmB,CACxB,MAAMC,EAAyB/Z,aAAa,GAAGkU,EAAKn2C,MAAMm2C,EAAK4F,oBAAqBt3B,GACpFi3B,EAAWn5C,IAAIy5C,EAAwBxxB,EAC3C,CACJ,CAGJ,OAAOkxB,GMqCsBO,CAAyBv/C,EAAQ+nB,GAE9D,IAAIy3B,EACAC,EAKAC,EAAqDvB,kBAIrD/tB,EAAuD+tB,kBACvDwB,EAA4DvB,yBAC5DwB,EAA4DzB,kBAC5D0B,EAAiEzB,yBACjE0B,EAAiE3B,kBAErE,GAA2B,aAAvBn+C,GAAQu4C,WACRiH,EAAO,IAAK1B,yBAAyB53C,GAAc2a,QAAS,IAC5D6+B,EAAQ1B,2BAA2B93C,GACnCu5C,EApGF,EACFv5C,EACAu4C,IAC8CD,eAAeV,yBAAyB53C,GAAcu4C,GAiGrFsB,CAA8B75C,EAAau4C,QACnD,GAA2B,gBAAvBz+C,GAAQu4C,WACfiH,EAjGF,CAAgCt5C,IAAA,IAC/B+3C,yBAAyB/3C,GAC5BnF,OAAQs9C,wBA+FG2B,CAA6B95C,GACpCu5C,EA7FF,EACFv5C,EACAu4C,IAC8CD,eAAeP,yBAAyB/3C,GAAcu4C,GA0FrFwB,CAAiC/5C,EAAau4C,QACtD,GAA2B,kBAAvBz+C,GAAQu4C,WAAgC,CAY/CiH,EAAO,IAAKjB,aAAcx9C,OAAQ,CAAC,MAAOs2C,mBAAoBgH,wBAC9DoB,EAAW,IACJf,qBAAqBD,GACxB19C,OAAQ,CAAC,MAAOs2C,mBAAoB/R,sBAExC,MAAM4a,EAAYlC,2BAA2B93C,GAC7Cw5C,EAAQ,IACDQ,EACHn/C,OAAQm/C,EAAUn/C,OACX,CAAC,MAAOs2C,mBAAoB6I,EAAUn/C,QACvCs2C,oBAEV,MAAMM,EAAQ33C,GAAQowB,SAASunB,MAC/BvnB,EP9KK,CACTlwB,KAAM,SACNa,OAAQw2C,8BACRh8B,OAAQ,CAMJ,aAAc28B,iCAAiC,CAAC,MA/CN,0BAoD1C,YAAa,EACb,sBAAsB,EACtB,yBAAyB,IO+JzByH,EPnGK,CACThI,IAAA,CAEAz3C,KAAM,SACNa,OAAQw2C,8BACRr2B,MAAO,IACA02B,iBAAiBD,EAAOA,GAAOE,QAAQsI,QAAA,IAC1C,mBAAoBzI,kBAAkBC,GACtC,0BAA2B,cO2FZyI,CAAiCzI,GAChDiI,EPjFK,CACTjI,IAAA,CAEAz3C,KAAM,SACNa,OAAQw2C,8BACRh8B,OAAQy8B,gBAAgBL,GACxBz2B,MAAO,CACH,aAAcy2B,GAAO7lC,OAAOvE,OAAA,UAC5B,iBAAkBmqC,kBAAkBC,GACpC,wBAAyB,cOwEV0I,CAA+B1I,GAC9CkI,EP9DK,CACTlI,IAAA,CAEAz3C,KAAM,SACNa,OAAQy2C,6BACRt2B,MAAO02B,iBAAiBD,EAAOA,GAAOE,QAAQyI,OAAA,MOyDtBC,CAAsC5I,GAC1DmI,EP/CK,CACTnI,IAAA,CAEAz3C,KAAM,SACNa,OAAQy2C,6BACRj8B,OAAQy8B,gBAAgBL,GACxBz2B,MAAO,CACH,aAAcy2B,GAAO7lC,OAAOvE,OAAA,aOwCRizC,CAAoC7I,EAC5D,MAEI6H,EAAOjB,aACPkB,EAAWf,qBAAqBD,GAWpC,MAAMgC,cAAEA,EAAAC,WAAeA,GA7FrB,CACF1gD,IAEA,MAAM2gD,EAAiB/c,oBAAoB5jC,GAAQ6pC,kBAAmB,cAAU,GAChF,MAAO,CACH4W,mBACiE,IAA7Djd,oBAAoBxjC,GAAQ6pC,kBAAmB,UACzCvP,iBAAiBE,iBACjB,OACVkmB,WAAYC,EAAiB,CAAExlC,SAAUwlC,GAAmB,CAAC,IAoF3BC,CAAkB5gD,GAExD,MAAO,CACH0/C,MAAO,IACAf,WAAWe,EAAO1/C,EAAQ,QAAS6oC,EAAqBkT,GAC3D5gC,SAAUslC,GAEdjB,KAAM,IACCb,WAAWa,EAAMx/C,EAAQ,OAAQ6oC,EAAqBkT,GACzD5gC,SAAU,YAEdskC,SAAU,IACHd,WAAWc,EAAUz/C,EAAQ,WAAY6oC,EAAqBkT,MAC9D2E,GAEPtwB,QAAS,IAAKyV,WAAWzV,EAASpwB,GAAQ8P,QAAQsgB,SAAUjV,SAAU,gBACtEwkC,aAAc,IAAK9Z,WAAW8Z,EAAc3/C,GAAQ8P,QAAQ6vC,cAAexkC,SAAU,gBACrFykC,aAAc,IAAK/Z,WAAW+Z,EAAc5/C,GAAQ8P,QAAQ8vC,cAAezkC,SAAU,qBACrF0kC,kBAAmB,IACZha,WAAWga,EAAmB7/C,GAAQ8P,QAAQ+vC,mBACjD1kC,SAAU,qBAEd2kC,kBAAmB,IAAKja,WAAWia,EAAmB9/C,GAAQ8P,QAAQgwC,sBAAuBY,MAC1F1gD,GAAQ8P,QAAQ+wC,aC3QdC,iCAAA,CAAoC/c,EAAsBx2B,KACnE,MAAMwzC,EAAqB,SAAdhd,EACPid,EAAiBld,yBAAyBC,GAAWE,UAC3D,OAAI12B,EACO,CAAE0zC,UAAW1zC,EAAO2zC,WAAYhd,0BAA0B32B,EAAOw2B,GAAYid,kBAEjF,CAAEC,UAAWF,EAAO,UDsCD,UCtC6BG,WAAYH,EAAO,UAAY,UAAWC,mBAexFG,eAAA,CACT5zC,EACA6zC,EACA5c,KAAA,CAEAtkC,KAAM,OACNqb,OAAQ,CAAE,WAAY,SACtB2F,MAAO,CACH,aAAc3T,EACd,aAAc,IACd,iBAAkB6zC,EAClB,eAAgB5c,KAMlB7pC,oBAAqBsmD,IACvB,MAAMn5C,EAAOq5C,eAAeF,EAAW,CAAC,EAAG,GAAI,IAC/C,MAAO,IAAKn5C,EAAMyT,OAAQ,CAAE,YAAa,WAAYzT,EAAKyT,UAMxD+lC,mBAAA,CACFJ,EACAF,KAAA,CAEA9gD,KAAM,SACNqb,OAAQ,CACJ,mBAAoB,cACpB,aAAc,CAAC,MAAO,SACtB,YAAa,CvCnBW,gBuCoBxB,YAAa,GACb,cAAe,CAAC,GAAG,GACnB,sBAAsB,EACtB,yBAAyB,EACzB,qBAAqB,GAEzB2F,MAAO,CACH,aAAcggC,EACd,kBAAmBF,EACnB,kBAAmB,OC7DrBO,iBAA4C,CAAC,KAAM,CAAC,MAAO,QAAS,QAUpEC,kBAAA,CAAqBj0C,EAAeg6B,KAAA,IACnC4Z,eAAe5zC,EAAO,CAAC,EAAG,GAAI,IACjCsT,QAAS0mB,EACTxmC,OAAQ,CAAC,KAAM,CAAC,iBAAkB,gBAIhC0gD,mBAAA,CACFl0C,EACAuqC,EACAvQ,KAAA,CAEArnC,KAAM,SACN2gB,QAAS0mB,EACTxmC,OAAQ,CAAC,KAAM,CAAC,iBAAkB,SAClCmgB,MAAO,CACH,eAAgB3T,EAChB,gBAAiB,CAAC,IAAK,CAAC,OAAQg0C,iBAAkB,EAAG,KAAM,CAAC,OAAQjc,oBAAqB,IAAK,IAC9F,sBAAuB,IACvB,sBAAuBwS,KAYlB4J,0BAAA,CACT1hD,EACA+jC,EAAuB,QACvB4d,EAAwB,EACxBC,KAEA,MAAMr0C,MAAEA,EAAAuqC,YAAOA,GApDb,EAAoC/T,EAAsBx2B,KAC5D,MAAM0zC,UAAEA,EAAAC,WAAWA,EAAAF,eAAYA,GAAmBF,iCAAiC/c,EAAWx2B,GAC9F,MAAO,CAAEA,MAAqB,SAAdw2B,EAAuBkd,EAAYC,EAAYpJ,YAAakJ,IAkD7Ca,CAAiC9d,EAAW6d,GACrEra,EAAU97B,KAAKvJ,IAAIlC,GAAQunC,SAAA,GAA0Coa,GAC3E,MAAO,CACHjyC,KAAMm2B,WAAW2b,kBAAkBj0C,EAAOg6B,GAAUvnC,GAAQ8P,QAAQJ,MACpEglB,MAAOmR,WAAW4b,mBAAmBl0C,EAAOuqC,EAAavQ,GAAUvnC,GAAQ8P,QAAQ4kB,SCrB9EotB,kBAAoB,CAAC,MAAO,cAAe,WAAY,iBC3B9DC,eAAA,CAAkBC,EAAiBC,IACrC,GAAGD,iBAAuBC,IAExBC,WAAA,CAAcC,EAAkClyB,IAClC,QAAZkyB,IAEY,aAAZA,OAA8C,IAAflyB,EAE5BoB,iBAAiBpB,ICGtBmyB,mBAAgC,CAAEngD,IAAK,EAAGC,IAAK,GAAIwB,KAAM,EAAGC,OAAQ,YAEpE0+C,sBAAwB,CAC1B9J,WAAY,CACRz1C,KAAM,OACNC,YAAa,kGACbN,QAASq/C,kBACTt+C,QAAS,OAEb+J,MAAO,CACHzK,KAAM,QACNC,YACI,wKAERqgB,QAAS,CACLtgB,KAAM,SACNC,YACI,sGACJS,SAAS,GAEb,+BAAgC,CAC5BV,KAAM,QACNC,YAAa,wDAEjB,kCAAmC,CAC/BD,KAAM,QACNC,YAAa,sCAEjB,oCAAqC,CACjCD,KAAM,SACNC,YAAa,sCACblB,MAAOgC,eAEX,aAAc,CACVf,KAAM,SACNC,YACI,wGACJlB,MAAOiC,kBAEX,cAAe,CACXhB,KAAM,QACNC,YACI,wGAER,kBAAmB,CACfD,KAAM,QACNC,YAAa,oFAEjB,kBAAmB,CACfD,KAAM,SACNC,YAAa,yEACblB,MAAOkC,wBAEX,gBAAiB,CACbjB,KAAM,SACNC,YAAa,iBACblB,MAAOgC,eAEX,eAAgB,CACZf,KAAM,SACNC,YAAa,uFACblB,MAAO,CAAEI,IAAK,EAAGC,IAAK,EAAGwB,KAAM,GAAKC,OAAQ,aAEhD,aAAc,CACVb,KAAM,QACNC,YACI,4HACJN,QAAS03B,UAEb,sBAAuB,CACnBr3B,KAAM,QACNC,YAAa,wEACbS,QZI2B,WYF/B,4BAA6B,CACzBV,KAAM,QACNC,YAAa,8CACbS,QZAkC,WYEtC,4BAA6B,CACzBV,KAAM,SACNC,YAAa,wDACblB,MAnF8B,CAAEI,IAAK,EAAGC,IAAK,EAAGwB,KAAM,GAAKC,OAAQ,YAoFnEH,QZLkC,KYOtC,4BAA6B,CACzBV,KAAM,QACNC,YAAa,6CACbS,QZTgC,WYWpC,2BAA4B,CACxBV,KAAM,SACNC,YAAa,sDACblB,MAAOiC,iBACPN,QAAA,IAEJ,8BAA+B,CAC3BV,KAAM,SACNC,YAAa,wEACblB,MAAOugD,mBACP5+C,QAAA,IAEJ,6BAA8B,CAC1BV,KAAM,SACNC,YAAa,+EACblB,MAAOugD,mBACP5+C,QAAA,IAEJ,uBAAwB,CACpBV,KAAM,SACNC,YACI,2GACJlB,MAAO,CAAEI,KAAK,GAAKC,IAAK,GAAIwB,KAAM,EAAGC,OAAQ,QAC7CH,QAASi0C,6BAEb,+BAAgC,CAC5B30C,KAAM,SACNC,YAAa,8FACblB,MAAO,CAAEI,IAAK,GAAIC,IAAK,IAAKwB,KAAM,GAAIC,OAAQ,YAC9CH,QAAA,IAEJ,gCAAiC,CAC7BV,KAAM,SACNC,YAAa,qEACblB,MAAO,CAAEI,IAAK,EAAGC,IAAA,GAAuBwB,KAAM,EAAGC,OAAQ,QACzDH,QAAA,IAEJ,kCAAmC,CAC/BV,KAAM,SACNC,YAAa,uDACblB,MAAO,CAAEI,IAAK,EAAGC,IAAK,GAAIwB,KAAM,EAAGC,OAAQ,aAE/C,yBAA0B,CACtBb,KAAM,SACNC,YACI,wGACJS,SAAS,GAEb,2BAA4B,CACxBV,KAAM,SACNC,YAAa,gFACblB,MAAO,CAAEI,IAAK,EAAGC,IAAK,EAAGwB,KAAM,IAAMC,OAAQ,QAC7CH,QT3HyC,IS6H7C,sBAAuB,CACnBV,KAAM,OACNC,YAAa,2FACbN,uBAASD,SAAT,CAA4C,CAAC,MAAO,WAAY,YAChEgB,QDjK8D,YCmKlE,sBAAuB,CACnBV,KAAM,SACNC,YACI,wGACJlB,MAAOmC,WACPR,QAAA,IAEJ,oBAAqB,CACjBV,KAAM,QACNC,YACI,uIAER,0BAA2B,CACvBD,KAAM,QACNC,YACI,0HAER,8BAA+B,CAC3BD,KAAM,QACNC,YAAa,gGAmBRu/C,mCAA6Dr/C,QAAQo/C,uBAOrEE,6BAAyDD,oBAAoBj/C,IAAA,EAAOC,QAASA,GC/L7Fk/C,SAAA,CACTn/C,EACAoW,EACAgpC,EACAC,EACAC,EACAC,KAEA,MAAM/mC,EA9BJ,EAAgB4mC,EAAgCC,IAClDD,GAAK/8B,OAAOA,MACN,CAAEA,MAAO+8B,EAAI/8B,MAAMA,MAAOZ,WAAY29B,EAAI/8B,MAAMZ,YAAA,GAChD,CAAEha,MAAO,IAAK43C,KAAcD,GAAK33C,QA2B1B+3C,CAAaJ,EAAKC,GAC/B,GAAIE,GAAWv/C,EAAIkhB,SAAS9K,IAzB1B,EAAeoC,EAAkB+mC,IACnC,UAAW/mC,EACL,UAAW+mC,GAAW/mC,EAAK6J,QAAUk9B,EAAQl9B,OAAS7J,EAAKiJ,aAAe89B,EAAQ99B,WAClF,UAAW89B,GAAW7mD,QAAQ8f,EAAK/Q,MAAO83C,EAAQ93C,OAsBhBg4C,CAAYjnC,EAAM+mC,GAAU,OAAOA,EAE3E,MAAMl9B,EAAQ,UAAW7J,EAAOA,EAAK6J,MAAQi9B,EAAQ9mC,EAAK/Q,OACpDga,EAAa,UAAWjJ,EAAOA,EAAKiJ,WAAA,EAE1C,OADAZ,iBAAiB,gBAAiBzK,EAASiM,EAAOriB,EAAK,CAAEyhB,eAClDjJ,GAQEknC,iBAAA,CACTxJ,EACAyJ,EAA+CvJ,GAASA,EAAKn2C,MAE7D,MAAM2+B,EAAqC,CAAC,EAC5C,IAAK,MAAMwX,KAAQF,GAAS,GACpBE,EAAK/zB,QACLuc,EAAO+gB,EAAUvJ,IAAS,CAAE/zB,MAAO+zB,EAAK/zB,MAAOZ,WAAY20B,EAAK30B,YAAA,IAExE,OAAOmd,GAWEghB,gBAAA,CAAmB5/C,EAAUk2C,KACtC,IAAK,MAAO9/B,GAASiM,MAAEA,EAAAZ,WAAOA,MAAiB3hB,OAAOsD,QAAQs8C,iBAAiBxJ,IAC3Er1B,iBAAiB,mBAAoBzK,EAASiM,EAAOriB,EAAK,CAAEyhB,gBCrEvDo+B,qBAAuB,CAChCC,MAAO,iCACPC,SAAU,oCACVC,MAAO,iCACPC,WAAY,mCASHC,oBAAA,CACTx9C,EACAy9C,EACArjD,KAEA4F,EAAU09C,qBAAsBC,IACxB39C,EAAUuX,UAAYkmC,EAAQrxC,KAAM7O,GAAOogD,EAAQ7nD,IAAIyH,KAAMnD,OCpB5DwjD,WAAclhD,IAEvB,GAAKiX,sBAGL,OAAOQ,SAAS0C,OAAOna,KCUrBmhD,gBAAA,CAAmBC,EAA0BC,IACvB,iBAAbD,EAA8BA,EAElCC,EAAYz8B,SAAShmB,KAAM+4C,GAAUA,EAAM92C,KAAOugD,GCjBvDE,kBAAqBtK,KACrBA,EAAK/zB,QAAU+zB,EAAK4F,kBAMb2E,mBAAA,CACT9E,EAAoD,MAEpD,MAAM3F,iBAAQ,IAAItzC,IAClB,IAAK,MAAMwzC,KAAQyF,EAAe,CAC9B,MAAM+E,EAAczO,qBAAqBiE,EAAKn2C,IAC1C2gD,IAAgB1K,EAAM19C,IAAIooD,IAAgBF,kBAAkBtK,IAAOF,EAAM1zC,IAAIo+C,EAAaxK,EAClG,CACA,OAAOF,GChBE2K,cAAA,CACTxtC,EACAgQ,EACAy9B,KAEA,GAAoB,YAAhBz9B,GAAS7X,GAAkB,OAAO6X,EAAQ09B,GAAG1tC,GAEjD,GAAoB,gBAAhBgQ,GAAS7X,GAAsB,OAEnC,MAAMoJ,EAAWyO,EAAQ09B,GAAG1tC,GAC5B,OAAOuB,GAAYksC,EAAgBlsC,ICbjCosC,yCAAyC,IAAIlwC,IAAI,CACnD,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAC5G,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAC5G,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAAS,QAC5G,QAAS,QAAS,UAKhBmwC,oBAA2C,IAAIr+C,IAE7C,CACI,CAAC,4BAA6B,iBAC9B,CAAC,mBAAoB,mBACrB,CAAC,UAAW,qBACZ,CAAC,4BAA6B,kBAC9B,CAAC,gBAAiB,aAClB,CAAC,uBAAwB,8BAE/B5C,IAAA,EAAMkhD,EAASC,KAAgB,CAACxmD,kBAAkBumD,GAAUvmD,kBAAkBwmD,MAMvEC,aAAgBC,IACzB,IAAKA,EACD,OAKJ,MAAMC,EAAkBN,0BAA0BxoD,IAAI6oD,GAChDA,EACA3iD,OAAO2iD,EAAW/5C,WAAWi6C,UAAU,EAAG,IAChD,OAAOprC,mBAAmBif,OAAO6rB,oBAAoB3oD,IAAIgpD,IAAoBA,KCCpEE,gBAAmBzK,GAC5BA,EAAMpqC,WAAWyqB,KAAKn5B,MAAQ84C,EAAMpqC,WAAWi4B,QAAQC,gBAKrD4c,UAAA,CAAaC,EAA0BxM,EAA8ByM,KAIvE,GAAmB,QAAfzM,GAAuC,kBAAfA,EAExB,OADgBkM,aAAazmD,kBAAkB+mD,KAC7BC,EACf,CACH,MAAMvrC,EAAU+7B,qBAAqBuP,GACrC,OAAOtrC,EAAU,OAAOA,IAAYurC,CACxC,GA+BSC,kBAAA,CAAqB7K,EAAcryB,EAAuB/nB,EAA6B,CAAC,KACjG,MAAMu4C,EAAav4C,EAAOu4C,YAAc,MAClCyM,EA1BJ,EAA6BhlD,EAA4B+nB,IAChC/nB,EAAOy5C,MAAMj2C,SACkB,cAAhCxD,EAAOu4C,YAAc,OAGxChT,arBtC0B,gBqBsCUxd,GrB5BD,oBqBiDfm9B,CAA0BllD,EAAQ+nB,GAEvDo9B,EAAWjB,cAAc9J,EAAOp6C,EAAOy5C,MAAM/yB,QAAUzO,GACzD6sC,UAAU7sC,EAAUsgC,EAAYyM,IAEpC,GAAIG,EAAU,OAAOA,EAGrB,MAAMJ,EAAc3K,EAAMpqC,WAAWyqB,KAAK8a,aAAa,GAGjD6P,EjBNG,EACThL,EACA2K,EACAh9B,EACA/nB,EACAu4C,KAEA,IAAKv4C,EAAO6+C,gBAAgBz7B,UAAY+2B,4BAA4BC,GAChE,OAKJ,MAAMiL,EAFQtK,qBAAqBX,KACjBp6C,EAAO6+C,eAAe5D,WAAA,IACsB,YAAc,WACtE6B,EAAiB98C,EAAOy5C,MAAMyF,eAAiBl/C,EAAOy5C,KAAKyF,cAAc96C,OAAS,EAElFkhD,EAA6BtlD,EAAOy5C,MAAMyF,eAAe79C,KAC1DkkD,GAAeA,EAAWjiD,KAAOyhD,GAAeQ,EAAWlG,oBAAsBgG,GAItF,OAAIC,EACO/f,aAAa,GAAG+f,EAA2BhiD,MAAM+hD,IAAiBt9B,GAIxE+0B,GAAiC,QAAfvE,OAAvB,EACW/+B,mBAAmB,QAAQ6rC,MiBrBTG,CAAwBpL,EAAO2K,EAAah9B,EAAe/nB,EAAQu4C,GAChG,GAAI6M,EACA,OAAOA,EAIX,MAAMK,EAlCJ,EACFV,EACA7F,EAAkD,MAElD,MAAM+E,EAAcc,GAAevP,qBAAqBuP,GACxD,OACI7F,EAAc79C,KAAMkkD,GAAeA,EAAWjiD,KAAOyhD,KACpDd,EAAcD,mBAAmB9E,GAAevjD,IAAIsoD,QAAe,IA2B7CyB,CAAiBX,EAAa/kD,EAAOy5C,MAAMyF,eACtE,OAAIuG,EACOlgB,aAAakgB,EAAmBniD,GAAIykB,GAIxC+8B,UAAUC,EAAaxM,EAAYyM,IASxCW,wBAAA,CAA2BvL,EAAcp6C,KAC3C,MAAM0mB,EAAU1mB,GAAQy5C,MAAM/yB,QAE9B,OADuC,gBAAhBA,GAAS7X,GAAuB6X,EAAQ09B,GAAGhK,QAAS,IAClDA,EAAMpqC,WAAWyqB,KAAK8a,aAAa,IAOnDqQ,4BAAA,CAA+BxL,EAAcp6C,KACtD,MAAMiY,EAAW0tC,wBAAwBvL,EAAOp6C,GAEhD,OAAOiY,GAAYu9B,qBAAqBv9B,IAQ/B4tC,oBAAA,CAAuBzL,EAAcp6C,KAC9C,MAAMiY,EAAW0tC,wBAAwBvL,EAAOp6C,GAChD,OAAOiY,GtBogBE,CAAqBA,IAC9B,MAAMq9B,EAAkBE,qBAAqBv9B,GAC7C,OAAOq9B,EAAkBI,sBAAsBJ,QAAmB,GsBtgB/CwQ,CAAkB7tC,IAkB5B8tC,SAAYC,GACjB9hD,MAAMC,QAAQ6hD,GACP,CAAE9lD,KAAM,oBAAqBmnB,SAAU2+B,GAE3B,YAAhBA,EAAO9lD,KAAqB,CAAEA,KAAM,oBAAqBmnB,SAAU,CAAC2+B,IAAYA,EAQrFC,yBAAA,CACFC,EACAC,EACAH,KAEA,IAAsC,IAAlCG,GAAsB/iC,QACtB,OAAO8iC,EAIX,IAAIE,GAAgB,EAChBC,GAAgC,EAEpC,IAAK,MAAMjM,KAAS4L,EAAO3+B,SAEvB,GAD8D,sBAA1C+yB,EAAMpqC,WAAWyqB,KAAK8a,aAAa,KAEnD6Q,GAAgB,EACZjM,4BAA4BC,IAAQ,CACpCiM,GAAgC,EAChC,KACJ,CAWR,OAPID,IAAkBC,GAClBn7C,QAAQC,KACJ,uHAKD,IACA+6C,EACHI,mBAAqBlM,GACjBD,4BAA4BC,GjBpJ3B,EAAyBA,EAAcp6C,KAChD,MAAMk6C,EAAeG,6BAA6BD,GAClD,OAAKF,GAIYl6C,GAAQumD,YAAc1L,+BACvBX,EAAaQ,eAAgBR,EAAaS,YAJ/C,IiBiJkC6L,CAAsBpM,EAAO+L,GAAwB,GAC9FM,oBAAsBrM,GAAkBD,4BAA4BC,GAASW,qBAAqBX,GAAS,IAW7GsM,yBAAA,CAA4BtM,EAAcp6C,KAC5C,MAAMiY,EAAW2tC,4BAA4BxL,EAAOp6C,GACpD,OAAOiY,EAAW,OAAOA,IrBtLiB,qBqB8LjC0uC,yBAAA,CACT7C,EACA/7B,EACA/nB,EAA6B,CAAC,KAE9B,MAAMgmD,EAASD,SAASjC,GAGlB8C,GACiC,IAAnC5mD,EAAO6+C,gBAAgBz7B,QACjB6iC,yBAAyBjmD,EAAOkmD,kBAAmBlmD,EAAO6+C,eAAgBmH,GAC1EhmD,EAAOkmD,kBAEjB,MAAO,IACAF,EACH3+B,SAAU2+B,EAAO3+B,SAAShkB,IAAK+2C,IAC3B,MAAM1V,EAAQD,aAAa2V,EAAOp6C,EAAO8R,OAAO4yB,MAAOmgB,iBAEjDqB,EAAoBU,EACpBzjD,OAAOqP,YACHrP,OAAOsD,QAAQmgD,GAAyBvjD,IAAA,EAAMwjD,EAAMtkD,KAAW,CAC3DskD,EACiB,mBAAVtkD,EAAuBA,EAAM63C,GAAS73C,KAGrD,CAAC,EAEDe,EAAK82C,EAAM92C,IAAM9F,aAEvB,MAAO,IACA48C,EACH92C,KACAqT,SAAU,IAAKyjC,EAAMzjC,SAAUm0B,KAAMsP,EAAMtP,MAC3C96B,WAAY,IACLoqC,EAAMpqC,WACT1M,KACAohC,QACAoiB,OAAQ7B,kBAAkB7K,EAAOryB,EAAe/nB,OAMpB,aAAvBA,GAAQu4C,YAAoD,kBAAvBv4C,GAAQu4C,aAAmC,CACjFtgC,SAAU2tC,4BAA4BxL,EAAOp6C,GAC7Ci8B,MAAO4pB,oBAAoBzL,EAAOp6C,OAQX,kBAAvBA,GAAQu4C,YAAkC,CAAAwO,cACLL,yBAAyBtM,EAAOp6C,OAEtEkmD,QA4CVc,wBAAA,CACTlD,EACA9jD,EAAgC,CAAC,IACL2mD,yBAAyB7C,EAAa,EAAe9jD,GC7P/EinD,aAAiD,CAAC,SAAU,qBAqIrDC,aAAb,MAAaA,UAAqBx7B,2BAwC9B,mBAAa9lB,CACTG,EACA/F,GAIA,aAFMod,oBAAoBrX,GAEnB,IAAImhD,EAAanhD,EAAuB/F,EACnD,CAEA,WAAAN,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,GA3CiBH,KAAAsnD,uBAAA,EAoBsDtnD,KAAAyI,sBAAA,GAyBlFi7B,oBAAoBlgC,EAAA,UACW,IAAvBxD,KAAKG,QAAQuN,OAAqB1N,KAAKunD,oBAAoBvnD,KAAKG,UAExEujD,oBAAoBlgD,EAAKk6C,eAAA,KACjB19C,KAAKG,QAAQu4C,YAAc+F,mBAAmBziD,IAAIgE,KAAKG,OAAOu4C,aAC9D14C,KAAKwnD,mBAAmBxnD,KAAKG,SAGzC,CAGA,OAAA6sC,CAAgB7sC,GACZ,OAAOA,GAAQuN,OAASy1B,eAAenjC,KAAKkG,UAChD,CAKA,sBAAA4iB,CAAiC3oB,EAA6B0oB,GAGrDA,IACD7oB,KAAKuvB,SAAW,UAAUvvB,KAAKkoB,gBAC/BloB,KAAKynD,oBAAsB,UAAUznD,KAAKkoB,4BAC1CloB,KAAK0nD,mBAAqB,sBAAsB1nD,KAAKkoB,gBACrDloB,KAAK2nD,mBAAqB,uBAAuB3nD,KAAKkoB,gBACtDloB,KAAK+lC,cAAgB/lC,KAAKuvB,SAC1BvvB,KAAKmlD,mBAAqBzf,atBtQD,gBsBsQqC1lC,KAAKkoB,eACnEloB,KAAK4nD,iBAAmB,GACxB5nD,KAAK6nD,mBAAqB,CAAC,GAI/B7nD,KAAKiiB,WAAajiB,KAAK8nD,gBAAgB3nD,GAEvC,MAAMmwB,EAAiBtwB,KAAK+nD,oBAAoB5nD,GAChDH,KAAKgoD,sBAAwB13B,EAW7B,MAAM23B,EAAajoD,KAAKkoD,yBAAyBloD,KAAKiiB,YAQhDkkC,EAAS,IAAI91B,wBACfrwB,KAAKqG,YACLrG,KAAKuvB,SACL04B,EAAWE,gBACX,EACA73B,GAGJ,IAAI83B,EACAH,EAAWI,iBAAiB9jD,OAAS,IACrC6jD,EAAoB,IAAI/3B,wBACpBrwB,KAAKqG,YACLrG,KAAKynD,oBACLQ,EAAWI,kBACX,IAKRroD,KAAKsoD,qBAAuBtoD,KAAKuoD,0BAA0BpoD,GAC3D,MAAMqoD,EAAcxoD,KAAKyoD,iBACrBzoD,KAAK0nD,mBACL1nD,KAAKsoD,sBAOTlmC,UAAU,IAAI+jC,EAAOllD,eAAiBmnD,GAAmBnnD,aAAe,IAAMjB,KAAKqG,aAEnFrG,KAAK0oD,qBAAuB1oD,KAAK6hD,0BAA0B1hD,GAC3D,MAAMwoD,EAAc3oD,KAAKyoD,iBACrBzoD,KAAK2nD,mBACL3nD,KAAK0oD,sBAIHE,EAAA,MACE5oD,KAAKG,QAAQwoD,aAAeA,EAAYl4B,cAAcjJ,SAASjjB,SAC/DvE,KAAK6oD,sBAAsB7oD,KAAKG,SASxC,OANAgmD,EAAOp1B,OAAO63B,GACdD,EAAY53B,OAAO63B,GAKZR,EACD,CAAEjC,SAAQiC,oBAAmBI,cAAaG,eAC1C,CAAExC,SAAQqC,cAAaG,cACjC,CAIA,gBAAAF,CACIl5B,EACAtN,GAEA,MAAM3F,EAAS,IAAI+T,wBAA2BrwB,KAAKqG,YAAakpB,EAAUjsB,OAAOH,OAAO8e,IAAa,GAErG,OADAG,UAAU9F,EAAOrb,YAAajB,KAAKqG,aAC5BiW,CACX,CAGA,iBAAAwsC,CACIC,EACAC,EACA1sC,GAGA,OADAiF,sBAAsBje,OAAOH,OAAO4lD,GAAWzlD,OAAOH,OAAO6lD,GAAW1sC,EAAQtc,KAAKqG,aAC9E0iD,CACX,CAOA,wBAAAb,CACIjmC,GAKA,IAAKjiB,KAAKgoD,sBACN,MAAO,CAAEG,eAAgB7kD,OAAOH,OAAO8e,GAAaomC,iBAAkB,IAE1E,MAAMA,EAA0E,GAC1EF,EAAwE,GAC9E,IAAK,MAAO1mD,EAAMuZ,KAAS1X,OAAOsD,QAAQqb,GACzB,UAATxgB,EAAkB4mD,EAAiB1/C,KAAKqS,GACvCmtC,EAAex/C,KAAKqS,GAE7B,MAAO,CAAEmtC,iBAAgBE,mBAC7B,CAEA,eAAAP,CACI3nD,GAEA,MAAM8oD,EAAqB/J,sBACvB/+C,EACAH,KAAKkG,UAAUG,YACfrG,KAAKkG,UAAU8iC,oBACfhpC,KAAKkoB,cACLloB,KAAKgtC,QAAQ7sC,IAEX+oD,EAAgB,IAAI50C,IAAIhR,OAAOC,KAAK0lD,IAI1C,OAAO3lD,OAAOqP,YACVrP,OAAOsD,QAAQqiD,GAAoBzlD,IAAA,EAAMgR,EAAKwG,MAC1C,MAAMM,EAAYN,EAA+BM,SACjD,MAAO,CACH9G,EACA,IACOwG,EACHvX,GAAI,GAAGzD,KAAK+lC,iBAAiBvxB,OACzB8G,GAAY4tC,EAAcltD,IAAIsf,GAC5B,CAAEA,SAAU,GAAGtb,KAAK+lC,iBAAiBzqB,KACrC,CAAC,MAK3B,CAKA,mBAAAysC,CAA4B5nD,GACxB,GAA2B,kBAAvBA,GAAQu4C,WAAgC,OAE5C,MAAMp8B,EAASnc,EAAOowB,SAASjU,OAC/B,MAAO,CACH6sC,cAAA,GACAC,eAAA,MACG9sC,EACHiU,SAAS,EAIT84B,kBAAmB,IAAK/sC,GAAQ+sC,qBAAsBjR,oCAE9D,CAEA,yBAAAmQ,CAAkCpoD,GAC9B,MAAMmpD,EbhYD,EACTnpD,EACAuN,EACAw2B,EAAuB,WAEvB,MAAMkd,UAAEA,EAAAC,WAAWA,EAAAF,eAAYA,GAAmBF,iCAC9C/c,EACA/jC,GAAQuN,OAASA,GAErB,MAAO,CACHmC,KAAMm2B,WAAWlrC,oBAAkBsmD,GAAYjhD,GAAQ8P,QAAQJ,MAC/DoC,MAAO+zB,WACHyb,mBAAmBthD,GAAQ8R,OAAOvE,OAAS2zC,EAAYlhD,GAAQ8R,OAAOmyB,WAAa+c,GACnFhhD,GAAQ8P,QAAQgC,SamXNs2C,CACVpoD,GAAQqoD,YACRxoD,KAAKgtC,QAAQ7sC,GACbH,KAAKkG,UAAU8iC,qBAEbugB,EAAS,GAAGvpD,KAAK+lC,gCACjByjB,EAAU,GAAGxpD,KAAK+lC,iCAKlB30B,EAAS2yB,oBAAoB5jC,GAAQ6pC,kBAAmB,cAAevP,iBAAiBI,aAC9F,MAAO,CACHhrB,KAAM,IAAKy5C,EAAMz5C,KAAMpM,GAAI8lD,EAAQjuC,SAAUlK,GAC7Ca,MAAO,IAAKq3C,EAAMr3C,MAAOxO,GAAI+lD,EAASluC,SAAUlK,GAExD,CAEA,yBAAAywC,CAAkC1hD,GAE9B,MAAMmwB,EAAiBtwB,KAAK+nD,oBAAoB5nD,GAC1C2hD,EAAwBxxB,GAAkBA,EAAe84B,gBAAA,IAAsC,EAAI,EACnGE,EAAQzH,0BACV1hD,GAAQwoD,YACR3oD,KAAKkG,UAAU8iC,oBACf8Y,EACA9hD,KAAKgtC,QAAQ7sC,IAEjB,MAAO,CAEH0P,KAAM,IACCy5C,EAAMz5C,KACTpM,GAAI,GAAGzD,KAAK+lC,iCACZzqB,SAAUtb,KAAKiiB,WAAW09B,KAAKl8C,IAInCoxB,MAAO,IACAy0B,EAAMz0B,MACTpxB,GAAI,GAAGzD,KAAK+lC,4BACZzqB,SAAUtb,KAAKiiB,WAAWsO,QAAQ9sB,IAG9C,CAKA,YAAA+lB,CAAuBrpB,GAEnB,OADAH,KAAKunD,oBAAoBpnD,GAClBA,CACX,CAKA,gBAAA2pB,GACI9pB,KAAK4nD,iBAAmB,EAC5B,CAKA,wBAAA/9B,GACI,MAAM4/B,EAAwBzpD,KAAK0mB,kBAAkBy/B,OAAO11B,cACtDi5B,EAAsB1pD,KAAK4nD,iBACjC5nD,KAAKsoB,sBAAsBtoB,KAAKG,QAAQ,GACxCH,KAAKG,QAAUH,KAAKwpB,aAAaxpB,KAAKG,QACtCH,KAAKgD,KAAKymD,GACNC,EAAoBnlD,QACpBvE,KAAK2pD,gBAAgBD,EAE7B,CAaA,WAAAnhC,CAA2DpoB,GACvD+G,MAAMqhB,YAAwBpoB,EAClC,CAeA,YAAAupB,CAA4DC,GACxDziB,MAAMwiB,aAAyBC,EACnC,CAGA,eAAAsC,CAA0B9rB,GACtBH,KAAK4pD,sBAAsBzpD,GAC3BH,KAAK6pD,kBACDvmD,OAAOH,OAAOnD,KAAKsoD,wBACjBtoD,KAAK0mB,kBAAkB8hC,YAAY/3B,cAAcjJ,SAASjjB,OAC5DpE,GAEJH,KAAK6pD,kBACDvmD,OAAOH,OAAOnD,KAAK0oD,wBACjB1oD,KAAK0mB,kBAAkBiiC,YAAYl4B,cAAcjJ,SAASjjB,OAC5DpE,EAER,CAEA,mBAAAonD,CAA4BpnD,GACxBH,KAAK8pD,YAAY3pD,GAMZjE,QAAQ8D,KAAK+nD,oBAAoB5nD,GAASH,KAAKgoD,wBAKpDhoD,KAAKwnD,mBAAmBrnD,GACxBH,KAAK4pD,sBAAsBzpD,GAC3BH,KAAK+pD,WAAW5pD,GAChBH,KAAKsoD,qBAAuBtoD,KAAK8oD,kBAC7B9oD,KAAKuoD,0BAA0BpoD,GAC/BH,KAAKsoD,qBACLtoD,KAAK0mB,kBAAkB8hC,aAE3BxoD,KAAKgqD,sBAAsB7pD,GAC3BH,KAAK0oD,qBAAuB1oD,KAAK8oD,kBAC7B9oD,KAAK6hD,0BAA0B1hD,GAC/BH,KAAK0oD,qBACL1oD,KAAK0mB,kBAAkBiiC,aAE3B3oD,KAAK6oD,sBAAsB1oD,GACtBH,KAAK8nB,eAAkB5rB,QAAQ8D,KAAKG,QAAQ6pC,kBAAmB7pC,GAAQ6pC,oBACxEhqC,KAAKiqD,iBApBLjqD,KAAKkqD,wBAAwB/pD,EAsBrC,CAIA,aAAA8pD,GACI,MAAMzB,YAAEA,EAAArC,OAAaA,EAAAiC,kBAAQA,EAAAO,YAAmBA,GAAgB3oD,KAAK0mB,kBACrEtE,UACI,CAAComC,EAAarC,EAAQiC,EAAmBO,GAAapsC,QAASD,GAAWA,GAAQrb,aAAe,IACjGjB,KAAKqG,aACL,EAER,CAGA,kBAAAmhD,CAA2BrnD,GACvB,MAAMmiB,EAAgBtiB,KAAK8nD,gBAAgB3nD,GAMrCgqD,EAAWnqD,KAAKgoD,sBAChBhoD,KAAKkoD,yBAAyB5lC,GAC9B,CAAE6lC,eAAgB7kD,OAAOH,OAAOmf,GAAgB+lC,iBAAkB,IAClE+B,EAAWpqD,KAAKgoD,sBAChBhoD,KAAKkoD,yBAAyBloD,KAAKiiB,YACnC,CAAEkmC,eAAgB7kD,OAAOH,OAAOnD,KAAKiiB,YAAaomC,iBAAkB,IAC1E9mC,sBACI4oC,EAAShC,eACTiC,EAASjC,eACTnoD,KAAK0mB,kBAAkBy/B,OACvBnmD,KAAKqG,aAELrG,KAAK0mB,kBAAkB0hC,mBAAqB+B,EAAS9B,iBAAiB9jD,OAAS,GAC/Egd,sBACI4oC,EAAS9B,iBACT+B,EAAS/B,iBACTroD,KAAK0mB,kBAAkB0hC,kBACvBpoD,KAAKqG,aAGbrG,KAAKiiB,WAAaK,CACtB,CAKA,uBAAA4nC,CAAgC/pD,GAC5B,MAAMspD,EAAwBzpD,KAAK0mB,kBAAkBy/B,OAAO11B,cACtDi5B,EAAsB1pD,KAAK4nD,iBAC3BpkD,EAAMxD,KAAKqG,YACjB,IAAK,MAAM2U,IAAQ,IACZ1X,OAAOH,OAAOnD,KAAKiiB,eACnB3e,OAAOH,OAAOnD,KAAKsoD,yBACnBhlD,OAAOH,OAAOnD,KAAK0oD,uBAElBllD,EAAIoE,SAASoT,EAAKvX,KAAKD,EAAIiZ,YAAYzB,EAAKvX,IAIpD,IAAK,MAAM8rB,IAAY,CACnBvvB,KAAKuvB,SACLvvB,KAAKynD,oBACLznD,KAAK0nD,mBACL1nD,KAAK2nD,oBAEDnkD,EAAI0X,UAAUqU,IAAW/rB,EAAIkZ,aAAa6S,GAGlDvvB,KAAKsoB,sBAAsBnoB,GAAQ,GACnCH,KAAK8pD,YAAY3pD,GACjB,MAAMkqC,EAAWyc,yBAAyB2C,EAAuBzpD,KAAKkoB,cAAe/nB,GAOrFH,KAAK0mB,kBAAkBy/B,OAAOnjD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IACpE3wB,KAAK0mB,kBAAkB0hC,mBAAmBplD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IAChF3wB,KAAK4pD,sBAAsBzpD,GACvBupD,EAAoBnlD,SACpBvE,KAAK4nD,iBAAmB,IAAI8B,GAC5B1pD,KAAKgqD,sBAAsB7pD,IAE/BH,KAAK6oD,sBAAsB1oD,EAC/B,CAEA,qBAAA6pD,CAA8B7pD,GAC1B,MAAM8jD,EAAcjkD,KAAK0mB,kBAAkBy/B,OAAO11B,cAC5C4Z,ELpqBD,EACTme,EACAvE,EACA9jD,EAA6B,CAAC,KAE9B,MAAMkqD,EAAUlqD,EAAOqoD,aAAav2C,OAAO4yB,MACrCrd,EAA0D,GAEhE,IAAK,MAAM8iC,KAAc9B,EAAa,CAClC,MAAMz5C,EAAOg1C,gBAAgBuG,EAAWv7C,KAAMk1C,GACxCj1C,EAAK+0C,gBAAgBuG,EAAWt7C,GAAIi1C,GAC1C,IAAKl1C,IAASC,EAAI,SAElB,MAAMvL,EAAK6mD,EAAW7mD,IAAM9F,aACtBsU,EAAQo4C,EAAUA,EAAQC,GAAc,GAE9C9iC,EAAS7e,KAAK,CACVtI,KAAM,UACNoD,KACAqT,SAAU,CACNzW,KAAM,aACNsqC,YAAa,CAAC57B,EAAK+H,SAAS6zB,YAAa37B,EAAG8H,SAAS6zB,cAEzDx6B,WAAY,CACR1M,KACA8mD,OAAmC,iBAApBD,EAAWv7C,KAAoBu7C,EAAWv7C,KAAOA,EAAKtL,GACrE+mD,KAA+B,iBAAlBF,EAAWt7C,GAAkBs7C,EAAWt7C,GAAKA,EAAGvL,GAC7DwO,UAGZ,CAEA,MAAO,CAAE5R,KAAM,oBAAqBmnB,aKooBfijC,CAA6BzqD,KAAK4nD,iBAAkB3D,EAAa9jD,GAClFH,KAAK0mB,kBAAkB8hC,YAAYxlD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IACzE3wB,KAAK6pD,kBAAkBvmD,OAAOH,OAAOnD,KAAKsoD,wBAAyBje,EAAS7iB,SAASjjB,OAAQpE,EACjG,CAEA,qBAAA0oD,CAA8B1oD,GAC1B,IAAIH,KAAKsnD,sBAAT,CAEAtnD,KAAKsnD,uBAAwB,EAC7B,IACI,MAAMqB,EAAc3oD,KAAK0mB,kBAAkBiiC,YACrCte,EV5qBL,EACT8b,EACApqC,EACA5b,KAEA,MAAMqnB,EAAgE,GACtE,IAAKrnB,EAAQ,MAAO,CAAEE,KAAM,oBAAqBmnB,YAEjD,MAAM86B,EAAUniD,EAAOmiD,SAAA,WACjBoI,iBAAsB,IAAItkD,IAChC,IAAK,MAAMyQ,KAAWkF,EAASyL,SACvB3Q,EAAQ1G,WAAWigB,YACnBs6B,EAAoB1kD,IAAI6Q,EAAQ1G,WAAW1M,GAAIoT,EAAQ1G,WAAWigB,YAI1E,IAAK,MAAMmqB,KAAS4L,EAAO3+B,SAAU,CACjC,MAAM26B,EAAU5H,EAAMpqC,WAAW1M,GAC3BklD,EAAcpO,EAAMpqC,WAAWw4C,aAAe,GAC9Cl9B,EAAMk9B,EAAYnlD,IAAA,CAAK6qC,EAAG+T,IAAoBF,eAAeC,EAASC,IAI5E,GAFIC,WAAWC,EAAS/H,EAAMpqC,WAAWigB,aACrC3E,EAAInZ,KAAM7O,GAAO4+C,WAAWC,EAASoI,EAAoB5uD,IAAI2H,KAGjE,IAAK,MAAO2+C,GAAiBuI,SAAEA,KAAaC,MAAiBjC,EAAY/hD,UAAW,CAChF,MAAMnD,EAAKgoB,EAAI22B,GACThyB,EAAas6B,EAAoB5uD,IAAI2H,GACrC0M,EAAmC,IAAKy6C,EAAYnnD,KAAI0+C,UAASC,mBACvE56B,EAAS7e,KAEL,CACItI,KAAM,UACNoD,KACAqT,SAAU,CAAEzW,KAAM,QAASsqC,YAAaggB,GACxCx6C,WAAYigB,EAAa,IAAKjgB,EAAYigB,cAAejgB,GAE7D,CACI9P,KAAM,UACNoD,KACAqT,SAAU,CAAEzW,KAAM,aAAcsqC,YAAa,CAAC4P,EAAMzjC,SAAS6zB,YAAaggB,IAC1Ex6C,cAGZ,CACJ,CAEA,MAAO,CAAE9P,KAAM,oBAAqBmnB,aU6nBXqjC,CACb7qD,KAAK0mB,kBAAkBy/B,OAAO11B,cAC9Bk4B,EAAYl4B,cACZtwB,GAAQwoD,aAEZA,EAAY3lD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IAClD3wB,KAAK6pD,kBAAkBvmD,OAAOH,OAAOnD,KAAK0oD,wBAAyBre,EAAS7iB,SAASjjB,OAAQpE,EACjG,CAAA,QACIH,KAAKsnD,uBAAwB,CACjC,CAdgC,CAepC,CAIA,iBAAAuC,CACI5nC,EACA6oC,EACA3qD,GAEAmjB,sBAAsBrB,EAAY6oC,IAA+B,IAApB3qD,GAAQojB,QAAmBvjB,KAAKqG,YACjF,CAKA,qBAAAujD,CAA8BzpD,GAC1BH,KAAK6pD,kBACDvmD,OAAOH,OAAOnD,KAAKiiB,cACjBjiB,KAAK0mB,kBAAkBy/B,OAAO11B,cAAcjJ,SAASjjB,OACvDpE,EAER,CAIA,WAAA2pD,CAAoB3pD,GAChBH,KAAK6nD,mBAAqBniC,WACtB1lB,KAAKqG,YACLrG,KAAK6nD,mBACL3E,iBAAiB/iD,GAAQy5C,MAAMyF,cAAA,EAAkB57C,KAAI+7C,uBACjD9Z,aAAa8Z,EAAoB,GAAG/7C,KAAM+7C,IAAsB/7C,EAAIzD,KAAKkoB,iBAGjFloB,KAAK+qD,WAAapI,SACd3iD,KAAKqG,YACLrG,KAAKmlD,mBACLhlD,GAAQy5C,MAAMj2C,QACd,CAAEsZ,UAAWjd,KAAKgtC,QAAQ7sC,IAC1B2jD,WACA9jD,KAAK+qD,WAEb,CAEA,UAAAhB,CAAmB5pD,GACf,MAAMkqC,EAAWyc,yBACb9mD,KAAK0mB,kBAAkBy/B,OAAO11B,cAC9BzwB,KAAKkoB,cACL/nB,GAIJH,KAAK0mB,kBAAkBy/B,OAAOnjD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IAGpE3wB,KAAK0mB,kBAAkB0hC,mBAAmBplD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,GACpF,CAsDA,UAAM3tB,CAAKmjD,SACDnmD,KAAKkpB,uBAIXlpB,KAAKgrD,uBAELhrD,KAAK0mB,kBAAkBiiC,YAAYniD,QACnC,MAAM6jC,EAAWyc,yBAAyBX,EAAQnmD,KAAKkoB,cAAeloB,KAAKG,QAG3EH,KAAK0mB,kBAAkBy/B,OAAOnjD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IAGpE3wB,KAAK0mB,kBAAkB0hC,mBAAmBplD,KAAKqnC,EAAU,CAAE1Z,qBAAqB,IAChF3wB,KAAK4pD,sBAAsB5pD,KAAKG,QAChC,IAAK,MAAMG,KAAWN,KAAKyI,sBACvBnI,EAAQ,CAAE6lD,UAElB,CAeA,WAAM3/C,SACIxG,KAAKkpB,uBACXlpB,KAAKgrD,uBACLhrD,KAAK0mB,kBAAkBy/B,OAAO3/C,QAC9BxG,KAAK0mB,kBAAkB0hC,mBAAmB5hD,OAC9C,CAqCA,qBAAMmjD,CAAgBnB,SACZxoD,KAAKkpB,uBACXlpB,KAAK4nD,iBAAmB,IAAIY,GAC5BxoD,KAAKgqD,sBAAsBhqD,KAAKG,QAChC,IAAK,MAAMG,KAAWN,KAAKyI,sBACvBnI,EAAQ,CAAEkoD,eAElB,CAUA,sBAAMyC,SACIjrD,KAAKkpB,uBACXlpB,KAAKgrD,sBACT,CAEA,oBAAAA,GACIhrD,KAAK4nD,iBAAmB,GACxB5nD,KAAK0mB,kBAAkB8hC,aAAahiD,OACxC,CAkBA,QAAAs8B,GACI,MAAO,CACHqjB,OAAQnmD,KAAK0mB,kBAAkBy/B,OAAO11B,cACtC+3B,YAAa,IAAIxoD,KAAK4nD,kBAE9B,CAGA,iBAAA77B,GACI,OAAOq7B,YACX,CAEA,UAAI18B,GACA,OAAO1qB,KAAK8qB,YAIR9qB,KAAK6qB,sBACD,IAAIu8B,aAAc,cAAe,eACjCpnD,KAAKyI,uBAET,CACI09C,OAAQnmD,KAAKmsB,qBAA+Ci7B,cAC5DoB,YAAaxoD,KAAKmsB,qBAAwC,CAAC,gBAC3Dw8B,YAAa3oD,KAAKmsB,qBAA6C,CAAC,eAAgB,CAC5EtF,QAAUhQ,GVz3BjB,EACTA,EACAotC,KAGA,MAAM9zC,EAAa0G,EAAQ1G,WAC3B,MAAO,CACH9P,KAAM,UACNoD,GAAI0M,EAAW1M,GACfqT,SAAU,CAAEzW,KAAM,QAASsqC,aAhBL7zB,EAgBuCD,EAAQC,SAfvD,UAAlBA,EAASzW,KAAmByW,EAAS6zB,YAAc7zB,EAAS6zB,YAAY,KAgBpEx6B,WAAY,IAAKA,EAAYoqC,MAAO5rB,mBAAmBs1B,EAAYz8B,SAAUrX,EAAWgyC,UAAUtrC,UAjBpG,IAAwBC,GUi4BNo0C,CAAyBr0C,EAAS7W,KAAK0mB,kBAAkBy/B,OAAO11B,kBAIpF,GCh7BS06B,kBAAoB,CAC7BC,kBAAmB,CAAC,aAAc,YAAa,WAAY,MAAO,SAAU,YAAa,gBACzFC,eAAgB,CACZ,gBACA,OACA,gBACA,cACA,cACA,cACA,mBACA,oBACA,gBACA,iBACA,8BAEJC,qBAAsB,CAClB,UACA,iBACA,sBACA,wBACA,kBACA,WACA,cAEJC,aAAc,CACV,SACA,4BACA,iBACA,qBACA,WACA,WACA,UACA,wBAEJC,cAAe,CAAC,iBAAkB,yBAClCC,sBAAuB,CACnB,iBACA,QACA,iBACA,kBACA,qBACA,iBACA,gBACA,gBACA,SACA,YACA,uBAEJC,2BAA4B,CAAC,qBAC7BC,mBAAoB,CAAC,gBACrBC,oBAAqB,CAAC,iBAAkB,iBAAkB,kBAC1DC,oBAAqB,CACjB,SACA,UACA,gBACA,YACA,eACA,gBACA,uBACA,uBAEJC,qBAAsB,CAClB,gBACA,cACA,gBACA,cACA,UACA,QACA,WACA,iBACA,gBACA,iBAEJC,gBAAiB,CAAC,SAAU,wBAAyB,iBAAkB,mBACvEC,iBAAkB,CAAC,oBAAqB,aAAc,UAAW,eAAgB,mBCjH/EC,mCAAsC,MACxC,MAAMC,iBAAmBrpD,kBACrB,qBACA,iCACA,IAAKS,OAAOC,KAAK4nD,sBAA8CjtD,eAC/D,iFAEJ,MAAO,CACHqlB,QAAS,CAAEtgB,KAAM,SAAUC,YAAa,gCAAiCS,SAAS,GAClF,0BAA2BuoD,EAAiBlpD,KAC5C,4BAA6BkpD,EAAiB/oD,OAC9CgpD,WAAY,CACRlpD,KAAM,SACNC,YAAa,0CACblB,MAAO4B,aACPD,QAAS,GAEb+jC,QAAS,CACLzkC,KAAM,SACNC,YAAa,8DACblB,MAAO,CAAEI,IAAK,EAAGC,IAAK,GAAIwB,KAAM,EAAGC,OAAQ,SAE/CsoD,UAAW,CACPnpD,KAAM,SACNC,YACI,0GACJlB,MAAO,CAAEI,KAAK,EAAIC,IAAK,EAAGwB,KAAM,EAAGC,OAAQ,QAC3CH,QAAS,GAEb,cAAe,CACXV,KAAM,QACNC,YAAa,sEAEjB,kBAAmB,CAAED,KAAM,QAASC,YAAa,kCACjD,uBAAwB,CACpBD,KAAM,SACNC,YACI,oGAGhB,EAxC4C,GAsD/BmpD,iCAAyDjpD,QAAQ6oD,qBAOjEK,2BAAqDD,kBAAkB7oD,IAAA,EAAOC,QAASA,GCpB9F8oD,YAAc,CAAE7kB,QAAS,UAAW8kB,QAAS,WAU7CC,iCAAgD,IAAIn4C,IAAI,CAAC,YAAa,cAKtEo4C,cAAgB,CAClB,YAAa,GACb,YAAa,EACb,aAAc,EACd1rC,QAAS,EACTD,QAAS,GACT,mBAAoB,WACpB,mCAAoC,IACpC,yBAA0B,GAC1B,yBAA0B,UAC1B,4BAA6B,UAC7B,yBAA0B,WAKxB4rC,iBAAoBC,GAAwE,WAAYA,EAoCjGC,UAAA,CAAazlD,EAAiBzC,IAA4B,GAAGyC,KAAWzC,IAI/EmoD,cAA+D,CAAEC,SAAU,EAAGv0C,MAAO,GAErFw0C,4BAAe,IAAIhmD,QAcZimD,WAAb,MAAaA,EAiBT,WAAAptD,CAAoBqG,GAdkBlG,KAAAmgC,SAAA,CAAC,EAGvBngC,KAAAktD,OAAA,EACKltD,KAAAmtD,0BAAA,IAAI/mD,IAEKpG,KAAAiL,MAAA,CAAEgF,OAAQ,GAAIgM,QAAS,CAAC,GAE7Bjc,KAAAotD,8BAAA,IAAIhnD,IACVpG,KAAAqtD,wBAAA,IAAIjnD,IAEMpG,KAAAstD,kCAAA,IAAIlnD,IAChBpG,KAAAutD,sBAAA,IAAIj5C,IAGjBtU,KAAKkG,UAAYA,EACjBlG,KAAKqoB,eAAiBphB,oBAAoB0c,IAAIzd,GAC9ClG,KAAKwtD,aACLtnD,EAAUI,sBAAsB,CAC5BC,qBAAA,KACIvG,KAAKktD,OAAQ,GAEjB1kC,eAAA,EAAmBC,gBAAiBzoB,KAAK6oB,QAAQJ,GAGjDhiB,SAAU,IAElB,CAGA,UAAO,CAAIP,GACP,OAAOL,MAAMmnD,aAAc9mD,EAAA,IAAiB,IAAI+mD,EAAW/mD,GAC/D,CAMA,QAAAyB,CACIsf,EACA5jB,EACAoqD,GAEIA,IAAcztD,KAAKytD,YACnBztD,KAAKytD,UAAYA,EACZztD,KAAKktD,OAAOO,EAAU7kD,SAE/B,IAAK,MAAOnF,EAAIiqD,KAAepqD,OAAOsD,QAAQvD,GACtCrD,KAAKmtD,WAAWnxD,IAAIyH,KAExBzD,KAAKmtD,WAAWnnD,IAAIvC,EAAI,CAAEwjB,QAAOymC,eAC5B1tD,KAAKktD,OAAOltD,KAAK2tD,YAAYlqD,GAE1C,CAGA,UAAIN,GACA,OAAOnD,KAAKmgC,QAChB,CAGA,gBAAIytB,GACA,OAAO5tD,KAAKiL,KAChB,CAOA,MAAA4iD,CAAO1qD,GACH,MAAM6Y,EAA0B,IAAKhc,KAAKmgC,UACpC2tB,EAAyB,GAC/B,IAAK,MAAMrqD,KAAMH,OAAOC,KAAKJ,GAA0B,CACnD,MAAMT,EAAQS,EAAOM,GACjBuY,EAAKvY,KAAQf,SAEH,IAAVA,SAA4BsZ,EAAKvY,GAChCuY,EAA8CvY,GAAMf,EACzDorD,EAAQnlD,KAAKlF,GACjB,CAEA,GADAzD,KAAKmgC,SAAWnkB,GACZhc,KAAKktD,OAAUY,EAAQvpD,OAA3B,CAEA,IAAK,MAAMd,KAAMqqD,EAAS9tD,KAAK+tD,YAAYtqD,GAC3CzD,KAAKguD,QAAQF,EAHsB,CAIvC,CAGA,OAAA5wB,CAAQz5B,GACJ,OAAOzD,KAAKmgC,SAAS18B,IAAOzD,KAAKiuD,eAAexqD,IAAOzD,KAAK0D,UAAUD,EAC1E,CAMA,OAAAyqD,CAAQ/xC,EAA2BxX,GAC/B,MAAMjC,EAAQyrD,aAAahyC,EAAOxX,GAClC,YAAc,IAAVjC,IAEJ1C,KAAKotD,eAAepnD,IAAI6mD,UAAU1wC,EAAM1Y,GAAIkB,GAAWjC,IAChD,EACX,CAMA,eAAA0rD,CACIhnD,EACAzC,GAEA,OAAO3E,KAAKotD,eAAetxD,IAAI+wD,UAAUzlD,EAASzC,GACtD,CAGA,WAAA0pD,CAAYjnD,EAAiBzC,EAAgDjC,GACzE,MAAM2D,EAAcrG,KAAKkG,UAAUG,YACnC,IAAKA,EAAYuB,SAASR,GAAU,OAEpC,MAAMknD,EAAiCjoD,EACnComD,kBAAkBzwD,IAAI2I,GACtB2pD,EAAOltC,kBAAkBha,EAASzC,EAAUjC,EAAO,CAAE4F,UAAU,IAE/DgmD,EAAOhtC,iBAAiBla,EAASzC,EAAUjC,EAAO,CAAE4F,UAAU,GAEtE,CAIA,OAAAugB,CAAgBJ,GACRA,IAAYzoB,KAAKmgC,SAAW,CAAC,GACjCngC,KAAKktD,OAAQ,EACbltD,KAAKwtD,aACL,MAAM/hC,EAAMnoB,OAAOC,KAAKvD,KAAKmgC,UAC7B,IAAK,MAAM18B,KAAMgoB,EAAKzrB,KAAK+tD,YAAYtqD,GACvCzD,KAAKguD,QAAQviC,EACjB,CAKA,OAAAuiC,CAAgBnK,GACZ,MAAM9nC,EAAW/b,KAAKkG,UAAUqoD,mBAC1B5K,EAAU,IAAIrvC,IAAIuvC,GACxB,IAAK,MAAMpgD,KAAMH,OAAOC,KAAKwY,GACrBA,EAAStY,KAAQzD,KAAKmgC,SAAS18B,IAAKkgD,EAAQ1oC,IAAIxX,GAExDzD,KAAKkG,UAAUsoD,sBAAsBxuD,KAAKmgC,SAAUwjB,EACxD,CAKA,UAAA6J,GACI,MAAMviD,EAAQjL,KAAKkG,UAAUG,YAAY4X,WACzCje,KAAKiL,MAAQ,CACTgF,QAAShF,GAAOgF,QAAU,IAAI/O,OAAQib,GAAUnc,KAAKkG,UAAUuoD,cAActyC,EAAM1Y,KACnFwY,QAAShR,GAAOgR,SAAW,CAAC,EAC5ByyC,WAAYzjD,GAAOyjD,WACnBC,IAAK1jD,GAAO0jD,KAEhB3uD,KAAKotD,8BAAiB,IAAIhnD,IAC1BpG,KAAKqtD,wBAAW,IAAIjnD,IACpBpG,KAAKstD,kCAAqB,IAAIlnD,IAC9BpG,KAAKutD,sBAAS,IAAIj5C,IAClBtU,KAAK4uD,iCACL5uD,KAAKytD,WAAW7kD,QAChB,IAAK,MAAMnF,KAAMzD,KAAKmtD,WAAW5pD,OAAQvD,KAAK2tD,YAAYlqD,EAC9D,CAKA,8BAAAmrD,GACI,MAAMvoD,EAAcrG,KAAKkG,UAAUG,YAC7BwoD,EAAaxoD,EAAY4E,OAAO4jD,WACtC,IAAKA,GAAcA,EAAWC,aAAehC,cAAe,OAE5D,MAAMiC,EAAUF,EAAWC,WAC3BD,EAAWC,WAAahC,cACxBzmD,EAAYqX,KAAK,SAAA,KACbmxC,EAAWC,WAAaC,GAEhC,CAEA,WAAApB,CAAoBlqD,GAChB,MAAM0pD,EAAantD,KAAKmtD,WAAWrxD,IAAI2H,GACvC,IAAK0pD,EAAY,OAEjB,MAAMO,WAAEA,GAAeP,EACjB6B,EAAatB,EAAWsB,WAAWxrD,IAAKopD,GAAc5sD,KAAKivD,iBAAiBxrD,EAAImpD,IAChFv7C,EAAU,IAAIiD,IAAI06C,EAAWzyC,QAAS2yC,GAAsBA,EAAkBv0C,WAAWoT,KAC/F/tB,KAAKqtD,SAASrnD,IAAIvC,EAAI,CAAEiqD,aAAYsB,aAAY39C,WACpD,CAEA,gBAAA49C,CAAyBxrD,EAAiBmpD,GACtC,IAAKD,iBAAiBC,GAClB,MAAO,CAAEA,YAAWjyC,SAAU3a,KAAKytD,WAAWrkC,QAAQ3lB,EAAImpD,IAAc,GAAIuC,cAAe,IAG/F,MAAMl/C,OAAEA,EAAAgM,QAAQA,GAAYjc,KAAKiL,MAC3B+xB,EAAU/sB,EAAO/O,OAAQib,GAAU0gB,wBAAwB+vB,EAAU38C,OAAQkM,EAAOF,IAC1F,IAAItB,EACAw0C,EAA0B,GAC9B,OAAQvC,EAAUvsD,MACd,IAAK,QACDsa,EAAW3a,KAAKovD,WAAWpyB,EAAS4vB,EAAUjoD,UAC9C,IAAK,MAAMyC,KAAWuT,EAAU,CAC5B,MAAMnG,EAAMq4C,UAAUzlD,EAASwlD,EAAUjoD,UACzC3E,KAAKstD,mBAAmBtnD,IAAIwO,EAAK,IAAKxU,KAAKstD,mBAAmBxxD,IAAI0Y,IAAQ,GAAK/Q,GACnF,CACA,MACJ,IAAK,QAAS,CACVkX,EAAW3a,KAAKovD,WAAWpyB,EAAS4vB,EAAUjoD,UAC9C,MAAM0qD,EAAiBzC,EAAU0C,QAAU,GACrCA,EAASr/C,EAAO/O,OAAQib,GAAU0gB,wBAAwBwyB,EAAgBlzC,EAAOF,IACvFkzC,EAAgBnvD,KAAKovD,WAAWE,EAAQ1C,EAAUjoD,UAClD,KACJ,CACA,IAAK,UACL,IAAK,UACDgW,EAAWqiB,EAAQx5B,IAAK2Y,GAAUA,EAAM1Y,IACxC,IAAK,MAAM0Y,KAAS6gB,EAChBh9B,KAAKkuD,QAAQ/xC,EAAO,WACpBnc,KAAKkuD,QAAQ/xC,EAAO,WAExB,MACJ,IAAK,eAEDxB,EAAWqiB,EAAQx5B,IAAK2Y,GAAUA,EAAM1Y,IACxC,IAAK,MAAM0Y,KAAS6gB,EAASh9B,KAAKkuD,QAAQ/xC,EAAOywC,EAAUjoD,UAC3D,MACJ,IAAK,aACDgW,EAAWqiB,EACN97B,OAAQib,GAAUzP,gBAAgB6iD,cAAcpzC,GAAQ,GAAGvP,SAC3DpJ,IAAK2Y,GAAUA,EAAM1Y,IAGlC,MAAO,CAAEmpD,YAAWjyC,WAAUw0C,gBAClC,CAGA,UAAAC,CAAmBn/C,EAA8BtL,GAC7C,OAAOsL,EAAO/O,OAAQib,GAAUnc,KAAKkuD,QAAQ/xC,EAAOxX,IAAWnB,IAAK2Y,GAAUA,EAAM1Y,GACxF,CAMA,WAAAsqD,CAAoBtqD,GAChB,MAAM+rD,EAAexvD,KAAKqtD,SAASvxD,IAAI2H,GACvC,GAAK+rD,GAAiBxvD,KAAKkG,UAAUuX,SAArC,CAE6B,IAAzB+xC,EAAan+C,cAAuC,IAAtBrR,KAAKmgC,SAAS18B,IAC5CzD,KAAKyvD,SACDhsD,EACAA,EACA,SAASA,wGAGjB,IAAK,MAAMyrD,KAAqBM,EAAaR,WACzChvD,KAAK0vD,iBAAiBjsD,EAAIyrD,EAViB,CAYnD,CAEA,gBAAAQ,CAAyBjsD,GAAiBmpD,UAAEA,EAAAjyC,SAAWA,EAAAw0C,cAAUA,IAC7D,GAAKxC,iBAAiBC,GAItB,GAAuB,UAAnBA,EAAUvsD,KAId,IAAK,MAAM+G,KAAWuT,EAAU3a,KAAK2vD,aAAalsD,EAAImpD,EAAWxlD,QAH7DpH,KAAK4vD,aAAahD,EAAUjoD,SAAUgW,EAAUw0C,EAAenvD,KAAKmgC,SAAS18B,SAJ7EzD,KAAKytD,WAAWoC,QAAQpsD,EAAImpD,EAQpC,CAEA,YAAA+C,CACIlsD,EACAmpD,EACAxlD,GAEA,MAAM1E,EAAQ1C,KAAKmgC,SAAS18B,GAC5B,OAAQmpD,EAAUvsD,MACd,IAAK,QACDL,KAAK8vD,sBAAsBrsD,EAAI2D,EAASwlD,EAAUjoD,UAClD,MACJ,IAAK,UACL,IAAK,UACD3E,KAAK+vD,iBAAiB3oD,EAASmlD,YAAYK,EAAUvsD,MAAOqC,GAC5D,MACJ,IAAK,eACD1C,KAAKquD,YACDjnD,EACAwlD,EAAUjoD,SACVjC,GAAS1C,KAAKouD,gBAAgBhnD,EAASwlD,EAAUjoD,WAErD,MACJ,IAAK,aACD3E,KAAKgwD,kBAAkB5oD,EAAS1E,GAG5C,CAGA,gBAAAqtD,CAAyB3oD,EAAiB6oD,EAAqBvtD,GAC3D,MAAM2D,EAAcrG,KAAKkG,UAAUG,YACnC,IAAKA,EAAYuB,SAASR,GAAU,OAEpC,MAAMpF,EAAQ,CACVgf,QAAShhB,KAAKkwD,eAAe9oD,EAAS,WACtC2Z,QAAS/gB,KAAKkwD,eAAe9oD,EAAS,YAE1CpF,EAAMiuD,GAASvtD,GAASV,EAAMiuD,GAC9B5pD,EAAY4a,kBAAkB7Z,EAASpF,EAAMgf,QAAShf,EAAM+e,QAChE,CAEA,cAAAmvC,CAAuB9oD,EAAiB6oD,GACpC,OAAOjwD,KAAKouD,gBAAgBhnD,EAAS6oD,IAAUvD,cAAcuD,EACjE,CAEA,iBAAAD,CAA0B5oD,EAAiBuF,GACvC3M,KAAKqoB,eAAelhB,aAChBC,EA1ZmB,4BA4ZT,IAAVuF,OAAsB,EAAazL,GAAWwL,gBAAgBxL,EAAQyL,GAAOzL,OAErF,CAEA,qBAAA4uD,CAA8BrsD,EAAiB2D,EAAiBzC,GAC5D,MAAMwrD,EAAWnwD,KAAKouD,gBAAgBhnD,EAASzC,GACzC5C,GAAU/B,KAAKstD,mBAAmBxxD,IAAI+wD,UAAUzlD,EAASzC,KAAc,IAAIuD,OAAA,CAC5EkoD,EAASC,IAAWD,GAAYpwD,KAAKmgC,SAASkwB,IAAkC,GACjF,GAEJ,GAAe,IAAXtuD,EAEA,YADA/B,KAAKquD,YAAYjnD,EAASzC,EAAUwrD,GAGxC,MAAM9jD,EAASV,kBAAkBwkD,EAAUpuD,QAC5B,IAAXsK,EAQJrM,KAAKquD,YAAYjnD,EAASzC,EAAU0H,GAPhCrM,KAAKyvD,SACDhsD,EACA,SAAS2D,KAAWzC,IACpB,iBAAiBA,gBAAuByC,gKAKpD,CAEA,YAAAwoD,CACIjrD,EACAgW,EACAw0C,EACAzsD,GAEA,IAAK,MAAM0E,KAAWuT,EAClB3a,KAAKquD,YAAYjnD,EAASzC,EAAUjC,GAAS1C,KAAKouD,gBAAgBhnD,EAASzC,IAI/E,MAAM2rD,EAAkB31C,EAASnX,IAAK4D,GAAYpH,KAAKouD,gBAAgBhnD,EAASzC,IAAWnD,KAAK0M,gBAChG,IAAK,MAAM9G,KAAW+nD,EAAe,CACjC,MAAMoB,EAAgBvwD,KAAKouD,gBAAgBhnD,EAASzC,GACpD,IAAI6rD,EAAkBD,EACtB,QAAc,IAAV7tD,EAAqB,CACrB,MAAMiK,EACFuB,eAAeqiD,IAAkBD,GAAmB1hD,gBAAgB0hD,EAAiBC,GACzFC,GAAU7jD,GAASsC,SAASvM,EAAOiK,KAAWjK,CAClD,CACA1C,KAAKquD,YAAYjnD,EAASzC,EAAU6rD,EACxC,CACJ,CAIA,cAAAvC,CAAuBxqD,GACnB,MAAOgtD,GAASzwD,KAAKqtD,SAASvxD,IAAI2H,IAAKiqD,WAAWsB,YAAc,GAChE,OAAOyB,IAAU9D,iBAAiB8D,GAASzwD,KAAKytD,WAAWQ,eAAexqD,EAAIgtD,QAAS,CAC3F,CAIA,SAAA/sD,CAAkBD,GACd,MAAM+rD,EAAexvD,KAAKqtD,SAASvxD,IAAI2H,GACvC,IAAK+rD,EAAc,OAEnB,MAAM9B,WAAEA,EAAAsB,WAAYA,GAAeQ,EACnC,GAAwB,WAApB9B,EAAWzqD,KAAmB,OAAO,EAEzC,MAAOwtD,GAASzB,GACT5nD,GAAWqpD,GAAO91C,UAAY,GACrC,QAAgB,IAAZvT,EAAuB,OAE3B,MAAMwlD,UAAEA,GAAc6D,EACtB,IAAK9D,iBAAiBC,GAAY,OAAO5sD,KAAKytD,WAAW/pD,UAAUD,EAAImpD,GAEvE,OAAQA,EAAUvsD,MACd,IAAK,QAAS,CACV,MAAM8vD,EAAWnwD,KAAKouD,gBAAgBhnD,EAASwlD,EAAUjoD,UACzD,OAAOuJ,eAAeiiD,GAAYA,OAAW,CACjD,CACA,IAAK,UACL,IAAK,UACD,OAAOnwD,KAAKkwD,eAAe9oD,EAASmlD,YAAYK,EAAUvsD,OAC9D,IAAK,eAAgB,CACjB,MAAM8vD,EAAWnwD,KAAKouD,gBAAgBhnD,EAASwlD,EAAUjoD,UACzD,MAA2B,iBAAbwrD,GAA6C,iBAAbA,EAAwBA,OAAW,CACrF,CACA,IAAK,aACD,OAAO,EACX,QACI,OAEZ,CAEA,QAAAV,CAAiBhsD,EAAiB+Q,EAAak8C,GACvC1wD,KAAKutD,OAAOvxD,IAAIwY,KAEpBxU,KAAKutD,OAAOtyC,IAAIzG,GAChBnJ,QAAQC,KAAK,IAAItL,KAAKmtD,WAAWrxD,IAAI2H,IAAKwjB,OAASgmC,EAAWxrD,SAASivD,KAC3E,GAYSC,eAAb,MAKI,WAAA9wD,CACIonB,EACA8kB,EACA1oC,EACAoqD,GAHiBztD,KAAAinB,MAAAA,EACAjnB,KAAA+rC,OAAAA,EACA/rC,KAAAqD,YAAAA,EACArD,KAAAytD,UAAAA,CAClB,CAEH,WAAY9J,GAIR,OAHA3jD,KAAK4wD,mBAAqB,IAAIxqD,IACzB9C,OAAOC,KAAKvD,KAAKqD,aAAsBG,IAAKC,GAAqB,CAACA,EAAGgB,MAAMzE,KAAK+rC,OAAOxnC,QAASd,KAE9FzD,KAAK4wD,gBAChB,CAGA,GAAA50D,CAAIyH,GACA,OAAOA,KAAMzD,KAAKqD,WACtB,CAGA,gBAAIwtD,GACA,OAAQvtD,OAAOC,KAAKvD,KAAKqD,aAAsBnC,OAC1CuC,GAA0E,UAA9BzD,KAAKqD,YAAYI,GAAIR,KAE1E,CAGA,QAAA6tD,CAAS5qD,GACL,MAAM6qD,EAAS9D,WAAWtpC,IAAIzd,GAE9B,OADA6qD,EAAOppD,SAAS3H,KAAKinB,MAAOjnB,KAAKqD,YAAarD,KAAKytD,YAAYvnD,IACxD6qD,CACX,CAGA,KAAA/oD,CAAM9B,EAAsB/F,GACxB,MAAMgD,EAAkC,CAAC,EACzC,IAAK,MAAO6tD,EAAOvtD,KAAOzD,KAAK2jD,QAASxgD,EAAOM,GAAM3H,IAAIqE,EAAQ6wD,GACjEhxD,KAAK8wD,SAAS5qD,GAAW2nD,OAAO1qD,EACpC,CAGA,OAAA+5B,CAAQh3B,EAAsBzC,GAC1B,MAAM4sD,EAASrwD,KAAK2jD,QAAQ7nD,IAAI2H,GAChC,YAAkB,IAAX4sD,OAAuB,EAAYrwD,KAAK8wD,SAAS5qD,GAAWg3B,QAAQmzB,EAC/E,GAOSY,eAAA,CACThqC,EACA8kB,EACA1oC,EACAoqD,IAC4C,IAAIkD,eAAe1pC,EAAO8kB,EAAQ1oC,EAAaoqD,GAEzF8B,cAAiBpzC,GAAwC,WAAYA,EAAQA,EAAMjb,YAAS,EAOrFitD,aAAA,CAAgBhyC,EAA2BxX,KACpD,MAAM+W,EAAU,WAAYS,EAAQA,EAAMT,YAAS,EAC7C2F,EAAS,UAAWlF,EAAQA,EAAMkF,WAAQ,EAChD,OAAQ1c,GACJ,IAAK,YACD,MAAsB,WAAfwX,EAAM9b,WAAgD,IAA3Bqb,IAAS,cACpCA,EAAO,cAAgBgxC,cAAc,kBACtC,EACV,IAAK,YACD,MAAsB,WAAfvwC,EAAM9b,WAAgD,IAA3Bqb,IAAS,cACpCA,EAAO,cAAgBgxC,cAAc,kBACtC,EACV,IAAK,aACD,MAAsB,SAAfvwC,EAAM9b,KAAmBghB,IAAQ,eAAiBqrC,cAAc,mBAAiB,EAC5F,IAAK,UACD,OAAOvwC,EAAM6E,QACjB,IAAK,UACD,OAAO7E,EAAM4E,QACjB,QAEI,OACIM,IAAQ1c,KACQ,cAAfwX,EAAM9b,MA9kBjB,CAAkBsE,GAAoEA,KAAY+nD,cA8kBzDwE,CAAevsD,GAAY+nD,cAAc/nD,QAAY,KC3jBvFwsD,UAAA,CAAaxsD,EAA4BsL,KAAA,CAClDhN,KAAM,SACN+rD,WAAY,CAAC,CAAE3uD,KAAM,QAASsE,WAAUsL,OAAQ,CAACA,OAIxCmhD,UAAA,CACTzsD,EACAsL,EACAq/C,KAAA,CAEArsD,KAAM,QACN+rD,WAAY,CAAC,CAAE3uD,KAAM,QAASsE,WAAUsL,OAAQ,CAACA,GAASq/C,OAAQA,GAAU,CAACA,OChGpE+B,2BAA6B,CAAC,OAAQ,cAAe,mBAAoB,aAiBhFC,+BAAmF,CACrFC,KAAM,CAAEt1C,QAAS,CvDrBa,euDqBIugB,aAAc,CAAC,OAAQtsB,WAAY,CAAC,WACtE9G,YAAa,CAAE6S,QAAS,CvDmCU,oBuDlClC9S,iBAAkB,CAAE8S,QAAS,CvDuBU,yBuDrBvCu1C,UAAW,CAAEthD,WAAY,CAAC,eAWjBuhD,wBAA2Br1B,GARlC,CAA2BA,GAC7BA,KAASk1B,+BAQTI,CAAwBt1B,GAASk1B,+BAA+Bl1B,GAAS4C,0BAA0B5C,GCrC1Fu1B,8BAAiC,MAE1C,MAAMC,EAAYH,wBAAwB,QAC1C,MAAO,CACH,kBAAmB,CACfxuD,KAAM,SACN+rD,WAAY,CACR,CAAE3uD,KAAM,QAASsE,SAAU,YAAasL,OAAQ,CAAC2hD,IACjD,CAAEvxD,KAAM,QAASsE,SAAU,YAAasL,OAAQ,CAAC2hD,MAGzD,eAAgB,CACZ3uD,KAAM,SACN+rD,WAAY,CAAC,CAAE3uD,KAAM,UAAW4P,OAAQ,CAAC,IAAK2hD,EAAWnhD,WAAY,CAAC,cAE1E,iBAAkB,CAAExN,KAAM,SAAU+rD,WAAY,CAAC,CAAE3uD,KAAM,aAAc4P,OAAQ,CAAC2hD,MAChF,mBAAoBR,UAAU,aAAcQ,GAC5C,uBAAwBR,UAAU,kBAAmBQ,GAE7D,EAnB8C,GAyBjCC,wBAA2BZ,eAAe,aAAc,QAASU,gBC3BjEG,YAAej7C,IACxB,MAAM1G,WAAEA,GAAe0G,EACvB,MAAO,CACHpT,GAAIoT,EAAQpT,GACZpD,KAAMwW,EAAQxW,KACdyW,SAAUD,EAAQC,SAClB3G,WAAY,CACR1M,GAAIm1B,OAAOzoB,GAAY1M,IACvBhC,KAAM0O,GAAY1O,KAClB2W,SAAUjI,GAAYiI,SACtBgkB,MAAOjsB,GAAYisB,MACnB31B,SAAU0J,GAAY1J,YCgBrBsrD,mBAAsBrc,IAC/B,MAAMsc,EAActc,EAAWn5B,QAASnE,GATtC,CAAmBA,GACrBA,KAAY+yC,kBASR8G,CAAgB75C,GACV+yC,kBAAkB/yC,GAAU5U,IAAImyC,sBAChC,CAACA,qBAAqBv9B,KAIhC,MAAO,IAAI,IAAI9D,IAAI09C,EAAY9wD,OAAQgxD,IAAsC/1D,MAAM+1D,OAWjFC,qBAAuB,CAAC,aAAc,eActCC,oBAAA,CACFC,EACAC,KAEA,MAAMC,EACwB,iBAAnBD,GAA+BA,EAAe5wD,SAAS,KAfhE,CAA2B8wD,IAC7B,MAAMzZ,EAA8C,GACpD,IAAK,MAAOnwC,EAAO+B,KAAS6nD,EAAUxZ,MAAM,QAAQpyC,UAC5CgC,EAAQ,EAAGmwC,EAAMpwC,KAAK,CAAC,MAAOgC,IACzBA,GAAMouC,EAAMpwC,KAAKgC,GAE9B,MAAO,CAAC,YAAaouC,IAUX0Z,CAAwBH,GACvBA,GAAkB,GACvB/Z,EAA6C,CAAC,QAAS,CAAC,MAAO,eAAgB,IAAI8Z,GAAiBvnB,QAE1G,OADAyN,EAAK5vC,KAAK4pD,GACHha,GAILma,cAAiBt6C,GAA0C,QAAQA,IA4G5Du6C,WAAb,MAAaA,UAAmBjmC,4BAmD5B,UAAO5wB,CAAI0H,EAAgBrD,GACvB,OAAOg9B,oBAAoB35B,EAAKmvD,EAAA,IAAkB,IAAIA,EAAWnvD,EAAKrD,GAASA,EACnF,CAEA,WAAAN,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,EACf,CAKA,sBAAA2oB,GACI,MAAMyoC,EAAO7zB,sBAAsB19B,KAAKqG,Y1DnPd,c0DmP0CssD,EAAWlxD,KAAO0a,GAClFyhB,YAAYl8B,SAASya,EAAM1Y,KAY/B,OAVAzD,KAAK4yD,aAAe,CAAC,EACrB5yD,KAAK6yD,aAAe,IAAI91B,iBAAiB,OAAQ,CAAE,uBAAwB,CAAC,CAAEvsB,WAAY,CAAC,aAC3FxQ,KAAK6yD,aAAajqD,MAAM2oD,EAAKtwD,aAC7BjB,KAAK8yD,gBAAkB9yD,KAAKqG,YAAYuB,SAAS6yB,iBAAiBG,KAC5D,CACI,aAAc56B,KAAKqG,YAAYkmB,kBAAkBkO,iBAAiBG,IAAK,cACvE,cAAe56B,KAAKqG,YAAYkmB,kBAAkBkO,iBAAiBG,IAAK,gBAE5E,CAAC,EAEA,CAAE22B,OACb,CAKA,YAAA/nC,CAAuBrpB,GAanB,OAZIA,IAAWhE,MAAMgE,EAAOojB,SACxBvjB,KAAK+yD,YAAY5yD,EAAOojB,SAAS,GACzBvjB,KAAK8nB,eAAkB9nB,KAAKksB,aAEpClsB,KAAK+yD,aAAY,GAAM,GAG3B/yD,KAAKgzD,sBAAsB7yD,GAAQoF,SAASmwC,YAC5C11C,KAAKizD,mBAAmB9yD,GAAQy5C,MAAMyF,eACtCwS,SAAS7pD,MAAMhI,KAAKkG,UAAW/F,GAC3BH,KAAKkG,UAAUuX,UAAUzd,KAAK6yD,aAAa7qD,MAAMhI,KAAK2sB,mBAAoBxsB,GAEvEA,CACX,CAKA,UAAA6pB,CAAWvmB,GACP,OAAOyD,MAAM8iB,WAAWvmB,IAAOouD,SAAS30B,QAAQl9B,KAAKkG,UAAWzC,IAAOzD,KAAK6yD,aAAa31B,QAAQz5B,EACrG,CAcA,SAAAyoB,GACI,OAAOlsB,KAAKG,QAAQojB,SAAWvjB,KAAK0mB,kBAAkB6qC,KAAKtiC,mBAC/D,CAgBA,UAAAjD,CAAWzI,GACPvjB,KAAK+yD,YAAYxvC,EACrB,CAEA,WAAAwvC,CAAoBxvC,EAAkBmG,GAAe,GACjD,GAAI1pB,KAAKkG,UAAUuX,SACf,IAAK,MAAMrW,KAAWpH,KAAK0mB,kBAAkB6qC,KAAKvoC,kBAAkBsE,SAChEttB,KAAK2sB,mBAAmBX,WAAW5kB,EA1RpB,eA0RqDmc,GAGxEmG,IACA1pB,KAAKG,OAAS,IAAKH,KAAKG,OAAQojB,WAChCvjB,KAAKypB,mBAEb,CAEA,qBAAAupC,CAA8B9G,GAC1B,GAAIlsD,KAAKkG,UAAUuX,WAAayuC,GAAoBlsD,KAAKksD,kBAAmB,CACxE,MAAMgH,EACFhH,GAAoB1mD,uBAAuB0mD,GACrCzmD,wBACI,WACAymD,EAAiBlpD,KACjB+uD,mBAAmB7F,EAAiB/oD,cAExC,EAEVnD,KAAKqoB,eAAe7gB,UAAUizB,iBAAiBG,IAhT5B,kBAgTyDs4B,EAChF,CACAlzD,KAAKksD,iBAAmBA,CAC5B,CAEA,kBAAA+G,CAA2B5T,GACvB,IAAKr/C,KAAKkG,UAAUuX,WAAazd,KAAKqG,YAAYuB,SAAS6yB,iBAAiBG,KAAM,OAElF,MAAMhV,EAAqC,CAAC,EACtCysC,iBAAkB,IAAIjsD,IACtB+sD,iBAAmB,IAAI/sD,IAC7B,IAAK,MAAOg+C,EAAaxK,KAASuK,mBAAmB9E,GAAgB,CACjE,MAAMzlC,EAAU84C,cAAc9Y,EAAKn2C,IACnCmiB,EAAOhM,GAAW,CAAEiM,MAAO+zB,EAAK/zB,MAAOZ,WAAY20B,EAAK30B,YAAA,GACxDotC,EAAgBrsD,IAAIo+C,EAAaxqC,GACjCu5C,EAAiBntD,IAAIo+C,EAAa,CAAErvB,EAAG6kB,EAAKC,SAAW,EAAG7kB,EAAG4kB,EAAKE,SAAW,GACjF,CACA,IAAKuY,EAAgBtkC,OAASzqB,OAAOC,KAAKvD,KAAK4yD,YAAYruD,OAAQ,OAEnE,MAAQ,aAAc+tD,EAAgB,cAAec,GAAoBpzD,KAAK8yD,gBACxEp3C,EAAqB,CACvB,aAAc22C,EAAgBtkC,KAAOqkC,oBAAoBC,EAAiBC,GAAkBA,EAC5F,cAAevY,0BAA0BoZ,EAAkB,WAAYC,IAAoBA,GAG/F,IAAK,MAAMzuD,KAAYwtD,qBACnBnyD,KAAKqG,YAAY+a,kBAAkBqZ,iBAAiBG,IAAKj2B,EAAU+W,EAAO/W,IAE9E3E,KAAK4yD,WAAaltC,WAAW1lB,KAAKqG,YAAarG,KAAK4yD,WAAYhtC,EACpE,CAgBA,QAAAkd,GACI,MAAO,CACHyuB,KAAMvxD,KAAK0mB,kBAAkB6qC,KAAKjiC,wBAAwB9rB,IAAIsuD,aAEtE,CAmCA,UAAIpnC,GACA,OAAO1qB,KAAK4qB,aAAgC,CAAC,QAAS,CAAE/D,QAASirC,aACrE,GCvZSuB,6BAAA,CACTlzD,EACA+jC,KAEA,MAAMovB,EAASnzD,GAAQuN,QAAwB,SAAdw2B,EA1BlB,UAHA,WA8BTlxB,EAAY9D,kBAAkBokD,GAC9BC,OAAyB,IAAdvgD,GAA2BA,EAzBjB,GANZ,UAGA,UA8Bf,MAAO,CAAEsgD,SAAQ3tB,KAAMxlC,GAAQ8R,OAAOvE,OAAS6lD,ICFtCC,sBAAwB,CACjC,UACA,WACA,uBACA,kBACA,WACA,aACA,aACA,eACA,UACA,SAQSC,oBAAsB,CAAC,QAAS,WAAY,UAAW,SAAU,qBAkBjEC,kBAAoB,IAC1BF,yBACAC,qBA0CDE,kBAAoB,CACtB,UACA,QACA,UACA,QACA,cACA,QACA,UACA,QAkFSC,iBAAmB,CAC5BC,QAAS,CACLC,UAAW,kBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,QACTC,MAAOL,mBAEXM,SAAU,CACNH,UAAW,mBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,SACTC,MAAOL,mBAEXO,qBAAsB,CAClBJ,UAAW,+BACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,QACTC,MAAOL,mBAEXQ,gBAAiB,CACbL,UAAW,0BACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,QACTC,MAAOL,mBAEXS,SAAU,CACNN,UAAW,mBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,QACTC,MAAOL,mBAEXU,WAAY,CACRP,UAAW,qBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,SACTC,MAAOL,mBAKXW,WAAY,CACRR,UAAW,qBACXvwC,SAAS,EACTywC,MA9HmB,CAAC,UAAW,SAgInCO,aAAc,CACVT,UAAW,uBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,SACTC,MAAOL,mBAEXa,QAAS,CACLV,UAAW,kBACXvwC,SAAS,EACTtY,MAAO,OACP8oD,QAAS,SACTC,MAAOL,mBAEXc,MAAO,CAAEX,UAAW,gBAAiBvwC,SAAS,EAAOtY,MAAO,OAAQ8oD,QAAS,QAASC,MAAOL,mBAC7Fe,MAAO,CAAEZ,UAAW,UAAWvwC,SAAS,EAAMtY,MAAO,SAAU8oD,QAAS,QAASC,MAAOL,mBACxFgB,SAAU,CAAEb,UAAW,YAAavwC,SAAS,EAAMtY,MAAO,OAAQ8oD,QAAS,QAASC,MAAOL,mBAC3FiB,QAAS,CACLd,UAAW,YACXvwC,SAAS,EACTtY,MAAO,SACP8oD,QAAS,QACTC,MAAO,CAAC,UAAW,UAAW,QAAS,cAAe,UAE1Da,OAAQ,CAAEf,UAAW,UAAWvwC,SAAS,EAAMtY,MAAO,SAAU8oD,QAAS,QAASC,MAAOL,mBACzFmB,kBAAmB,CACfhB,UAAW,oBACXvwC,SAAS,EACTtY,MAAO,SACP8oD,QAAS,SACTC,MAAOL,oBA+CFoB,oBAAA,CACT10D,EACA20D,IACqCpB,iBAAiBvzD,GAAM2zD,MAAqCtyD,SAASszD,GAYjGC,aAAgB50D,GAAmEuzD,iBAAiBvzD,GAMpG60D,uBAA0B70D,GAE5BtF,sBAAU2G,SAASrB,GAkBjB80D,sBAAyB90D,GAAoCuzD,iBAAiBvzD,GAAMkjB,QAuBpF6xC,UAAa/0D,GACtB00D,oBAAoB10D,EAAM,QAmBjBg1D,kBAbqB7B,sBAAsBtyD,OAAQb,IAAqC+0D,UAAU/0D,IAalDA,GACzDuzD,iBAAiBvzD,GAAMyzD,WC7WrBwB,cAAA,CACFrzC,EAAkE,CAAC,EACnE8jB,IAKAziC,OAAOsD,QAAQqb,GAAYze,IAAA,EACrBC,EAAIuX,MAAA,IAEKA,EACHvX,GAAIoiC,cAAcpiC,EAAIsiC,MAoCzBwvB,kBAAqBp1D,GAC9BA,GAAQq1D,cAAgBt4D,aAAa0+B,SAAS9/B,MAAM05D,aC9FlDC,SAAA,IAAe/pD,IAA6C,CAAC,cAAe,CAAC,UAAW,CAAC,WAAYA,GAY9FgqD,yBAA2B,UAuB3BC,sBAAiD,CAAC,KAAM,CAAC,MAAO,cAAe,YAM/EC,wBAAmD,CAAC,KAAM,CAAC,MAAO,cAAe,cAMjFC,kBAAA,CACTx0C,EACA3F,KAAA,CAEAxa,OAAQy0D,sBACRt1D,KAAM,OACNqb,OAAQ,CAAE,YAAa,WAAYA,GACnC2F,UAQSy0C,uBAAA,CACTtD,EACAxxC,KAAA,CAEA9f,OAAQy0D,sBACRt1D,KAAM,SACN2gB,UACAtF,OAAQ,CACJ,mBAAoB,QACpB,sBAAsB,EACtB,aAAc82C,EACd,YAAaiD,SAASz0C,EAAS,GAAK,KAAM,MAQrC+0C,gBAAA,CAAmBn8C,EAAiBsO,SAC3B,IAAlBA,EAA8BtO,EAAU8rB,aAAa9rB,EAASsO,GAWrD8tC,yBAAsC,CAAE5zD,IAAK,GAAKC,IAAK,EAAGwB,KAAM,GAAKC,OAAQ,QAU7EmyD,iCAA8C,CAAE7zD,IAAK,GAAKC,IAAK,EAAGwB,KAAM,GAAKC,OAAQ,QASrFoyD,2CAAwD,CAAE9zD,IAAK,EAAGC,IAAK,IAAKwB,KAAM,IAAMC,OAAQ,QAShGqyD,+BAA4C,CAAE/zD,IAAK,IAAMC,IAAK,EAAGwB,KAAM,IAAMC,OAAQ,QAUrFsyD,2CAAwD,CAAEh0D,IAAK,EAAGC,IAAK,KAAMwB,KAAM,IAAMC,OAAQ,QAsCxGuyD,aAAgB3zD,GAAqD2B,MAAMC,QAAQ5B,GAEnF4zD,UAAA,CAAapoB,EAA+BnsC,KAC9C,GAAe,IAAXA,EAAc,OAAOmsC,EAEzB,MAAM7hC,EAASV,kBAAkBuiC,EAAMnsC,GACvC,OAAOs0D,aAAahqD,GAAUA,EAAS6hC,GAQ9BqoB,mBAAA,CAAsBC,EAAyBrK,KACxD,MAAMsK,EAAWD,GAAiB,EAC5BnqD,EAASV,kBAAkB8qD,EAAU30D,YAAYqqD,EAAYgK,iCACnE,MAAyB,iBAAX9pD,GAAuBgqD,aAAahqD,GAAUA,EAASoqD,GASnEC,YAAA,CAAeC,EAAuDC,IACxEN,UACIK,EAASC,GAAW7xC,OAAA,KACpBjjB,YAAY80D,GAAWC,YAAab,2BAGtCc,uBAAsE,CACxEvoD,EAAGknD,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,EAAG,GAAI,GACnCsB,EAAGtB,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,EAAG,GAAI,IACnCjnD,EAAGinD,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,KAGlCuB,oBAAmE,CACrEzoD,EAAGknD,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,EAAG,GAAI,IACnCsB,EAAGtB,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,IACpCjnD,EAAGinD,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,KAGlCwB,mBAAqBxB,SAAS,EAAG,GAAK,GAAI,IAU1CyB,6BAAkE,CAAE3oD,EAAG,IAAMwoD,EAAG,EAAGvoD,EAAG,MAM/E2oD,uBAA0BP,GACnCF,YAAYI,uBAAwBF,GASlCQ,cAAA,CAAiBrpC,EAA0Bo+B,IAC7C+K,6BAA6BnpC,GAAA,KAC7BjsB,YAAYqqD,EAAY8J,kCAcfoB,yBAAA,CAA4BtpC,EAA0Bo+B,IAC/DmK,UAAUW,mBAvC4B,GAuC4BG,cAAcrpC,EAAMo+B,IAO7EmL,sBAA0C,CAAC,GAAG,KA9CjB,IAgDpCC,oBAAmE,CACrEhpD,EAAGknD,SAAS,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,IACpCsB,EAAGtB,SAAS,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,GAAI,IACrCjnD,EAAGinD,SAAS,EAAG,GAAI,EAAG,GAAI,GAAI,GAAI,GAAI,KAW7B+B,oBAAuBZ,GAChCF,YAAYa,oBAAqBX,GAE/Ba,wBAAuE,CACzElpD,EAAGknD,SAAS,GAAI,EAAG,GAAI,IACvBsB,EAAGtB,SAAS,GAAI,GAAI,GAAI,IACxBjnD,EAAGinD,SAAS,GAAI,GAAI,GAAI,KAGtBiC,2BAA0E,CAC5EnpD,EAAGknD,SAAS,GAAI,GAAI,GAAI,IACxBsB,EAAGtB,SAAS,GAAI,GAAI,GAAI,IACxBjnD,EAAGinD,SAAS,GAAI,GAAI,GAAI,KAOfkC,wBAA2Bf,GACpCF,YAAYe,wBAAyBb,GAM5BgB,2BAA8BhB,GACvCF,YAAYgB,2BAA4Bd,GAEtCiB,uBAAyBpC,SAAS,GAAI,EAAG,GAAI,GAStCqC,wBAA2BlB,GACpCN,UAAUuB,uBAAwB/1D,YAAY80D,GAAWC,YAAab,2BC7TpE+B,wBAA0B1E,kCAA6B,EAAW,SAY3D2E,0BAA4B,uBAUnCC,mBAA8C,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,EAAG,GAAI,GAAI,IAI/FC,wBAAmD,CACrD,OACA,CAAC,IAAK,CAAC,MAAO,WAAY,KAC1B,CAAC,IAAK,CAAC,MAAO,WAAY,KAC1B,CAAC,IAAK,CAAC,MAAO,WAAY,KAYjBC,sBAAA,CACTjwC,EACA/nB,EACAsjC,KAEA,MAAM20B,EAAuC,UAAtBj4D,GAAQk4D,UAE/B,MAAO,CACHh4D,KAAM,SACNa,OAAQy0D,sBACR30C,QAAS7gB,GAAQunC,SAAA,EACjBhsB,OAAQ,CACJ,mBAAoB,QACpB,aAAcq6C,gBAjDsB,gCAiD4B7tC,GAChE,gBAAiB,OACjB,wBAAyB,CAAC,EAAG,EAAG,EAAG,GACnC,0BAA2BkwC,EAAiB,MAAQ,WACpD,0BAA2BA,EAAiB,MAAQ,cAChDA,GAAkB,CAClB,cAAeF,wBACf,cAAeA,yBAEnB,aAAc,CAAC,MAAO,SACtB,YAAa,CjElDI,aiEmDjB,YAAaD,mBAEb,sBAAsB,EACtB,sBAAsB,MACE,IAApB93D,GAAQojB,SAAqB,CAAE5H,WAAY,SAEnD0F,MAAO,CAAE,aAAcoiB,GAAQkC,MAAQoyB,wBAAwBpyB,QC/CjE2yB,wBAAyF,CAC3FzE,QAAS,CAAEnmD,MAAO,UAAWi3B,QAAS,KACtCsvB,SAAU,CAAEvmD,MAAO,UAAWi3B,QAAS,IACvCuvB,qBAAsB,CAAExmD,MAAO,UAAWi3B,QAAS,IACnDwvB,gBAAiB,CAAEzmD,MAAO,UAAWi3B,QAAS,IAC9CyvB,SAAU,CAAE1mD,MAAO,UAAWi3B,QAAS,IACvC0vB,WAAY,CAAE3mD,MAAO,UAAWi3B,QAAS,IACzC4vB,aAAc,CAAE7mD,MAAO,UAAWi3B,QAAS,KAC3C6vB,QAAS,CAAE9mD,MAAO,UAAWi3B,QAAS,IACtC8vB,MAAO,CAAE/mD,MAAO,UAAWi3B,QAAS,KAGlC4zB,YAAwE,CAC1EC,WAAO,EACPC,OAAQ,CAAC,EAAG,KACZC,OAAQ,CAAC,EAAG,MAiBHC,iBAAA,CAAoBt4D,EAAwBF,IACrDA,GAAQojB,SAAWqwC,iBAAiBvzD,GAAMkjB,QASjCq1C,iBAAA,CAAoBv4D,EAAwBF,IACpD40D,oBAAoB10D,EAAM,UAAYF,GAAQ8K,OAAUgqD,aAAa50D,GAAM4K,MAK1E4tD,qBAAyD,CAC3DC,KAAM,yBACNC,OAAQ,mBAYCC,mBAAA,CAAsB34D,EAAwBF,KACvD,MAAM84D,EAAYL,iBAAiBv4D,EAAMF,GACzC,OAAO84D,GAAaJ,qBAAqBI,IAiChCC,qBAAA,CACT74D,EACA2a,EACA7a,EACAg5D,EACAC,EAAwB,eAExB,MAAMxC,EAA4B,CAC9B7xC,MAAO5kB,GAAQ4kB,OAASo0C,GAAgBp0C,MACxC8xC,YAAa12D,GAAQ02D,aAAesC,GAAgBtC,aAElDx1C,EAAyC,IACxCrG,EAAKqG,MACR,aACuC,SAAnCu3C,iBAAiBv4D,EAAMF,GACjBq3D,oBAAoBZ,GACpBO,uBAAuBP,IAGrC,GAAIz2D,GAAQuN,OAASqnD,oBAAoB10D,EAAM,SAAU,CACrD,MAAM0mC,EAA2B,YAATqyB,EAAqB9pD,cAAcnP,EAAOuN,OAASvN,EAAOuN,MAC9Eq5B,IAAiB1lB,EAAM,cAAgB0lB,EAC/C,CAQA,YANwB,IAApB5mC,GAAQwkC,SAAyBowB,oBAAoB10D,EAAM,aAC3DghB,EAAM,gBAAkBlhB,EAAOwkC,SAGtB,cAATy0B,GAAwBrE,oBAAoB10D,EAAM,YAxDpD,EACFA,EACAghB,EACAlhB,KAEA,MAAM4zD,EAAU5zD,GAAQ4zD,SAAWkB,aAAa50D,GAAM0zD,QAChDxS,EAAYwS,GAAWwE,YAAYxE,GACrCxS,EACAlgC,EAAM,kBAAoBkgC,SAIvBlgC,EAAM,mBA4CqDg4C,CAAqBh5D,EAAMghB,EAAOlhB,GAE7F,IACA6a,EACHU,OAAQ,IACDV,EAAKU,WACHi9C,iBAAiBt4D,EAAMF,IAAW,CAAEwb,WAAY,SAEzD0F,UAcKi4C,uBAAA,CACTj5D,EACA2a,EACA7a,KAEA,MAAMy5C,EAAOmb,oBAAoB10D,EAAM,QAAUF,GAAQy5C,UAAO,EAChE,MAAO,IACA5+B,EACHU,OAAQ,IACDV,EAAKU,UACJk+B,GAAQ,CACR,aAAcA,EAAK/zB,cACK,IAApB+zB,EAAKuS,YAA4B,CACjC,YAAaoK,mBAAmBv7C,EAAKU,SAAS,aAAck+B,EAAKuS,gBAEjEvS,EAAK2f,WAAa,CAClB,mBAAuC,UAAnB3f,EAAK2f,UAAwB,OAAS,oBAG7DZ,iBAAiBt4D,EAAMF,IAAW,CAAEwb,WAAY,WASpD69C,YAAA,CACTn5D,EACAF,EACAg5D,IAEAD,qBACI74D,EACA,CACIa,OAAQy0D,sBACRt1D,KAAM,OAGNqb,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C2F,MAAO,CACH,aAAci3C,wBAAwBj4D,GAAMqN,MAC5C,eAAgB4qD,wBAAwBj4D,GAAMskC,UAGtDxkC,EACAg5D,GA0DFM,eAA0C,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,GAAI,GAAK,GAAI,IAI7FC,sBAAiD,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,GAAI,IAAM,GAAI,KAIrGC,sBAAiD,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,GAAI,EAAG,GAAI,MAUlGC,cAAyC,CAAC,IAAK,CAAC,MAAO,mBAAoB,CAAC,MAAO,kBAKnFC,iBAA4C,CAC9C,OACA,CAAC,KAAM,CAAC,MAAO,YAAa,UAC5B,CAAC,UAAW,CAAC,EAAG,MAChB,CAAC,UAAW,CAAC,EAAG,KAGdC,cAAiB35D,GACnBA,GAAQqT,MAAM+lD,WAAA,WAqCLQ,YAAA,CACT15D,EACAF,KAEA,MAAO,CACHe,OAAQy0D,sBACRt1D,KAAM,SACN2gB,QAAS7gB,GAAQqT,MAAMk0B,SAAA,GACvBhsB,OAAQ,KAzBZ69C,EA0B4BO,cAAc35D,GAxB5B,aAAdo5D,EACM,CAAE,mBAAoB,SACtB,CAAE,mBAAoB,OAAQ,iBA3DnB,MAkFT,aAAc,CAAC,MAAO,eACtB,YAAaG,sBACb,0BAA2B,WAC3B,aAAc,CAAC,MAAO,aACtB,YAAa,ClExVI,akEyVjB,YAAaC,sBACb,cAAeE,iBACf,0BAA2B,WAC3B,kBAAmBD,kBACdjB,iBAAiBt4D,EAAMF,IAAW,CAAEwb,WAAY,SAGzD0F,MAAO,CAAE,aAAc,CAAC,MAAO,uBAxCjC,IACFk4C,GAqDSS,6BAAqE,CAC9EnF,OAAQ,oBACRC,kBAAmB,iCAWVmF,iBAAA,CACT55D,EACA6nB,KAAA,CAEAhnB,OAAQy0D,sBACRt1D,KAAM,SACN2gB,QAAA,GACAtF,OAAQ,CACJ,mBAAoB,QACpB,kBACsB,IAAlBwM,EACM8xC,6BAA6B35D,GAC7BqlC,aAAas0B,6BAA6B35D,GAAO6nB,GAC3D,YAAauxC,eACb,0BAA2B,cAM7BS,eAAA,CACF75D,EACA4C,IAEA,eAAgB5C,EAAK85D,OAAO,GAAGC,cAAgB/5D,EAAKoE,MAAM,KAAuBxB,IAoBxEo3D,mBAAsDh6D,GAC/D65D,eAAe75D,EAAM,QC1bZi6D,YAAc,QAIdC,aAAe,SAIfC,aAAe,SA+IfC,mBAAqB,mBC/F5BC,2BAAc/3D,SAAd,CAAoC,CAAC,IAAK,IAAK,MAE/Cg4D,qBAAuB,CACzBp3C,QAAS,CACLtgB,KAAM,SACNC,YAAa,+EACbS,SAAS,GAEb+J,MAAO,CACHzK,KAAM,QACNC,YACI,yIAER6hB,MAAO,CACH9hB,KAAM,OACNC,YAAa,sFACbN,QAAS83D,YACT/2D,QAAA,KAEJkzD,YAAa,CACT5zD,KAAM,SACNC,YACI,qGACJlB,MAAOg0D,yBACPryD,QAAS,GAEb,+BAAgC,CAC5BV,KAAM,SACNC,YACI,6GACJlB,MAAOo0D,2CACPzyD,QL3D8C,KK6DlD,iBAAkB,CACdV,KAAM,OACNC,YAAa,4FACbN,uBAASD,SAAT,CAAsC,CAAC,IAAK,IAAK,MACjDgB,QAAA,KAEJ,uBAAwB,CACpBV,KAAM,SACNC,YAAa,4CACblB,MAAOi0D,iCACPtyD,QAAS,GAEb,iCAAkC,CAC9BV,KAAM,QACNC,YAAa,8DAEjB,oCAAqC,CACjCD,KAAM,QACNC,YAAa,wCAEjB,sCAAuC,CACnCD,KAAM,SACNC,YAAa,wCACblB,MAAOgC,eAEX,0BAA2B,CACvBf,KAAM,OACNC,YAAa,uEACbN,uBAASD,SAAT,CAA0C,CAAC,UAAW,UAAW,SACjEgB,QLxE8D,WK0ElE,iCAAkC,CAC9BV,KAAM,SACNC,YAAa,0FACblB,MAAOk0D,2CACPvyD,QL1F8C,KK4FlD,0BAA2B,CACvBV,KAAM,SACNC,YAAa,oFACbS,SAAS,GAEb,wBAAyB,CACrBV,KAAM,QACNC,YAAa,6EAEjB,4BAA6B,CACzBD,KAAM,QACNC,YAAa,6FAEjB,uBAAwB,CACpBD,KAAM,SACNC,YAAa,kFACblB,MAAOiC,kBAEX,4BAA6B,CACzBhB,KAAM,SACNC,YAAa,2DACblB,MAAOkC,uBACPP,QL5GyC,KK8G7C,0BAA2B,CACvBV,KAAM,SACNC,YAAa,kCACblB,MAAOgC,cACPL,QAAS,GAEb,uBAAwB,CACpBV,KAAM,QACNC,YAAa,6EACbN,QAAS03B,SACT32B,QAAS,CpE9IY,coEgJzB,wBAAyB,CACrBV,KAAM,OACNC,YAAa,iGACbN,uBAASD,SAAT,CAAgD,CAAC,sBAAuB,WACxEgB,QAAS,UAEb,6BAA8B,CAC1BV,KAAM,OACNC,YACI,oJACJN,uBAASD,SAAT,CAAsC,CAAC,SAAU,cAAe,iBAEpE,mCAAoC,CAChCM,KAAM,OACNC,YAAa,qFAEjB,+BAAgC,CAC5BD,KAAM,OACNC,YAAa,gFAEjB,8BAA+B,CAC3BD,KAAM,OACNC,YAAa,gFAEjB,6BAA8B,CAC1BD,KAAM,OACNC,YAAa,+EAEjB,8BAA+B,CAC3BD,KAAM,OACNC,YAAa,gFAEjB,0BAA2B,CACvBD,KAAM,OACNC,YAAa,oFAEjB,4BAA6B,CACzBD,KAAM,OACNC,YAAa,uFAEjB,wBAAyB,CACrBD,KAAM,SACNC,YAAa,wDACbS,SAAS,GAEb,8BAA+B,CAC3BV,KAAM,SACNC,YAAa,6CACbS,SAAS,GAEb,yBAA0B,CACtBV,KAAM,SACNC,YAAa,wEACbS,SAAS,GAEb,2BAA4B,CACxBV,KAAM,SACNC,YAAa,uFACbS,SAAS,GAEb,2BAA4B,CACxBV,KAAM,SACNC,YAAa,oDACblB,MAAOmC,WACPR,QAAA,GAEJ,yBAA0B,CACtBV,KAAM,QACNC,YAAa,oFAEjB,+BAAgC,CAC5BD,KAAM,QACNC,YAAa,kGAEjB,6BAA8B,CAC1BD,KAAM,OACNC,YAAa,yFACbN,uBAASD,SAAT,CAA6C,CAAC,WAAY,UAC1DgB,QAAS,aAIXi3D,uBAEF,CACAr3C,QAAUljB,IAAA,CACN4C,KAAM,SACNC,YAAa,SAAS7C,gBACtBsD,QAASsxD,aAAa50D,GAAMkjB,UAEhC7V,MAAQrN,IAAA,CAAY4C,KAAM,QAASC,YAAa,wBAAwB7C,kBACxEskC,QAAUtkC,IAAA,CACN4C,KAAM,SACNC,YAAa,yBAAyB7C,gBACtC2B,MAAOgC,gBAEX+gB,MAAQ1kB,IAAA,CACJ4C,KAAM,OACNC,YAAa,8BAA8B7C,2CAC3CuC,QAAS83D,cAEb7D,YAAcx2D,IAAA,CACV4C,KAAM,SACNC,YAAa,yBAAyB7C,yDACtC2B,MAAOg0D,2BAEX/qD,MAAQ5K,IAAA,CACJ4C,KAAM,OACNC,YAAa,wCAAwC7C,8CACrDuC,uBAASD,SAAT,CAAqC,CAAC,OAAQ,cAC3Ce,UAAUuxD,aAAa50D,GAAM4K,SAEpC8oD,QAAU1zD,IAAA,CACN4C,KAAM,OACNC,YAAa,yBAAyB7C,gBACtCuC,uBAASD,SAAT,CAAuC,CAAC,QAAS,SAAU,cACxDe,UAAUuxD,aAAa50D,GAAM0zD,WAEpC,aAAe1zD,IAAA,CACX4C,KAAM,QACNC,YAAa,4BAA4B7C,8DAE7C,iBAAmBA,IAAA,CACf4C,KAAM,OACNC,YAAa,iBAAiB7C,+EAC9BuC,uBAASD,SAAT,CAAyC,CAAC,SAAU,aACjDe,UAAUwxD,uBAAuB70D,GAAQ,cAAW,KAE3D,kBAAoBA,IAAA,CAChB4C,KAAM,SACNC,YAAa,yBAAyB7C,4CACtC2B,MAAOm0D,+BACPxyD,QAAS,IAEb,iBAAmBtD,IAAA,CACf4C,KAAM,OACNC,YAAa,eAAe7C,6EAC5BuC,uBAASD,SAAT,CAAyC,CAAC,WAAY,UACtDgB,QFhCoD,aEkCxD,eAAiBtD,IAAA,CACb4C,KAAM,SACNC,YAAa,qBAAqB7C,0BAClC2B,MAAOmC,WACPR,QAAA,KAEJ,gBAAkBtD,IAAA,CACd4C,KAAM,OACNC,YAAa,cAAc7C,4EAC3BuC,uBAASD,SAAT,CAAwC,CAAC,iBAAkB,kBAAmB,sBAElF,YAActC,IAAA,CACV4C,KAAM,OACNC,YAAa,gBAAgB7C,yGAC7BuC,uBAASD,SAAT,CAAoC,CAAC,OAAQ,WAI/Ck4D,iBAAsD,CACxDC,MAAO,aACPC,OAAQ,YACRC,OAAQ,mBAGNC,gCAEF,CACAh+C,UAAYi+C,IAAA,CAAYj4D,KAAM,QAASC,YAAa,sBAAsBg4D,kCAC1E99C,aAAe89C,IAAA,CACXj4D,KAAM,QACNC,YAAa,yBAAyBg4D,kCAE1C79C,eAAiB69C,IAAA,CACbj4D,KAAM,SACNC,YAAa,0BAA0Bg4D,gCACvCl5D,MAAOgC,iBAeTm3D,cAAA,CAAiB96D,EAAwB2rC,IAC3C,YAAY3rC,KAAQ2rC,IAElBovB,eAAA,KACF,MAAMC,EAAoB/3D,OAAOC,KAAKq3D,wBAEhCU,EAAYtG,GACdqG,EAAkBn6D,OAAQ8qC,GAASA,IAASgpB,GAAYhpB,EAAKjsB,WAAW,GAAGi1C,OAE/E,MAAO,IAEA5xD,QAAQu3D,sBAAsBp+C,QAAS+c,GAC1B,wCAAZA,EAAK71B,GAA+C,CAAC61B,KAtB5D,CAACghC,YAAaC,aAAcC,cAAwBj+C,QAAS68C,GACzD91D,OAAOC,KAAK03D,iCAAmEz3D,IAC3EwoC,IAAA,CACGvoC,GAAI,kBAAkB21D,WAAcptB,OACjCivB,gCAAgCjvB,GAAM6uB,iBAAiBzB,SAkBsB,CAAC9/B,OAEtFo6B,kBAAkBn3C,QAASlc,GAC1B40D,aAAa50D,GACR2zD,MAAMz3C,QAAQ++C,GACd93D,IAAKwoC,IAAA,CAAyBvoC,GAAI03D,cAAc96D,EAAM2rC,MAAU4uB,uBAAuB5uB,GAAM3rC,SAkBjGk7D,oCAA+DH,iBAO/DI,8BAA2DD,qBAAqB/3D,IAAA,EAAOC,QAASA,GC3XhGg4D,yBAA+E,CACxF,CAAC,WAAY,CAAC,MAfI,gBAemB,IACrC,CAAE,aAbsB,UAaa,YAAa,CAAC,UAAW,CrEyBlC,mBqEnBnBC,gBAA2C,CAAC,MAtBnC,gBCKhBC,sBACFx7D,IAEwB,IAApBA,GAAQojB,QACD,GAOPpjB,GAAQ0kC,OAAS,CACb,SACA,CAAC,MAAO,SACR,CAAC,EACD,KACA,CAAC,EACD,CAAC,MAAO,iBACR,CAAC,EACD,QACA,CAAC,KACE42B,0BASFG,mBACTz7D,IAKA,MAAMk9C,EAAatD,0BAA0BN,uBAAuBt5C,GAAQy5C,MAAMwF,cAElF,MAAO,IACA9H,iBACHp2C,OAAQy0D,sBACR30C,QAAS,EACTtF,OAAQ,IACD47B,iBAAiB57B,OACpB,aAAcigD,sBAAsBx7D,GAAQ8R,UACxCorC,GAAc,CAAE,cAAeA,IAEvCh8B,MAAO,IACAi2B,iBAAiBj2B,SClD1Bw6C,YAAc,CAChB36D,OAAQy0D,sBACR30C,QAAS,IAGP86C,iBAAwE,CAC1E,WAAY,SAMHC,mBAAsBnF,IAAA,IAC5BiF,YACHx7D,KAAM,OACNqb,OAAQogD,iBACRz6C,MAAO,CACH,aAAcu2C,2BAA2BhB,GACzC,aAAc,UAOToF,gBAAmBpF,IAAA,IACzBiF,YACHx7D,KAAM,OACNqb,OAAQogD,iBACRz6C,MAAO,CACH,aAAcs2C,wBAAwBf,GACtC,aAAc,WClCTqF,iBAA8DpG,kBAAkB,CACzF,aAAc,YAGZqG,oBAAsBpG,uBAAuB,qBAAsB,GAK5DqG,mBAAkE,IACxED,oBAEHn7C,QAAS,KACTrF,OAAQ,IACDwgD,oBAAoBxgD,OAEvB,yBAAyB,ICJ3B0gD,aAAA,CAAgBxF,EAAuCyF,IACzD7vD,WAAWi5B,oBTgOF,CAAuBmxB,GAChCF,YAAYM,oBAAqBJ,GSjOD0F,CAAoB1F,GAAYyF,GAE9DE,sBAAmE,CACrEl8D,KAAM,OACNqb,OAAQ,CACJ,YAAa,QACb,WAAY,QACZ,gBAAiB,CAAC,MAAO,WAQpB8gD,uBAAA,CACT5F,EACAyF,EVHkD,OUGhC,IAEfE,sBACHr7D,OAAQ00D,wBACRv0C,MAAO,CACH,aTVgC,USWhC,aAAc+6C,aAAaxF,EAAWyF,MAOjCI,oBAAuB7F,IAAA,IAC7B2F,sBACHr7D,OAAQ00D,wBACRv0C,MAAO,CACH,aT5BmC,US6BnC,aAAc81C,uBAAuBP,MAQhC8F,aAAA,CACT9F,EACAx5C,EACAi/C,EVhCkD,OUgChC,IAEfE,sBACHr7D,OAAQy0D,sBACRt0C,MAAO,CACH,aAAcjE,GAAA,UACd,aAAcg/C,aAAaxF,EAAWyF,MAOjCM,cAAA,CACT/F,EACAgG,KAAA,IAEGL,sBACHr7D,OAAQy0D,sBACRt0C,MAAO,CACH,aAAcu7C,GAAA,UACd,aAAczF,uBAAuBP,MAOhCiG,gBAA+D,CACxEx8D,KAAM,SACNqb,OAAQ,CACJ,mBAAoB,OACpB,aAAc,oBAEd,cAAe,MC7FVohD,sBAAmEjH,kBAC5E,CAAE,aAAc,WAChB,CAAE,WAAY,UAMLkH,qBAAoEjH,uBAC7E,iBACA,GC+CSkH,sBAAiD,CAC1D,QACA,CAAC,MAAO,oBACR,UXiSoC,uBAKG,0BY5U9BC,yBAA2B,CACpC5Z,qBAAqBC,MACrBD,qBAAqBE,SACrBF,qBAAqBG,OASZ0Z,mBAAA,CAAsB/8B,EAAwC,CAAC,KAAA,CACxEmjB,MAAOnjB,EAASkjB,qBAAqBC,QAAA,UACrCC,SAAUpjB,EAASkjB,qBAAqBE,WAAA,UACxCC,MAAOrjB,EAASkjB,qBAAqBG,QAAA,YAO5B2Z,sBAAA,EACT7Z,QACAC,WACAC,QACAC,aACAxhD,cAC+G,CAC/G,QACA,CAAC,MAAO,oBACR,QACAqhD,EACA,WACAC,EACA,QACAC,KACIC,EAAa,CAAC,aAAcA,GAAc,GAC9CxhD,GAMSm7D,0BAA4BD,sBAAsB,CAC3D7Z,MZ6QmC,UY5QnCC,SZoQgC,UYnQhCC,MZ+P6B,UY9P7BC,WAAY,UACZxhD,SZ6Q+B,YY9PtBo7D,yBAAsE,CAC/Eh9D,KAAM,OACNa,OD5CyD,CACzD,KACA,CAAC,MAAO,oBACR,CAAC,UAAW,CAAC,UAAW,gBC0CxBwa,OAAQ,CAAE,YAAa,SACvB2F,MAAO,CACH,aAAc27C,sBACd,iBAAkB,CAAC,IAAK,KAK1BM,qBAAA,CACFC,EACA7hD,EACA2F,EAA2C,CAAC,KAAA,CAE5CngB,OAAQ,CAAC,MAAO,CAAC,MAAOq8D,GAAiB5H,uBACzCt1D,KAAM,SACN2gB,QAAS,EACTtF,OAAQ,CACJ,mBAAoB,QACpB,sBAAsB,EACtB,yBAAyB,EACzB,aAAc,CAAC,MAAO6hD,MACnB7hD,GAEP2F,UAMSm8C,wBAA0BF,qBACnC,YACA,CACI,cAAe,cACf,cAAe,cAEf,aAAc,CAAC,MAAO,SACtB,YAAa,C5E5GQ,a4E6GrB,cAAe,CAAC,KAAK,KACrB,YAAa,IAEjB,CACI,aAAcF,0BACd,kBAAmB,UACnB,kBAAmB,IAOdK,0BAA4BH,qBAAqB,cAAe,CACzE,cAAe,iBC5INI,iBAA8D7H,kBAAkB,CACzF,aAAc,UACd,eAAgB,KCFP8H,qCAAkF9H,kBAAkB,CAC7G,adYoC,YcN3B+H,iCAA8E/H,kBACvF,CAAE,adDqC,WcEvC,CAAE,WAAY,UCQZgI,oBAA+C,CAAC,MAAO,oBAOhDC,8BAAiC51C,IAC1C,MAAMtO,EAAWmkD,GAAwBhI,gBAAgBgI,EAAa71C,GAEtE,MAAO,CACH7nB,KAAM,SACNqb,OAAQ,CACJ,aAAc,CACV,OACAi6C,sBACA/7C,ENmE+B,gCMlE/BA,ENsEiC,mCMpErC,mBAAoB,QACpB,0BAA2B,WAC3B,0BAA2B,WAC3B,kBAAmB,CAAC,OAAQ+7C,sBAAuB,EAAG,GACtD,gBAAiB,OACjB,wBAAyB,CAAC,GAAI,EAAG,EAAG,IACpC,YAAa,C/EuBM,e+EtBnB,YAAa,GACb,eAAgB,EAChB,eAAgB,OAChB,mBAAoB,IACpB,aAAc,CACV,SACA,CAAC,MAAO,qBACR,CACI,YAAa,CAAC,UAAW,C/ElChB,c+EmCT,aAAc,CAAC,OAAQA,sBAAuB,QftBxB,YewB1B,CAAC,SAAU,OAAQ,CAAC,MAAO,sBAC3B,CAAE,afzBwB,We0B1B,CAAC,OAAQkI,oBAAqB,KAAM,IACpC,CAAC,EACD,CACI,QACA,CACI,OACAA,oBACAV,sBAAsB,CAClB7Z,MAAO1pC,EAjDG,iBAkDV2pC,SAAU3pC,EAtDG,oBAuDb4pC,MAAO5pC,EA3DG,iBA4DV3X,SAAU2X,EAhEA,mBAkEd,KAGR,CAAC,EACD,CAAC,OAAQikD,oBAAqB,CAAC,SAAU,KAAM,CAAC,MAAO,qBAAsB,IAC7E,CACI,YAAa,CAAC,UAAW,C/E1DhB,c+E2DT,aAAcT,6BAI1B/7C,MAAO,CACH,iBAAkB,CAAC,GAAG,IACtB,iBAAkB,CAAC,GAAG,OASrB28C,yBAAwEF,gCC/D/EG,uBAAkD,CACpD,SACA,CAAC,WAAY,CAAC,MAAOjnB,OAAQ,IAC7B,CAAC,EACD,CAAC,OAAQ,CAAC,MAAO,CAAC,MAAOA,OAAQ0kB,iBAAkB,KAAM,IACzD,CAAC,KACED,0BASMyC,gBAAmB/9D,IAC5B,MAAMk8D,EAAkBv6D,YACpB3B,EAAOg+D,WAAW33B,WAAW2lB,WAC7B+J,2CjBb8C,KiBiB5C7Y,EAAatD,0BAA0BN,uBAAuBt5C,EAAOg+D,WAAWvkB,MAAMwF,cAC5F,MAAO,CACH/+C,KAAM,SACNghB,MAAO,IACA81B,iBACH,aAAc,UACd,iBAAkBD,cAClB,wBAAyB,YAE7Bx7B,OAAQ,IACDu7B,kBACH,YAAazqC,WACTi5B,qBhB0LoB1X,EgBzLA5tB,EAAOg+D,WAAWpwC,KhByLQo+B,EgBzLFhsD,EAAOg+D,WAAWhS,WhB0L1EmK,UAAUW,mBAAoBG,cAAcrpC,EAAMo+B,KgBzLtCkQ,MAEAhf,GAAc,CAAE,cAAeA,GACnC,aAAc,CAAC,MbmFO,oBalFtB,YAAa,ChFnDI,agFqDjB,YAAa7wC,WACTi5B,oBACA4xB,yBAAyBl3D,EAAOg+D,WAAWpwC,KAAM5tB,EAAOg+D,WAAWhS,YACnEkQ,GAEJ,cAAe/E,sBACf,sBAAsB,EACtB,kBAAmB,CAAC,MAAO,CAAC,IAAK,CAAC,MAAO,SAAU,QhB2KlD,IAAuBvpC,EAA0Bo+B,GgBjKjDiS,eAAA,CACTl6B,EACAjyB,KAEA,MAAMkyB,UAAEA,EAAAC,UAAWA,GAAcH,yBAAyBC,GAC1D,MAAO,CACH7jC,KAAM,SACNghB,MAAO,IACAg2B,iBACH,aAAclT,EACd,kBjB1DqC,IiB2DrC,kBAAmBC,KAChBK,qBAAqBxyB,IAE5ByJ,OAAQ,IACD07B,kBACH,cAAiC,IAAnBnlC,GAAOsR,QAAoB,GAAKwhB,eAAe9yB,GAAO4yB,MAAOo5B,wBAC3E,cAAe,MACf,cAAe,CAAC,EAAG,OAChB15B,sBAAsBtyB,MCrD/BosD,eAAA,CAAkB/iD,EAA8ByqB,IAC7CzqB,GAAayqB,IAIdzqB,EAASyE,WAAW,UAAYzE,EAASyE,WAAW,aAC7C,GAAGgmB,KAAiBzqB,IAJpBA,EAaTgjD,gBAAA,CACFtd,EACAjb,IAEKib,GAAejb,EAIbziC,OAAOqP,YACVrP,OAAOsD,QAAQo6C,GAAYx9C,IAAA,EAAMgR,EAAK2H,KAAW,CAC7C3H,EACA2H,GAAOb,SAAW,IAAKa,EAAOb,SAAU+iD,eAAeliD,EAAMb,SAAUyqB,IAAmB5pB,KANvF6kC,EAqBFud,mBAAA,CACTp+D,EAA8B,CAAC,EAC/B4lC,EACA7d,EACAgc,EAAuB,QACvBs6B,EAAoCtB,wBAIpC,MAAMuB,EAAA,CACF9zD,EACAq5B,IACqBD,oBAAoB5jC,EAAO6pC,kBAAmBr/B,EAAMq5B,GACvE06B,EAAoBD,EAAa,YAAahkC,iBAAiBI,aAC/D8jC,EAA2BF,EAAa,mBAAoBhkC,iBAAiBI,aAC7E+jC,EAAqBH,EAAa,aAAS,GAE3CI,EAAe1+D,EAAO8P,OACtB6uD,EAAsBD,GAAcE,SACpCnC,EAAaz8D,EAAOuN,MACpB0P,EAAew/C,EAAattD,cAAcstD,QAAc,EACxDzD,EAAiC,CAAEp0C,MAAO5kB,EAAO4kB,MAAO8xC,YAAa12D,EAAO02D,aAC5EwF,EAAkBv6D,YACpB3B,EAAOqmC,WAAWw4B,mBAClB5I,2ClB3F8C,KkB8F5C6I,GV3EuBrI,EU2EkBuC,EV3ElB,IAC1B0C,YACHx7D,KAAM,SACNqb,OAAQ,CACJ,sBAAsB,EACtB,aAVkC,oBAWlC,0BAA2B,MAC3B,cAAe,CAAC,MAAO,2BACvB,YAAao8C,wBAAwBlB,MARhC,IAAoBA,EU6E7B,MAAMsI,EAAiB/+D,EAAO4+D,SAExBI,EAAsBn5B,WACxB,IACOk4B,gBAAgB/9D,GACnBmb,SAAU+iD,eAAe,2BAA4Bt4B,IAEzD84B,GAAcV,WAAWgB,qBAEvBC,EjBqBG,CACTC,IAEA,MAAMjuD,EAAuC,iBAAvBiuD,EAAkCA,EAAqB,SACvEC,EAAUC,GACD,WAAXnuD,GAAuBmuD,EArBM,GAGR,EAoBzB,MAAO,CACHD,EAAOluD,EAAO2O,WAAW,WACzBu/C,EAAOluD,EAAOsO,SAAS,SACvB4/C,EAAOluD,EAAO2O,WAAW,QACzBu/C,EAAOluD,EAAOsO,SAAS,YiBhCF8/C,CAAwBL,EAAoBzjD,SAAS,gBACxE+jD,EAA+EzkD,IAAA,IAC9EA,EACHU,OAAQ,IAAKV,EAAKU,OAAQ,eAAgB0jD,KAQxCM,EAAA,CACFr/D,EACA2a,EACApY,KAAA,IAEGs2D,qBAAqB74D,EAAM2a,EAAMkkD,IAAiB7+D,GAAO84D,EAAgBv2D,GAASw2D,MACrF99C,SAAU+iD,eAAez7D,GAAS0Y,UAAY09C,mBAAmB34D,EAAM6+D,IAAiB7+D,IAAQ0lC,KAG9F45B,EAAA,CACFt/D,EACA2a,EACAM,KAAA,IAEGg+C,uBAAuBj5D,EAAMo/D,EAAqBzkD,GAAOkkD,IAAiB7+D,IAC7Eib,SAAU+iD,eAAe/iD,EAAUyqB,KAOjC65B,EAAoBt8D,OAAOqP,YAC7B6gD,sBAAsBhwD,IAAKnD,IACvB,MAAMw/D,EAAgBX,IAAiB7+D,GACjCy/D,EfkQL,CAAsDz/D,GAC/D65D,eAAe75D,EAAM,QenQO0/D,CAAmB1/D,GACjC2/D,EfwQL,CAAwD3/D,GACjE65D,eAAe75D,EAAM,UezQS4/D,CAAqB5/D,GACrC6/D,EAAiBpB,IAAsBz+D,GAQ7C,GAAI+0D,UAAU/0D,GAAO,CACjB,MAAM8/D,EAAc9F,mBAAmBh6D,GACvC,MAAO,CACHA,EACA,CACK8/D,CAAAA,GAAcn6B,WACXy5B,EAAqB1F,YAAY15D,EAAMw/D,IACvCK,IAAiBC,OAElB7B,gBAAgB4B,GAAgBlf,WAAYjb,IAG3D,CAEA,MAAMq6B,EAAcP,GfwCnB,EACTx/D,EACAF,KAEA,GAAKA,EAAOy5C,MAASmb,oBAAoB10D,EAAM,QAE/C,OAAOi5D,uBACHj5D,EACA,CACIa,OAAQy0D,sBACRt1D,KAAM,SACNqb,OAAQ,CACJ,mBAAoB,cAGpB,sBAAsB,EACtB,0BAA2B,aAGnCvb,Ie3DyCkgE,CAAchgE,EAAMw/D,GACzD,MAAO,CACHx/D,EACA,CACKy/D,CAAAA,GAAc95B,WACX,IACOwzB,YAAYn5D,EAAMw/D,EAAe1G,GACpC79C,SAAU+iD,eAAerF,mBAAmB34D,EAAMw/D,GAAgB95B,IAEtEm6B,IAAiBJ,OAEjBM,GAAe,CAAAJ,CACdA,GAAgBh6B,WACb,IAAKo6B,EAAa9kD,SAAU+iD,eAAe,oBAAqBt4B,IAChEm6B,IAAiBF,QAGtB1B,gBAAgB4B,GAAgBlf,WAAYjb,QAM/D,MAAO,CACHu6B,UAAW,CACPzD,gBAAiB72B,WACb,IAAK62B,gBAAiBvhD,SAAUojD,GAChCG,GAAcyB,WAAWzD,iBAE7B0D,UAAWv6B,WACP,IACO22B,cAAcxD,EAAgByD,GACjCthD,SAAU+iD,eAAe,8BAA+Bt4B,IAE5D84B,GAAcyB,WAAWC,WAE7B7D,aAAc12B,WACV,IACO02B,aAAavD,EAAgB/7C,EAAci/C,GAC9C/gD,SAAU+iD,eAAe,YAAat4B,IAE1C84B,GAAcyB,WAAW5D,cAE7BD,oBAAqBz2B,WACjB,IAAKy2B,oBAAoBtD,GAAiB79C,SAAU+iD,eAAe,eAAgBt4B,IACnF84B,GAAcyB,WAAW7D,qBAE7BD,uBAAwBx2B,WACpB,IACOw2B,uBAAuBrD,EAAgBkD,GAC1C/gD,SAAU+iD,eAAe,sBAAuBt4B,IAEpD84B,GAAcyB,WAAW9D,2BAE1B8B,gBAAgBO,GAAcyB,WAAWtf,WAAYjb,IAE5Do4B,UAAW,CACPgB,sBACAqB,mBAAoBx6B,WAChB,IACOo4B,eAAel6B,EAAW/jC,EAAOg+D,WAAWlsD,OAC/CqJ,SAAU+iD,eAAe,sBAAuBt4B,IAEpD84B,GAAcV,WAAWqC,uBAE1BlC,gBAAgBO,GAAcV,WAAWnd,WAAYjb,IAE5D06B,cAAe,CACXC,wBAAyB16B,WACrB,IACO41B,mBAAmBz7D,EAAOsgE,eAC7BnlD,SAAU+iD,eAAe,sBAAuBt4B,IAEpD84B,GAAc4B,eAAeC,4BAE9BpC,gBAAgBO,GAAc4B,eAAezf,WAAYjb,IAEhEg5B,SAAU,IACHa,EACHhL,QAAS,CACL+L,uBAAwB36B,WACpB25B,EAAqB,UAAWnC,wBAAyB,2BACzDsB,GAAqBlK,SAAS+L,wBAElCC,yBAA0B56B,WACtB25B,EAAqB,UAAWlC,0BAA2B,2BAC3DqB,GAAqBlK,SAASgM,0BAElCC,4BAA6B76B,WACzB05B,EAAmB,WL5MFj8B,EK4MkC+6B,EL5MlC,CACjCn+D,KAAM,OACNqb,OAAQ,CAAE,WAAY,SACtB2F,MAAO,CAAE,aAAc87C,sBAAsB,IAAK15B,EAAQxhC,SZwQxB,eiB/DiD,CAC/DqZ,SAAU,4BAEdwjD,GAAqBlK,SAASiM,6BAElCC,wBAAyB96B,WACrB05B,EAAmB,UAAWrC,0BAC9ByB,GAAqBlK,SAASkM,4BAE/BxC,gBAAgBQ,GAAqBlK,SAAS5T,WAAYjb,IAEjE2uB,MAAO,CACHqM,eAAgB/6B,WACZ05B,EAAmB,QAASzD,kBAC5B6C,GAAqBpK,OAAOqM,gBAEhCC,iBAAkBh7B,WACd25B,EAAqB,QAASxD,mBAAoB,0BAClD2C,GAAqBpK,OAAOsM,qBAE7B1C,gBAAgBQ,GAAqBpK,OAAO1T,WAAYjb,IAE/D4uB,SAAU,CACNsM,qBAAsBj7B,WAClB05B,EAAmB,WAAY5C,uBAC/BgC,GAAqBnK,UAAUsM,sBAEnCC,oBAAqBl7B,WACjB25B,EAAqB,WAAY5C,qBAAsB,2BACvD+B,GAAqBnK,UAAUuM,wBAEhC5C,gBAAgBQ,GAAqBnK,UAAU3T,WAAYjb,IAElE8uB,OAAQ,CACJsM,gBAAiBn7B,WACb05B,EAAmB,SAAUhC,kBAC7BoB,GAAqBjK,QAAQsM,iBAIjCC,kBAAmBp7B,WACf25B,EAAqB,SAAU1F,iBAAiB,SAAU/xC,GAAgB,oBAC1E42C,GAAqBjK,QAAQuM,sBAE9B9C,gBAAgBQ,GAAqBjK,QAAQ7T,WAAYjb,IAEhE+uB,kBAAmB,CAGf6I,qCAAsC33B,WAClC05B,EAAmB,oBAAqB/B,qCAAsC,CAC1EriD,SAAU,uCACV89C,KAAM,YAEV0F,GAAqBhK,mBAAmB6I,sCAE5C0D,qCAAsCr7B,WAClC05B,EAAmB,oBAAqB9B,kCACxCkB,GAAqBhK,mBAAmBuM,sCAI5CC,6BAA8Bt7B,WAC1B25B,EACI,oBACA1F,iBAAiB,oBAAqB/xC,GACtC,0BAEJ42C,GAAqBhK,mBAAmBwM,iCAEzChD,gBAAgBQ,GAAqBhK,mBAAmB9T,WAAYjb,KAG/Ew7B,iBAAkB,CACdC,qBAAsBx7B,WAClB,IAAKg2B,gBAAgB7C,GAAiB79C,SAAUqjD,GAChDE,GAAc0C,kBAAkBC,sBAEpCC,wBAAyBz7B,WACrB,IACO+1B,mBAAmB5C,GACtB79C,SAAU+iD,eAAe,uBAAwBt4B,IAErD84B,GAAc0C,kBAAkBE,4BAEjCnD,gBAAgBO,GAAc0C,kBAAkBvgB,WAAYjb,IAEnE27B,kBAAmB,CACfC,4BAA6B37B,WACzB,IACOi5B,EACH3jD,SAAU+iD,eAAe,uBAAwBt4B,WAC3B,IAAlB7d,GAA+B,CAC/BxM,OAAQ,IACDujD,EAAsBvjD,OACzB,aAAcgqB,aAAA,oBAAyCxd,MAInE22C,GAAc6C,mBAAmBC,gCAElCrD,gBAAgBO,GAAc6C,mBAAmB1gB,WAAYjb,IAEpE67B,eAAgB,CACZC,yBAA0B77B,WACtB,SAC0B,IAAlB9d,EACE81C,yBACAF,8BAA8B51C,MAChC02C,GAAsB,CAAEtjD,SAAUsjD,IAE1CC,GAAc+C,gBAAgBC,6BAE/BvD,gBAAgBO,GAAc+C,gBAAgB5gB,WAAYjb,IAEjE+7B,iBAAkB,CACb9J,CAAAA,2BAA4BhyB,WACzB,IACOmyB,sBACCjwC,EACA/nB,EAAO2hE,iBACPzO,6BAA6BlzD,EAAO2hE,iBAAkB59B,IAI1D5oB,SAAU+iD,eAAe,sBAAuBt4B,IAEpD84B,GAAciD,kBAAmB,yBAElCxD,gBAAgBO,GAAciD,kBAAkB9gB,WAAYjb,KL7U9D,IAAwBtC,GKuWxBs+B,qBAAoDxD,qBC1a3DyD,OAAUC,GACZpoD,sBAAwBQ,SAAS4nD,UAAe,EAKvCC,wBAA0BF,OAAA,IAAarlD,qcAKvCwlD,oBAAuBz0D,GAChCs0D,OAAA,KACI,MAAMI,EAAMzlD,qcAGZ,OAFAylD,EAAIllD,cAAc,YAAYC,aAAa,OAAQzP,GACnD00D,EAAIllD,cAAc,SAASC,aAAa,OAAQzP,GACzC00D,IAQFC,0BAAyD,CAClEp9C,WAAA,EACAq9C,SAAU,CACN,CAAC,GAAI,IACL,CAAC,IAAK,MAEVC,SAAU,CAAC,CAAC,GAAI,KAChBC,QAAS,CAAC,GAAI,GAAI,IAAK,KASdC,kCAAiE,CAC1Ex9C,WAAA,EACAq9C,SAAU,CAAC,CAAC,GAAI,MAChBC,SAAU,CAAC,CAAC,GAAI,KAChBC,QAAS,CAAC,GAAI,GAAI,IAAK,KAQdE,yBAA4Bh1D,GACrCs0D,OAAA,KACI,MAAMI,EAAMzlD,oWAEZ,OADAylD,EAAIllD,cAAc,YAAYC,aAAa,OAAQzP,GAC5C00D,IAOTO,aAAA,CAAgBC,EAAc73B,EAAc83B,IAC9C,iHAAiH,GAAKA,EAAY,YAAYD,cAAiB73B,oBAAuB83B,aAEpLC,eAAiBH,aAAa,OAPnB,UAOqC,GAEhDI,sBAA4D,CAC9DC,UAAWF,eACXG,WAAYN,aAAa,UAXZ,UAWiC,GAC9CrP,icAGE4P,wBAAgE,CAClErO,wRAEAC,kBAAmBgO,gBAGjBK,QAAWf,GAAkCJ,OAAA,IAAarlD,SAASylD,IAM5DgB,oBAAuB/iE,GAChC8iE,QAAQD,wBAAwB7iE,IAQvBgjE,kBAAqBT,GAA+CO,QAAQJ,sBAAsBH,IAKlGU,WAAc51D,GACvBs0D,OAAA,KACI,MAAMI,EAAMzlD,kmDACNgjC,EAAOyiB,EAAIllD,cAAc,SAG/B,OAFAyiC,GAAMxiC,aAAa,YAAa,YAChCwiC,GAAMxiC,aAAa,OAAQzP,GACpB00D,IAITmB,eAAgE,CAClEzI,ybACAC,YAAQ,EACRC,+XC5GEwI,kBAAA,CAAqB74B,EAAyB/hC,KAChD,MAAM66D,EAAS94B,EAAY/hC,EAAQ,IAAM+hC,EAAY/hC,GAC/C86D,EAAQ/4B,EAAY/hC,EAAQ,IAAM+hC,EAAY/hC,GACpD,OAAI66D,IAAWC,EAAc,GAErBloE,QAAeioE,EAAQC,GAAS,KAAO,KAG7CC,kBAAA,CACFC,EACAC,EACAl5B,EACAm5B,KAEA,MAAMrgE,EAAK9F,aAEX,MAAO,CACH0C,KAAM,UACNoD,KACAqT,SAAU,CAAEzW,KAAM,QAASsqC,YAAaA,EAAYk5B,EAAQE,kBAC5D5zD,WAAY,CACR1M,KACAugE,gBAAiBJ,EAASK,gBAC1BC,cAAeL,EAAQI,gBACvBhyD,MAAO,GAAG2xD,EAASK,qBAAqCJ,EAAQI,kBAChE/kE,QAASskE,kBAAkB74B,EAAak5B,EAAQE,iBAChDI,2BAA4BP,EAASG,gBACrCK,yBAA0BP,EAAQE,gBAClCM,WAAYP,EAAWO,WACvBC,WAAYR,EAAWl7D,SAc7B27D,iBAAoBC,IACtB,MAAMC,EAAYD,EAAMr0D,WAAW4uD,SAASrkC,QAC5C,IAAK+pC,GAAWlgE,OAAQ,MAAO,GAG/B,MAAMmgE,EAAU,IAAID,GAAWE,KAAA,CAAM7oB,EAAMC,IAAUD,EAAKioB,gBAAkBhoB,EAAMgoB,iBAC5Ep5B,EAAc65B,EAAM1tD,SAAS6zB,YAEnC,OAAO+5B,EAAQjgE,MAAM,GAAG8X,QAAA,CAASsnD,EAASj7D,IACjC+hC,EAAYk5B,EAAQE,iBAElB,CAACJ,kBAAkBe,EAAQ97D,GAAQi7D,EAASl5B,EAAa65B,EAAMr0D,aAFpB,KAe7Cy0D,0BAA6BC,IAAA,CACtCxkE,KAAM,oBACNmnB,SAAUq9C,EAAOr9C,SAASjL,QAAQgoD,oBC7EhCO,UAAA,CAAaC,EAA4B5kE,KAC3C,MAAM6kE,EAAa7kE,GAAQsgE,eAAe7mB,KAC1C,GAAIorB,GAAYn+C,QAAS,CACrB,MAAMA,EAAUm+C,EAAWn+C,QAC3B,OAAQA,EAAQo+C,SACZ,IAAK,gBACD,GAAIF,EAAa50D,WAAW+0D,wBAAwBC,cAChD,OAAOt+C,EAAQnkB,MAAMqiE,EAAa50D,WAAW+0D,wBAAwBC,eAEzE,MACJ,IAAK,SACD,OAAOt+C,EAAQ09B,GAAGwgB,GAE9B,CAGA,OAAOprD,mBAAmB,SAGxByrD,YAAeL,IACjB,MAAM50D,EAAa40D,EAAa50D,WAChC,OAAOA,EAAWk1D,kBAAqBl1D,EAAWm1D,0BC/BhDC,OAAUC,GAA+CA,EAAa9vB,WAAWh0C,SAAS,OAqCnF+jE,6BACTD,IAEA,MAAM3gC,EAtCJ,CAAc2gC,GAChBD,OAAOC,GAAgB/nE,eAAe+nE,EAAa/sD,qBAAkB,EAqCvDitD,CAAWF,GACnBG,EApBJ,EAAeH,EAAmC3gC,IAC/C0gC,OAAOC,GAIL,eADWA,EAAaltD,kBAAoB,aApBjD,CAA0BusB,GACvBA,GAAOtgC,OAGRsgC,EAAMtgC,OAAS,EAER,QAEPsgC,EAAMtgC,OAAS,EAER,SAGJ,QAXI,YAmBwBqhE,CAAuB/gC,KAH/C,KAkBOghC,CAAYL,EAAc3gC,GACtCihC,EAbJ,CAAiBN,IACnB,MAAMO,EAAsBP,EAAa9vB,WAAWl0C,KAAM4W,GAA0B,QAAbA,GACvE,OAAO2tD,EVxBE,CAAkB3tD,IAC3B,OAAQA,GACJ,IAAK,WACD,MAAO,6BACX,IAAK,YACD,MAAO,8BACX,IAAK,cACD,MAAO,gCACX,IAAK,cACL,IAAK,eACD,MAAO,gCACX,IAAK,SACL,IAAK,kBACD,MAAO,2BACX,IAAK,sBACD,MAAO,wCACX,IAAK,OACD,MAAO,yBACX,IAAK,MACD,MAAO,wBACX,IAAK,OACD,MAAO,yBACX,IAAK,QACD,MAAO,0BACX,IAAK,WACD,MAAO,6BACX,QACI,OAAO,OUHc4tD,CAAeD,GAAuB,MAW/CE,CAAcT,GAClC,MAAO,IACAA,KACCG,GAAa,CAAEA,gBACfG,GAAe,CAAEA,kBACjBjhC,GAAS,CAAEA,WChBVqhC,mBAAsBC,IAC/B,MAAMC,EAAkBD,GAAUh2D,WAClC,OAAOi2D,GAAiBxrC,KAAKn5B,MAAQ2kE,GAAiBh+B,SAASC,sBAAmB,GAOzEg+B,mBAAwD,CACjEvL,MAAO,gBACPC,OAAQ,eACRC,OAAQ,kBAOCsL,sBAAA,CAAyBC,EAA8Br+C,IAChE6tC,gBAAgBsQ,mBAAmBE,GAAYr+C,GAetCs+C,iBAAA,CACTC,EACAC,EvBpBkE,auBsBlE,GAAkB,SAAdA,EAEJ,MAAqB,YAAdA,EAlBL,CAAaD,IACf,IAAIE,EAAU,GACd,IAAK,IAAIvgC,EAAOqgC,EAAYrgC,EAAO,EAAGA,EAAOx6B,KAAKg7D,OAAOxgC,EAAO,GAAK,IACjEugC,EAAU/tC,OAAOiuC,cAAc,IAAOzgC,EAAO,GAAK,IAAOugC,EAE7D,OAAOA,GAa0BG,CAAUL,GAAc7tC,OAAO6tC,IAmB9DM,WAAcC,IAChB,OAAI3iE,MAAMC,QAAQ0iE,GAjBU,CAC5B3mE,KAAM,UACNyW,SAAU,CACNzW,KAAM,QACNsqC,YAc8Bq8B,GAZlC72D,WAAY,CAAC,GAcc,UAAvB62D,EAAc3mE,KAXO,CACzBA,KAAM,UACNyW,SAFyB+d,EAYMmyC,EAT/B72D,WAAY,CAAC,KACT0kB,EAAMoW,MAAQ,CAAEA,KAAMpW,EAAMoW,OAUzB+7B,EAvBL,IASuBnyC,GAoChBoyC,mBAAA,CACT9I,EACAv7D,EACAslB,EACAg/C,KAAA,CAEA7mE,KAAM,oBAENmnB,SAAU22C,EAAU5hD,QAAA,CAASyqD,EAAep+D,KACxC,IAAKo+D,EAAe,MAAO,GAE3B,MAAMb,EAAqBY,WAAWC,GAChCT,EA3HR,EAAgB39D,EAAeu+D,IACvB,IAAVv+D,EAAc0xD,YAAc1xD,EAAQu+D,EAAc,EAAI5M,aAAeC,aA0H/C4M,CAAax+D,EAAOu1D,EAAU55D,QAC1Cd,EAAM0iE,EAAS1iE,IAAiB9F,aAChC0pE,EAAe5pE,eAAe0oE,EAASh2D,WAAWm3D,qBAAsBJ,GACxEK,EAAmC,IAClCpB,KAC0B,wBAAzBvjE,GAAS+lD,aAAyC,CAClD7xC,SAAU,CACNzW,KAAM,QAENsqC,YAAa9sC,YAAYsoE,EAAU,CAAEqB,cAAe,0BAG5D/jE,KACA0M,WAAY,IACLg2D,EAASh2D,WACZ1M,KACAmF,QACA29D,eACIc,GAAgB,CAAEA,gBACtBpgB,OAAQqf,sBAAsBC,EAAWr+C,MAErC,WAAAq+C,GAA8B,CAAEkB,iBAAkB7+D,KAIxDi8B,EAAQD,aAAa2iC,EAAiB3kE,GAASqP,OAAO4yB,MAAOqhC,oBAC/DrhC,IAAO0iC,EAAgBp3D,WAAW00B,MAAQA,GAE9C,MAAMoiB,EAAS5C,cAAckjB,EAAiB3kE,GAASg3C,MAAM/yB,QAAUzO,GACnEwsC,aAAazmD,kBAAkBia,KAEnC,GAAI6uC,EACAsgB,EAAgBp3D,WAAW82C,OAASA,OACjC,GAAI,WAAAsf,IAA+B3jE,GAASg3C,MAAMmhB,QAAQl1C,OAAOA,MAAO,CAC3E,MAAM5T,EAAQu0D,iBAAiB59D,EAAOhG,GAAS8kE,eAC3Cz1D,IAAOs1D,EAAgBp3D,WAAWq2D,iBAAmBv0D,EAC7D,CACA,MAAO,CAACs1D,OEhIHI,uBAAA,CAIT9C,EACA+C,EACAC,KAAA,CAKAxnE,KAAM,oBACNmnB,SAAUq9C,EAAOr9C,SAASjL,QAASioD,GArDjC,EAIFA,EACAoD,EACAC,IAKCrD,EAAMr0D,WAAW4uD,SAAS6I,IAAsBpkE,IAAKgiE,IAClD,MAAM/hE,EAAK+hE,EAAa/hE,IAAM9F,aAC9B,MAAO,CACH0C,KAAM,UACNoD,KACAqT,SAAU,CACNzW,KAAM,aACNsqC,YAAa65B,EAAM1tD,SAAS6zB,YAAYlmC,MACpC+gE,EAAazB,gBACbyB,EAAasC,cAAgB,IAGrC33D,WAAY,IACJ03D,EACEA,EAA2BrC,EAAchB,EAAMr0D,YAC/Cq1D,EACNnB,WAAYG,EAAMr0D,WAAWk0D,WAC7BC,WAAYE,EAAMr0D,WAAWvH,MAC7BnF,UAGN,GAsBFskE,CAAkCvD,EAAOoD,EAAaC,MChCjDG,+BAAA,CACTC,EACAjnE,IAEAA,EAAiBgC,KArBR,EACT6hE,EACAxlE,KAAA,IAEGA,EACHmoB,SAAUnoB,EAAkBmoB,SAAShkB,IAAKgkB,IAAA,IACnCA,EACHrX,WAAY,IACLqX,EAASrX,WACZk0D,WAAYQ,EAAOr9C,SAASA,EAASrX,WAAWm0D,YAAc,GAAGn0D,WAAWk0D,iBAY9D6D,CAAkCD,EAAqBjnE,EAAiByvB,gBCfrF03C,gBAAA,CAAmBtD,EAAwBuD,EAAgB,KACpE,MAAMC,EAA2B,aAAcxD,EAASA,EAAS,CAAExkE,KAAM,oBAAqBmnB,SAAU,CAACq9C,IACzG,MAAO,IACAwD,EACH7gD,SAAU6gD,EAAiB7gD,SAAShkB,IAAA,CAAKghE,EAAO57D,KAC5C,MAAMnF,EAAK+gE,EAAM/gE,IAAM9F,aACvB,MAAO,IACA6mE,EACH/gE,KACA0M,WAAY,IACLq0D,EAAMr0D,WACT1M,KACA4gE,WAAYz7D,IAAUw/D,EAAgB,WAAa,mBAmB1DE,wBAAA,CACTzD,EACArP,KAAA,CAEAn1D,KAAM,oBACNmnB,SAAUq9C,EAAOr9C,SAAShkB,IAAKghE,IAC3B,MAAM+D,EAAU/D,EAAMr0D,WAAWo4D,QAC3BC,EAAmBhE,EAAM1tD,SAAS6zB,YAClC89B,EAAmBhrE,eAAe8qE,EAAQG,sBAAuBlT,GAAcmT,MAC/ErwD,EApBR,CAAyBksD,IAC3B,MAAMoE,EAAkBpE,EAAMr0D,WAAW4uD,SAASnK,SAAW,GAC7D,MAAQ,CAAC,QAAS,WAAY,SAAmBpzD,KAAMqnE,GACnDD,EAAgBt2D,KAAMw2D,GAAYA,EAAQxwD,mBAAqBuwD,KAiBtCE,CAAsBvE,GACzC/gE,EAAK+gE,EAAM/gE,IAAM9F,aACvB,MAAO,CACH0C,KAAM,UACNoD,KACAqT,SAAU,CACNzW,KAAM,QACNsqC,YAAa69B,EAAiB58D,KAAK9M,MAAM0pE,EAAiBjkE,OAAS,KAEvE4L,WAAY,CACR1M,KACA6gE,WAAYE,EAAMr0D,WAAWvH,MAC7By7D,WAAYG,EAAMr0D,WAAWk0D,WAC7B2E,kBAAmBxrE,eAAe+qE,EAAQU,eAAgBzT,GAAc0T,aACpE5wD,GAAoB,CAAEA,uBACtBmwD,GAAoB,CAAEA,oBAC1BU,kBAAmB1rE,eAAe8qE,EAAQa,oBAAqB5T,GAAcmT,YC1CvFU,gBAAkD,CAGpDC,IAAK,CAAEC,KAAM,OACbC,IAAK,CAAED,KAAM,OACbE,IAAK,CAAEF,KAAM,OACbG,IAAK,CAAE9G,KAAM,SAAU2G,KAAM,OAE7BI,IAAK,CAAE/G,KAAM,cACbgH,IAAK,CAAEhH,KAAM,cACbiH,IAAK,CAAEjH,KAAM,cAEbkH,IAAK,CAAEC,SAxBW,YA6BhBC,sBAAmE,CACrEC,OAAQ,CAAC,EACTC,YAAa,CAAEX,KAAM,OACrBY,YAAa,CAAEvH,KAAM,SAAU2G,KAAM,QAY5Ba,6BAAmE,CAC5EpH,UAAW,2BACXC,WAAY,4BACZ3P,OAAQ,yBAYN+W,iBAAA,CAAoBC,EAA4Bf,IACzC,QAATA,EA/Da,EAgEP39D,KAAK9M,MAA4B,IAArBwrE,EAA6BrtE,eAhElC,GAiEPqtE,ECuHGC,cAAb,MAAaA,UAAsB1+C,2BAmD/B,mBAAa9lB,CAAOG,EAAsB/F,GAEtC,aADMod,oBAAoBrX,GACnB,IAAIqkE,EAAcrkE,EAAW/F,EACxC,CAEA,WAAAN,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,GA5CAH,KAAAyI,sBAAA,GA8CXi7B,oBAAoBlgC,EAAA,UACW,IAAvBxD,KAAKG,QAAQuN,QAAqB1N,KAAKG,OAASH,KAAKwpB,aAAaxpB,KAAKG,WAE/EujD,oBAAoBlgD,EAAKy5D,yBAAA,IAAgCj9D,KAAKwqE,yBAClE,CAIA,sBAAAA,GACI,MAAMC,UAAEA,GAAczqE,KAAK0qE,iBAAiB1qE,KAAKG,QAC7CjE,QAAQuuE,EAAWzqE,KAAK2qE,YAAYF,aAExClpD,sBACIkpD,EACAzqE,KAAK2qE,YAAYF,UACjBzqE,KAAK0mB,kBAAkB+jD,UACvBzqE,KAAKqG,aAETrG,KAAK2qE,YAAc,IAAK3qE,KAAK2qE,YAAaF,aAC9C,CAIA,YAAAG,CAAqBzqE,GACjB,OAAOA,GAAQuN,OAASy1B,eAAenjC,KAAKkG,UAChD,CAIA,gBAAAwkE,CAAyBvqE,GAQrB,M7B9NK,EACT0qE,EAAkC,CAAC,EACnC9kC,KAAA,IAEIziC,OAAOqP,YACP6gD,sBAAsBhwD,IAAKnD,GAAS,CAChCg1D,iBAAiBh1D,GACjBi1D,cAAcuV,EAAa9L,WAAW1+D,GAAO0lC,MAGrDu6B,UAAWhL,cAAcuV,EAAavK,UAAWv6B,GACjDo4B,UAAW7I,cAAcuV,EAAa1M,UAAWp4B,GACjD06B,cAAenL,cAAcuV,EAAapK,cAAe16B,GACzD7H,QAASo3B,cAAcuV,EAAa9L,UAAUrK,MAAO3uB,GACrD0kC,UAAWnV,cAAcuV,EAAa9L,UAAUnK,QAAS7uB,GACzD+kC,UAAWxV,cAAcuV,EAAa9L,UAAUpK,SAAU5uB,GAC1DglC,QAASzV,cAAcuV,EAAa9L,UAAUlK,OAAQ9uB,GACtD+uB,kBAAmBQ,cAAcuV,EAAa9L,UAAUjK,kBAAmB/uB,GAC3Ew7B,iBAAkBjM,cAAcuV,EAAatJ,iBAAkBx7B,GAC/D27B,kBAAmBpM,cAAcuV,EAAanJ,kBAAmB37B,GACjE67B,eAAgBtM,cAAcuV,EAAajJ,eAAgB77B,GAC3D+7B,iBAAkBxM,cAAcuV,EAAa/I,iBAAkB/7B,K6ByMpDilC,CAPQzM,mBACX,IAAKp+D,EAAQuN,MAAO1N,KAAK4qE,aAAazqE,IACtCH,KAAK+lC,cACL/lC,KAAKkoB,cACLloB,KAAKkG,UAAU8iC,oBACfk0B,mBAAmBl9D,KAAKkG,UAAUqoD,qBAELvuD,KAAK+lC,cAC1C,CAEA,uBAAAklC,CAAgCN,GAC5B,MAAMO,EAAexlC,aAAa,SAAU1lC,KAAKkoB,eAWjD,MAAO,IATyB5kB,OAAOqP,YACnC6gD,sBAAsBhwD,IAAKnD,IACvB,MAAMmU,EAAM6gD,iBAAiBh1D,GAC7B,MAAO,CACHmU,EACA,IAAI6b,wBAAwBrwB,KAAKqG,YAAa,GAAG6kE,KAAgB12D,IAAOm2D,EAAYn2D,IAAM,OAMlG8rD,UAAW,IAAIjwC,wBACXrwB,KAAKqG,YACL,GAAG6kE,cACHP,EAAYrK,WACZ,GAEJnC,UAAW,IAAI9tC,wBACXrwB,KAAKqG,YACL,GAAG6kE,cACHP,EAAYxM,WACZ,GAEJsM,UAAW,IAAIp6C,wBACXrwB,KAAKqG,YACL,GAAG6kE,cACHP,EAAYF,WACZ,GAEJvsC,QAAS,IAAI7N,wBACTrwB,KAAKqG,YACL,GAAG6kE,YACHP,EAAYzsC,SACZ,GAEJuiC,cAAe,IAAIpwC,wBACfrwB,KAAKqG,YACL,GAAG6kE,kBACHP,EAAYlK,eACZ,GAEJqK,UAAW,IAAIz6C,wBACXrwB,KAAKqG,YACL,GAAG6kE,cACHP,EAAYG,WACZ,GAEJC,QAAS,IAAI16C,wBACTrwB,KAAKqG,YACL,GAAG6kE,YACHP,EAAYI,SACZ,GAEJjW,kBAAmB,IAAIzkC,wBACnBrwB,KAAKqG,YACL,GAAG6kE,sBACHP,EAAY7V,mBACZ,GAEJyM,iBAAkB,IAAIlxC,wBAClBrwB,KAAKqG,YACL,GAAG6kE,qBACHP,EAAYpJ,kBACZ,GAEJG,kBAAmB,IAAIrxC,wBACnBrwB,KAAKqG,YACL,GAAG6kE,sBACHP,EAAYjJ,mBACZ,GAEJE,eAAgB,IAAIvxC,wBAChBrwB,KAAKqG,YACL,GAAG6kE,mBACHP,EAAY/I,gBACZ,GAEJE,iBAAkB,IAAIzxC,wBAClBrwB,KAAKqG,YACL,GAAG6kE,qBACHP,EAAY7I,kBACZ,GAGZ,CAKA,sBAAAh5C,CAAiC3oB,EAA8B0oB,GAGtDA,IACD7oB,KAAK+lC,cAAgBL,aAAa,SAAU1lC,KAAKkoB,gBAGrDloB,KAAK2qE,YAAc3qE,KAAK0qE,iBAAiBvqE,GACzC,MAAMgrE,EAAqDnrE,KAAKirE,wBAAwBjrE,KAAK2qE,aAC7FvoD,UACI9e,OAAOH,OAAOgoE,GAA0B5uD,QAASD,GAAWA,EAAOrb,aACnEjB,KAAKqG,aAGT,MAAMzD,EAAuC,CAAEqiB,WAAA,GAGzCmmD,EAA0B1lC,arBtWE,oBqBsWuC1lC,KAAKkoB,eACxEmjD,EAA8B3lC,anB5SG,+BmB4S2C1lC,KAAKkoB,eACjFojD,EAAgC5lC,anBzSG,iCmByS6C1lC,KAAKkoB,eACrFqjD,EAAsB7lC,ab/YE,gBa+YmC1lC,KAAKkoB,eAChEsjD,EAAsB9lC,ab5YE,gBa4YmC1lC,KAAKkoB,eAChEujD,EAAyB/lC,abzYE,mBayYsC1lC,KAAKkoB,eACtEwjD,EAAsBhmC,abtYE,gBasYmC1lC,KAAKkoB,eAGtEloB,KAAK2rE,sBAAsBP,EAAyBlJ,wBAAyBt/D,GAC7E5C,KAAK2rE,sBACDN,EACAlJ,oBAAoB,SACpBE,2BAEJriE,KAAK2rE,sBACDL,EACAnJ,oBAAoB,WACpBE,2BAIJ,IAAK,MAAOO,EAAMhpD,KAAYtW,OAAOsD,QAAQwjE,8BACzCpqE,KAAK2rE,sBACDjmC,aAAa9rB,EAAS5Z,KAAKkoB,eAC3Bm7C,kBAAkBT,GAClBhgE,GAGR,IAAK,MAAOvC,EAAMuZ,KAAYtW,OAAOsD,QAAQozD,8BACzCh6D,KAAK2rE,sBACDjmC,aAAa9rB,EAAS5Z,KAAKkoB,eAC3Bk7C,oBAAoB/iE,GACpBuC,GAgBR,OAXA5C,KAAK4rE,2BAAwB,EAC7B5rE,KAAK2rE,sBACDjmC,a3B/aoC,gC2B+aW1lC,KAAKkoB,eACpDw6C,yBAAyB1iE,KAAK6rE,eAAe7rE,KAAKG,QAAQmzD,QAC1D,IAAK1wD,KAAY6/D,oCAErBziE,KAAK2rE,sBAAsBJ,EAAqBjI,W5BvGrB,W4BuGsD1gE,GACjF5C,KAAK2rE,sBAAsBH,EAAqBlI,W5BxHvB,W4BwHsD1gE,GAC/E5C,KAAK2rE,sBAAsBF,EAAwBnI,W5BrHvB,W4BqHyD1gE,GACrF5C,KAAK2rE,sBAAsBD,EAAqBpI,W5B9GjB,W4B8GsD1gE,GAE9EuoE,CACX,CAKA,YAAA3hD,CAAuBrpB,GACnB,MAAMq1D,EAAeD,kBAAkBp1D,GACjC2rE,GAAuB5vE,QAAQ8D,KAAK+rE,oBAAqBvW,GAM/D,GALAx1D,KAAK+rE,oBAAsBvW,EAC3Bx1D,KAAKgsE,oBAAoB7rE,IAIpBH,KAAK8nB,cAAe,CAErB,MAAMtG,EAAiBxhB,KAAK0qE,iBAAiBvqE,GAG7CmD,OAAOC,KAAKie,GAAgBQ,QAASiqD,IACjC,MAAMxoE,EAAKwoE,EACX1qD,sBACIC,EAAe/d,GACfzD,KAAK2qE,YAAYlnE,GACjBzD,KAAK0mB,kBAAkBjjB,GACvBzD,KAAKqG,eAMb,MAAM6lE,EAAgB5oE,OAAOH,OAAOnD,KAAK0mB,mBACzCtE,UACI8pD,EAAc3vD,QAASD,GAAWA,EAAOrb,aACzCjB,KAAKqG,aACJnK,QAAQ8D,KAAKG,QAAQ6pC,kBAAmB7pC,GAAQ6pC,oBAErDhqC,KAAKmsE,sBAAsBhsE,EAAQ2rE,GACnC9rE,KAAKosE,kBAAkBjsE,GACvBH,KAAK2qE,YAAcnpD,CACvB,CAEAxhB,KAAKqsE,gBAAgBlsE,EAAQ2rE,GAI7B,MAAMQ,GAA4D,IAApCnsE,GAAQyhE,gBAAgBr+C,QAChDgpD,GAAqBrwE,QAAQ8D,KAAKG,QAAQyhE,gBAAgBr+C,QAASpjB,GAAQyhE,gBAAgBr+C,SAC3FipD,EAAoBxsE,KAAK0mB,kBAAkBk7C,eAAenxC,cAAcjJ,SAASjjB,OAAS,EAC1FkoE,EAAYzsE,KAAK0mB,kBAAkB45C,UAAU7vC,cAAcjJ,SAASjjB,OAAS,EAiBnF,OAfK+nE,GAAyBC,EAC1BvsE,KAAK0mB,kBAAkBk7C,eAAep7D,QAEtC8lE,IACEC,GAAqBE,GAAeX,GAAuBU,IAE7DxsE,KAAK0mB,kBAAkBk7C,eAAe5+D,KAClCslE,wBAAwBtoE,KAAK0mB,kBAAkB45C,UAAU7vC,cAAe+kC,IAIhFx1D,KAAK0sE,2BAA2BvsE,GAEhCH,KAAKisB,gBAAgB9rB,GAEdA,CACX,CAWA,mBAAA6rE,CAA4B7rE,GACxB,MAAMy5C,EAAOz5C,GAAQg+D,WAAWvkB,KAC1BiJ,EAAiC,CAAE5lC,UAAWjd,KAAK4qE,aAAazqE,MAAYy5C,GAAM3uC,OAClFoW,EAASklD,GACX5jB,SACI3iD,KAAKqG,YACLigE,sBAAsBC,EAAWvmE,KAAKkoB,eACtC0xB,IAAO2sB,GACP1jB,EACC53C,GVrZJ,EAAkBs7D,EAA8Bt7D,IACzD+2D,OAAA,KACI,MAAMI,EAAMrlD,OAAO9R,GACb0hE,EAAQpJ,eAAegD,GAG7B,OAFIoG,GAAOvK,EAAIwK,YAAYjwD,SAASgwD,IAE7BvK,IU+YYyK,CAAetG,EAAWt7D,GACrCjL,KAAK8sE,sBAAsBvG,IAEnCvmE,KAAK8sE,oBAAsB,CACvBhS,MAAOz5C,EAAMi5C,aACbS,OAAQ15C,EAAMk5C,cACdS,OAAQ35C,EAAMm5C,eAElBpX,gBAAgBpjD,KAAKqG,YAAauzC,GAAMwF,aACxCgE,gBAAgBpjD,KAAKqG,YAAalG,GAAQsgE,eAAe7mB,MAAMwF,YACnE,CAMA,eAAAitB,CAAwBlsE,EAAyC2rE,GAC7D,IAAK9rE,KAAK+sE,gBAAkB/sE,KAAK8nB,cAAe,OAChD,IAAKgkD,GAAuB5vE,QAAQ8D,KAAKG,QAAQg+D,UAAWh+D,GAAQg+D,WAAY,OAGhF,MAAMA,EAAYn+D,KAAK0mB,kBAAkBy3C,UACnC6O,EAAmBhtE,KAAKitE,mBAAmBjtE,KAAK+sE,eAAgB5sE,GACtEg+D,EAAUn7D,KAAK,IACRgqE,EACHxlD,SAAUwlD,EAAiBxlD,SAAShkB,IAAK2iE,GACrCh2C,eAAeg2C,EAAUhI,EAAUntC,SAASm1C,EAAS1iE,KAAKoT,QAAQ1G,WAAWigB,cAGzF,CAEA,kBAAA68C,CAA2B9O,EAA+Bh+D,GACtD,OAAO8mE,mBAAmB9I,EAAWh+D,GAAQg+D,UAAWn+D,KAAKkoB,cAAeqtC,kBAAkBp1D,IAASwoE,KAC3G,CAWA,sBAAAuE,CACIC,EACAhtE,GDhfK,IACR+nB,EAAuBtlB,ECifpB5C,KAAK0mB,kBAAkB0mD,mBAAmBpqE,KACtC2kE,uBACIwF,EACA,cDpfXjlD,ECqf2CloB,KAAKkoB,cDrfzBtlB,ECqfwC,CAChD2mE,KAAMppE,GAAQ4+D,UAAUzK,YAAY9gD,MAAM+1D,KAC1C/T,aAAcD,kBAAkBp1D,IDvfxB,CAEpBqlE,EACA1B,KAEA,MAAMppC,GA3BK2yC,EA2Be7H,EAAazB,gBA3BRuJ,EA2ByBxJ,GAAY/E,SAASrkC,QA1BjF4yC,GAAiB9rE,KAAMk5B,GAAY2yC,GAAc3yC,EAAQqpC,iBAAmBsJ,GAAc3yC,EAAQotC,gBAC5FyF,iBAFJ,IAAaF,EAAoBC,EA4B/B,MAAME,EAAa9yC,EACZ2uC,gBAAgB3uC,IAAY,CAAC,EAC9BsvC,sBACIpnE,GAAS4yD,cAAc0T,UAAU7oE,MAC7BnD,aAAa0+B,SAAS9/B,MAAM05D,cAAc0T,UAAU7oE,MACpD,UAERuiE,EAAO4K,EAAW5K,MAAQ,YAC1B2G,EAAO3mE,GAAS2mE,MAAQiE,EAAWjE,MAAQ,OAEjD,MAAO,IACA/D,EACHiI,YAAa/nC,aAAa0kC,6BAA6BxH,GAAO16C,GAC9DwlD,UAAW90C,OAAOyxC,iBAAiB7E,EAAa8E,mBAAoBf,IAGpEoE,SAAU/K,EACVgL,kBAAmBJ,EAAWzD,UAhGjB,cCokBrB,CAGA,cAAA8B,CAAuB1rE,GACnB,OAAOkzD,6BAA6BlzD,GAAQ2hE,iBAAkB9hE,KAAKkG,UAAU8iC,oBACjF,CAuBA,0BAAA0jC,CAAmCvsE,GAC/B,MAAM0tE,GAAyD,IAAtC1tE,GAAQ2hE,kBAAkBv+C,QAC7CgpD,GAAqBrwE,QAAQ8D,KAAKG,QAAQ2hE,kBAAkBv+C,QAASpjB,GAAQ2hE,kBAAkBv+C,UAEhGsqD,GAAoBtB,EACrBvsE,KAAK0mB,kBAAkBo7C,iBAAiBt7D,QACjCqnE,GAAoBtB,GAC3BvsE,KAAK0mB,kBAAkBo7C,iBAAiB9+D,KACpC4hE,0BAA0B5kE,KAAK0mB,kBAAkB45C,UAAU7vC,gBAInE,MAAMgT,EAASzjC,KAAK6rE,eAAe1rE,GACnC,GAAIjE,QAAQ8D,KAAK4rE,sBAAuBnoC,GAAS,OAEjDzjC,KAAK4rE,sBAAwBnoC,EAC7Bpf,iBACI,gBACAqhB,a3BvnBoC,gC2BunBW1lC,KAAKkoB,eACpDw6C,yBAAyBj/B,EAAO6vB,QAChCtzD,KAAKqG,YACLo8D,mCAGJ,MAAMqL,EAAkBjoC,c3BvnBS,uB2BunBgC7lC,KAAK+lC,eAClE/lC,KAAKqG,YAAYuB,SAASkmE,IAC1B9tE,KAAKqG,YAAYib,iBAAiBwsD,EAAiB,aAAcrqC,EAAOkC,KAAM,CAAEr9B,UAAU,GAElG,CAUA,qBAAA6jE,CAA8BhsE,EAAyC2rE,GAC1C5vE,QACrB8D,KAAKG,QAAQ4+D,UAAUzK,YAAY9gD,MAAM+1D,KACzCppE,GAAQ4+D,UAAUzK,YAAY9gD,MAAM+1D,QAEfuC,GAEpB9rE,KAAK0mB,kBAAkB0mD,mBAAmB38C,cAAcjJ,SAASjjB,QAEtEvE,KAAKktE,uBAAuBltE,KAAK0mB,kBAAkB45C,UAAU7vC,cAAetwB,EAChF,CAeA,eAAA8rB,CAA0B9rB,GACtB,MAAMojB,GAA8B,IAApBpjB,GAAQojB,QACxB,IAAK,MAAMjH,KAAUhZ,OAAOH,OAAOnD,KAAK0mB,mBACpCpK,EAAOkT,iBAAiBjM,KAAajH,EAAOmU,cAAcjJ,SAASjjB,QAEvE,IAAK,MAAMlE,KAAQqzD,kBACXiF,iBAAiBt4D,EAAMF,GAAQ4+D,WAAW1+D,KAE9CL,KAAK0mB,kBAAkB2uC,iBAAiBh1D,IAAOmvB,kBAAiB,EAExE,CAGA,WAAA2wC,CAAoB9/D,GAChB,GAAK+0D,UAAU/0D,GAEf,OAAOwlC,cAAcw0B,mBAAmBh6D,GAAOL,KAAK+lC,cACxD,CAYA,iBAAAqmC,CAA0BjsE,GACtB,IAAK,MAAME,KAAQmzD,sBAAuB,CACtC,MAAM2M,EAAcngE,KAAKmgE,YAAY9/D,GACrC,IAAK8/D,IAAgBngE,KAAKqG,YAAYuB,SAASu4D,GAAc,SAE7D,MAAMN,EAAkD1/D,GAAQ4+D,WAAW1+D,GAC3EL,KAAKqG,YAAYgd,UAAU88C,EAAangE,KAAK+tE,kBAAkB5N,EAAaN,GAChF,CACJ,CAWA,iBAAAkO,CAA0B5N,EAAqBhgE,GAC3C,MAAMsG,E1B/ZD,CAAgBtG,GACzBA,GAAQqT,MAAM/M,WAAuC,aAA1BqzD,cAAc35D,GAAyB,kBAAoB,kB0B8ZjE6tE,CAAa7tE,GAC9B,GAAiB,oBAAbsG,EAAgC,OACpC,GAAiB,mBAAbA,EAA+B,OAAOg0B,iBAAiBI,YAE3D,MAAMozC,EAAoB,IAAI35D,IAC1B,CAAC,kBAAmB,+BAA+B9Q,IAAK4f,GACpDyiB,cAAcziB,EAASpjB,KAAK+lC,iBAGpC,OAAO/lC,KAAKqG,YACP4X,WACAhO,OAAOzO,KACH2a,GACkB,WAAfA,EAAM9b,MACN8b,EAAM1Y,KAAO08D,IACZ8N,EAAkBjyE,IAAImgB,EAAM1Y,OAC3BzD,KAAK+lC,eAAiB5pB,EAAM1Y,GAAGsc,WAAW/f,KAAK+lC,kBACtDtiC,EACX,CAGA,gBAAAqmB,GAGI9pB,KAAK+sE,oBAAiB,CAC1B,CAKA,wBAAAljD,GAKI,MAAM6Y,EAAkBp/B,OAAOqP,YAC3BrP,OAAOsD,QAAQ5G,KAAK0mB,mBAAmBljB,IAAA,EAAMgR,EAAKxT,KAAsB,CACpEwT,EACAxT,EAAiByvB,iBAIzBzwB,KAAKsoB,sBAAsBtoB,KAAKG,QAAQ,GACxCH,KAAKwpB,aAAaxpB,KAAKG,QAEvB,IAAK,MAAMqU,KAAOlR,OAAOC,KAAKm/B,GAC1B1iC,KAAK0mB,kBAAkBlS,GAAKxR,KAAK0/B,EAAgBluB,IAErDxU,KAAKisB,gBAAgBjsB,KAAKG,OAC9B,CAEA,qBAAAwrE,CACIrnD,EACAuB,EACAjjB,GAEAyhB,iBAAiB,mBAAoBC,EAASuB,EAAO7lB,KAAKqG,YAAazD,EAC3E,CAiDA,gBAAMsrE,CAAWrJ,EAAwBjiE,GACrC,MAAMuqE,EAAgBhF,gBAAgBtD,EAAQjiE,GAASwlE,qBACjDpoE,KAAKkpB,uBACXlpB,KAAK0mB,kBAAkB45C,UAAUt9D,KAAKmqE,GACtCntE,KAAK0mB,kBAAkBouC,kBAAkB9xD,KAAK2kE,uBAAuBwF,EAAe,sBACpFntE,KAAK0mB,kBAAkB+jD,UAAUznE,KAC7B2kE,uBAAuBwF,EAAe,UAAW1H,+BAErDzlE,KAAK0mB,kBAAkB+5C,cAAcz9D,KR1xBhC,EACT6hE,EACA1kE,KAEA,MAAMguE,EAA0D,GAEhE,IAAuC,IAAnChuE,GAAQsgE,eAAel9C,QACvB,IAAK,MAAMihD,KAASK,EAAOr9C,SACvB,IAAK,MAAM4mD,KAAO5J,EAAMr0D,WAAW4uD,SAASqP,IAAK,CAC7C,MAAMrJ,EAAeqJ,EAAI7F,QAAQ8F,8BAEjC,GAAItJ,EAAc,CACd,MAAM50D,EAAa40D,EAAa50D,WAK1Bk3D,EAAe5pE,eACjB2wE,EAAI7F,QAAQ+F,mBAAqBn+D,EAAWo+D,sBAC5CpuE,GAAQq1D,cAAcmT,MAE1BwF,EAAqBxlE,KAAK,IACnBo8D,EACH50D,WAAY,IACL40D,EAAa50D,WAChB1M,GAAIshE,EAAa50D,WAAWq+D,gBAAkB7wE,aAC9CspD,OAAQ6d,UAAUC,EAAc5kE,GAChC0kC,MAAOugC,YAAYL,GACnB0J,cAAe,GAAGt+D,EAAW+0D,wBAAwBwJ,uBACrDC,iBAAkBlxE,eACd0S,EAAWo+D,sBACXpuE,GAAQq1D,cAAcmT,SAEtBtB,GAAgB,CAAEA,gBACtBhD,WAAYG,EAAMr0D,WAAWk0D,WAG7BC,WAAYE,EAAMr0D,WAAWvH,QAGzC,CACJ,CAGR,MAAO,CAAEvI,KAAM,oBAAqBmnB,SAAU2mD,IQ8uBAS,CAAuBzB,EAAentE,KAAKG,SACrFH,KAAK0mB,kBAAkBwX,QAAQl7B,KAAK2kE,uBAAuBwF,EAAe,UAC1EntE,KAAK0mB,kBAAkBqkD,QAAQ/nE,KAAK2kE,uBAAuBwF,EAAe,WAO1EntE,KAAK0mB,kBAAkBokD,UAAU9nE,KAAK2kE,uBAAuBwF,EAAe,aAC5E,IAAK,MAAM9sE,KAAQmzD,sBACX4B,UAAU/0D,IAEdL,KAAK0mB,kBAAkB2uC,iBAAiBh1D,IAAO2C,KAAK2kE,uBAAuBwF,EAAe9sE,IAE9FL,KAAKktE,uBAAuBC,EAAentE,KAAKG,QAChDH,KAAKosE,kBAAkBpsE,KAAKG,QAC5BH,KAAK0mB,kBAAkB66C,iBAAiBv+D,KL11BnC,CAAyB6hE,IAAA,CAClCxkE,KAAM,oBACNmnB,SAAUq9C,EAAOr9C,SAASjL,QAAA,CACrBioD,EAAOF,IACJE,EAAMr0D,WAAW0+D,UAAUC,cACrB5tE,OAAQ6tE,GAAgBA,EAAYC,WAAWzqE,QAChDf,IACIurE,IAAA,CACG1uE,KAAM,UACNyW,SAAU,CACNzW,KAAM,aACNsqC,YAAaokC,EAAYC,UAAUxrE,IAAKyrE,GAAcA,EAAUp6C,QAEpE1kB,WAAY,IACL4+D,EACHtrE,GAAI9F,aACJ2mE,aACAD,WAAYG,EAAMr0D,WAAWk0D,gBAGpC,MKs0BgC6K,CAAsB/B,IACnEntE,KAAK0mB,kBAAkBg7C,kBAAkB1+D,KLh0BpC,CAA8B6hE,IAAA,CACvCxkE,KAAM,oBACNmnB,SAAUq9C,EAAOr9C,SAASjL,QAAA,CACrBioD,EAAOF,IACJE,EAAMr0D,WAAW0+D,UAAUC,cACrB5tE,OAAQ6tE,IAAiBA,EAAYC,WAAWzqE,QAAU,GAAK,GAChEf,IAAKurE,IACF,MAAMI,EAAyB,CAC3BJ,EAAYC,UAAUD,EAAYC,UAAUzqE,OAAS,IAAIswB,MACzDk6C,EAAYC,UAAUD,EAAYC,UAAUzqE,OAAS,IAAIswB,OAG7D,MAAO,CACHx0B,KAAM,UACNyW,SAAU,CAAEzW,KAAM,QAASsqC,YAAawkC,EAAuB,IAC/Dh/D,WAAY,IACL4+D,EACHtrE,GAAI9F,aACJ2mE,aACAD,WAAYG,EAAMr0D,WAAWk0D,WAC7B+K,wBAAyBlwE,QAAQiwE,EAAuB,GAAIA,EAAuB,SAGzF,MKyyBgCE,CAA2BlC,KAC1B,IAA3CntE,KAAKG,QAAQ2hE,kBAAkBv+C,QAC/BvjB,KAAK0mB,kBAAkBo7C,iBAAiB9+D,KAAK4hE,0BAA0BuI,IAEvEntE,KAAK0mB,kBAAkBo7C,iBAAiBt7D,SAGC,IAAzCxG,KAAKG,QAAQyhE,gBAAgBr+C,QAC7BvjB,KAAK0mB,kBAAkBk7C,eAAe5+D,KAClCslE,wBAAwB6E,EAAe5X,kBAAkBv1D,KAAKG,UAGlEH,KAAK0mB,kBAAkBk7C,eAAep7D,QAE1CxG,KAAKisB,gBAAgBjsB,KAAKG,QAC1B,IAAK,MAAMG,KAAWN,KAAKyI,sBACvBnI,EAAQ,CAAEukE,UAElB,CAeA,iBAAMyK,SACItvE,KAAKkpB,uBACX,IAAK,MAAM1U,KAAOlR,OAAOC,KAAKvD,KAAK0mB,mBACnB,cAARlS,GACAxU,KAAK0mB,kBAAkBlS,GAAuChO,OAG1E,CA6BA,iBAAM+oE,CAAY3mE,GACd,MAAM4mE,EAAgBrH,gBAAgBnoE,KAAK0mB,kBAAkB45C,UAAU7vC,cAAe7nB,SAEhF5I,KAAKkpB,uBACXlpB,KAAK0mB,kBAAkB45C,UAAUt9D,KAAKwsE,GAGtCxH,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkB+5C,eACrEuH,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBouC,mBACrEkT,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkB+jD,WACrEzC,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBwX,SACrE8pC,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBokD,WACrE,IAAK,MAAMzqE,KAAQmzD,sBACfwU,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkB2uC,iBAAiBh1D,KAE1F2nE,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBqkD,SACrE/C,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkB66C,kBACrEyG,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBg7C,mBACrEsG,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBk7C,gBACrEoG,+BAA+BwH,EAAexvE,KAAK0mB,kBAAkBo7C,kBACrE9hE,KAAKisB,gBAAgBjsB,KAAKG,OAC9B,CA0BA,mBAAMsvE,CAActR,GAChB,MAAM4O,EN/2BD,CAAyB5O,GAClCA,EAAU36D,IAAKwjE,IACX,IAAKA,EAAe,OAAOA,EAE3B,MAAMb,EAAWY,WAAWC,GAC5B,MAAO,IAAKb,EAAU1iE,GAAI0iE,EAAS1iE,IAAM9F,gBM02BlB+xE,CAAsBrrE,MAAMC,QAAQ65D,GAAaA,EAAYA,EAAU32C,UACxFwlD,EAAmBhtE,KAAKitE,mBAAmBF,EAAgB/sE,KAAKG,cAChEH,KAAKkpB,uBACXlpB,KAAK+sE,eAAiBA,EACtB/sE,KAAK0mB,kBAAkBy3C,UAAUn7D,KAAKgqE,GACtChtE,KAAKisB,gBAAgBjsB,KAAKG,QAE1BH,KAAKosE,kBAAkBpsE,KAAKG,QAC5B,IAAK,MAAMG,KAAWN,KAAKyI,sBACvBnI,EAAQ,CAAE69D,aAElB,CAMA,oBAAMwR,SACI3vE,KAAKkpB,uBACXlpB,KAAK+sE,oBAAiB,EACtB/sE,KAAK0mB,kBAAkBy3C,UAAU33D,OACrC,CA2CA,QAAAs8B,GACI,MAAO,CACHw9B,UAAWtgE,KAAK0mB,kBAAkB45C,UAAU7vC,cAC5C0tC,UAAWn+D,KAAK0mB,kBAAkBy3C,UAAU1tC,cAC5Cg6C,UAAWzqE,KAAK0mB,kBAAkB+jD,UAAUh6C,cAC5CyN,QAASl+B,KAAK0mB,kBAAkBwX,QAAQzN,cACxCgwC,cAAezgE,KAAK0mB,kBAAkB+5C,cAAchwC,cACpDq6C,UAAW9qE,KAAK0mB,kBAAkBokD,UAAUr6C,iBACxCntB,OAAOqP,YACP6gD,sBAAsBhwD,IAAKnD,GAAS,CAChCg1D,iBAAiBh1D,GACjBL,KAAK0mB,kBAAkB2uC,iBAAiBh1D,IAAOowB,iBAGvDs6C,QAAS/qE,KAAK0mB,kBAAkBqkD,QAAQt6C,cACxCqkC,kBAAmB90D,KAAK0mB,kBAAkBouC,kBAAkBrkC,cAC5D8wC,iBAAkBvhE,KAAK0mB,kBAAkB66C,iBAAiB9wC,cAC1DixC,kBAAmB1hE,KAAK0mB,kBAAkBg7C,kBAAkBjxC,cAC5DmxC,eAAgB5hE,KAAK0mB,kBAAkBk7C,eAAenxC,cACtDqxC,iBAAkB9hE,KAAK0mB,kBAAkBo7C,iBAAiBrxC,cAElE,CAGA,iBAAA1E,GACI,MAAO,CAAC,YACZ,CAyBA,UAAIrB,GAGA,MAAMklD,EAAyBtsE,OAAOqP,YAClC6gD,sBAAsBhwD,IAAKnD,GAAS,CAChCg1D,iBAAiBh1D,GACjBL,KAAKmsB,qBAAmC,CAACkpC,iBAAiBh1D,QAMlE,OAAOL,KAAK8qB,YACR9qB,KAAK6qB,sBACD,CACI,YACA,YACA,gBACA,iBACA,mBACA,YACA,oBACA,UACA,YACA,UACA,sBACG2oC,sBAAsBhwD,IAAI6xD,mBAEjCr1D,KAAKyI,uBAET,IACOmnE,EACHtP,UAAWtgE,KAAKmsB,qBAA+C,CAAC,cAChEgyC,UAAWn+D,KAAKmsB,qBAAqD,CAAC,cACtEs0C,cAAezgE,KAAKmsB,qBAAmC,CAAC,kBACxDy1C,eAAgB5hE,KAAKmsB,qBAA0C,CAAC,mBAChE21C,iBAAkB9hE,KAAKmsB,qBAA6C,CAAC,oBAAqB,CACtFtF,QAAUhQ,GT9gCjB,EACTA,EACAg5D,KAEA,MAAM1/D,EAAa0G,EAAQ1G,WACrBs0D,EAAYoL,EAAYroD,SAASrX,EAAWm0D,aAAan0D,WAAW4uD,SAASrkC,SAAW,GACxFo1C,EAAa/L,GACfU,EAAUjjE,KAAMk5B,GAAYA,EAAQqpC,kBAAoBA,GAE5D,MAAO,CACH1jE,KAAM,UACNoD,GAAIoT,EAAQpT,GACZqT,SAAUD,EAAQC,SAClB3G,WAAY,IACLA,EACH4/D,YAAaD,EAAU3/D,EAAWg0D,4BAClC6L,UAAWF,EAAU3/D,EAAWi0D,6BS+/BpB6L,CAA8Bp5D,EAAS7W,KAAK0mB,kBAAkB45C,UAAU7vC,iBAEhFg6C,UAAWzqE,KAAKmsB,qBAA+D,CAAC,cAChF2oC,kBAAmB90D,KAAKmsB,qBAAmC,CAAC,sBAC5D+R,QAASl+B,KAAKmsB,qBAAmC,CAAC,YAClD2+C,UAAW9qE,KAAKmsB,qBAAmC,CAAC,cACpD4+C,QAAS/qE,KAAKmsB,qBAAmC,CAAC,YAClDo1C,iBAAkBvhE,KAAKmsB,qBAAyC,CAAC,sBAG7E,CAQA,0BAAA+jD,GACI,MAAMC,EAAgBtqC,cAAc,kBAAmB7lC,KAAK+lC,eAC5D,OAAO1qB,sBACHrb,KAAKqG,YACLrG,KAAK2qE,YAAYrK,UAAU9+D,KAAMwZ,GAASA,EAAKvX,KAAO0sE,IAAgB70D,SAE9E,GC1nCEvZ,OAAUmB,IAAA,CACZD,KAAM,SACNC,cACAlB,MAAO4B,aACPwsE,UAAW,cAGT30E,WAAYiS,IAAA,CACdzK,KAAM,QACNC,YAAagO,oBAAoBxD,GAAOxK,YACxCktE,UAAW,kBAYFE,+BAAkC,MAAA,CAGvC,oBAAqBvuE,OAAO,2EAC5B,qBAAsBA,OAAO,wEAC7B,oBAAqBA,OAAO,sEAIzB0Q,kBAAkBhX,YACrB,gBAAiB,CACbwH,KAAM,QACNC,YACI,gLAEJktE,UAAW,aAMf,kBAAmB,CACfntE,KAAM,OACNC,YACI,mHACJN,QAAS,CAAC,WAAY,SACtBwtE,UAAW,aAEf,WAAY,CACRntE,KAAM,SACNC,YACI,gKACJktE,UAAW,aAEf,gBAAiB,CACbntE,KAAM,QACNC,YACI,8IACJktE,UAAW,aAEf,oBAAqB,CACjBntE,KAAM,QACNC,YACI,6HACJktE,UAAW,aAEf,kBAAmB,CACfntE,KAAM,QACNC,YACI,4OACJktE,UAAW,eAlDwB,GAkElCG,yCAA4CjtE,OAAOC,KAAK+sE,iBASxDE,+CACOD,0BAA0B/sE,IAAKC,IAAA,CAAkCA,QAAO6sE,gBAAgB7sE,MCnC/FgtE,eAAb,MAII,WAAA5wE,CACI6wE,EACAC,GADiB3wE,KAAA0wE,SAAAA,EACR1wE,KAAA2wE,MAAAA,CACV,CAMH,YAAIh2D,GACA,OAAO3a,KAAK0wE,SAAStnD,QAAQppB,KAAK2wE,MACtC,CAGA,QAAAC,CAASvvD,GACL,IAAK,MAAO1c,EAAUjC,KAAUY,OAAOsD,QAAQya,GAC3CrhB,KAAK0wE,SAASzqC,SAASjmC,KAAK2wE,MAAO,CAAE1tE,KAAM,QAAS0B,WAAUjC,UAClE,OAAO1C,IACX,CAGA,SAAA6wE,CAAUn1D,GACN,IAAK,MAAO/W,EAAUjC,KAAUY,OAAOsD,QAAQ8U,GAC3C1b,KAAK0wE,SAASzqC,SAASjmC,KAAK2wE,MAAO,CAAE1tE,KAAM,SAAU0B,WAAUjC,UACnE,OAAO1C,IACX,CAMA,SAAAqI,CAAUnH,GAEN,OADAlB,KAAK0wE,SAASI,eAAe9wE,KAAK2wE,MAAOzvE,GAClClB,IACX,CAGA,UAAAgsB,CAAWzI,GACP,OAAOvjB,KAAK6wE,UAAU,CAAEl1D,WAAY4H,EAAU,UAAY,QAC9D,CAGA,KAAAwtD,GAEI,OADA/wE,KAAK0wE,SAASK,MAAM/wE,KAAK2wE,OAClB3wE,IACX,GASSgxE,eAAb,MAQI,WAAAnxE,CACIwG,EACAgiB,EACA/c,GAFiBtL,KAAAqG,YAAAA,EACArG,KAAAqoB,eAAAA,EACAroB,KAAAsL,KAAAA,EAVkBtL,KAAAiQ,OAAA,GACPjQ,KAAAic,QAAA,CAAC,EAEHjc,KAAAmQ,0BAAA,IAAI/J,IAEPpG,KAAAuF,uBAAA,IAAIa,GAM5B,CAGH,KAAAwC,CAAMqH,EAA8BgM,GAChCjc,KAAKiQ,OAASA,EACdjQ,KAAKic,QAAUA,CACnB,CAGA,KAAAzV,GACIxG,KAAKmQ,WAAW3J,QAChBxG,KAAKuF,QAAQiB,OACjB,CAEA,OAAA4iB,CAAQunD,GACJ,MAAM7zC,EAAYm0C,YAAYN,GACxBllD,EAAMzrB,KAAKiQ,OACZ/O,OAAQib,GAAU2gB,EAAUzN,MAAOrf,GAAaisB,qBAAqBjsB,EAAUmM,EAAOnc,KAAKic,WAC3FzY,IAAK2Y,GAAUA,EAAM1Y,IAO1B,OANmB,IAAfgoB,EAAIlnB,QACJvE,KAAKsL,KACD,SAASU,KAAKC,UAAU0kE,KACxB,gBAAgB3kE,KAAKC,UAAU0kE,2EAGhCllD,CACX,CAEA,QAAAwa,CAAS0qC,EAAmB1qC,GACxB,MAAMzxB,EAAM,GAAGyxB,EAAShjC,QAAQgjC,EAASthC,WACzC,IAAK,MAAMyC,KAAWpH,KAAKopB,QAAQunD,GAAQ,CACvC,MAAMO,EAAWlxE,KAAKmQ,WAAWrU,IAAIsL,mBAAY,IAAIhB,IAC/Cwe,EAAWssD,EAASp1E,IAAI0Y,GAC9B08D,EAASlrE,IAAIwO,EAAK,CAAEyxB,WAAUw9B,OAAQ7+C,EAAWA,EAAS6+C,OAASzjE,KAAKmxE,KAAK/pE,EAAS6+B,KACtFjmC,KAAKmQ,WAAWnK,IAAIoB,EAAS8pE,GAC7BlxE,KAAKoxE,MAAMhqE,EAAS6+B,EACxB,CACJ,CAEA,cAAA6qC,CAAeH,EAAmBzvE,GAC9B,IAAK,MAAMkG,KAAWpH,KAAKopB,QAAQunD,GAC/B3wE,KAAKuF,QAAQS,IAAIoB,EAASlG,GAC1BlB,KAAKqoB,eAAelhB,aAAaC,EAhJb,iBAgJsB,IAA+BlG,EAEjF,CAEA,KAAA6vE,CAAMJ,GACF,IAAK,MAAMvpE,KAAWpH,KAAKopB,QAAQunD,GAAQ,CACvC,IAAK,MAAM1qC,SAAEA,EAAAw9B,OAAUA,KAAYzjE,KAAKmQ,WAAWrU,IAAIsL,IAAUjE,UAAY,GACzEnD,KAAKoxE,MAAMhqE,EAAS,IAAK6+B,EAAUvjC,MAAO+gE,IAE9CzjE,KAAKmQ,WAAWrJ,OAAOM,GACnBpH,KAAKuF,QAAQuB,OAAOM,IACpBpH,KAAKqoB,eAAelhB,aAAaC,EA3JjB,sBA2JmD,EAE3E,CACJ,CAOA,OAAAiqE,GACI,IAAK,MAAOjqE,EAAS8pE,KAAalxE,KAAKmQ,WACnC,GAAKnQ,KAAKqG,YAAYuB,SAASR,GAE/B,IAAK,MAAM6mB,KAASijD,EAAS/tE,SAAU,CACnC,MAAMiY,EAAUpb,KAAKmxE,KAAK/pE,EAAS6mB,EAAMgY,UACpC/pC,QAAQkf,EAAS6S,EAAMgY,SAASvjC,SAAQurB,EAAMw1C,OAASroD,GAE5Dpb,KAAKoxE,MAAMhqE,EAAS6mB,EAAMgY,SAC9B,CAEJ,IAAK,MAAO7+B,EAASlG,KAAWlB,KAAKuF,QACjCvF,KAAKqoB,eAAelhB,aAAaC,EAjLb,iBAiLsB,IAA+BlG,EAEjF,CAEA,IAAAiwE,CAAa/pE,GAAiBnE,KAAEA,EAAA0B,SAAMA,IAClC,MAAMwL,EAAqCnQ,KAAKqG,YAChD,MAAgB,UAATpD,EACDkN,EAAWmhE,iBAAiBlqE,EAASzC,GACrCwL,EAAWoc,kBAAkBnlB,EAASzC,EAChD,CAEA,KAAAysE,CAAchqE,GAAiBnE,KAAEA,EAAA0B,SAAMA,EAAAjC,MAAUA,IAC7C,IAAK1C,KAAKqG,YAAYuB,SAASR,GAAU,OAEzC,MAAM+I,EAAqCnQ,KAAKqG,YAC1CzD,EAAU,CAAE0F,UAAU,GACf,UAATrF,EACAkN,EAAWmR,iBAAiBla,EAASzC,EAAUjC,EAAOE,GAEtDuN,EAAWiR,kBAAkBha,EAASzC,EAAUjC,EAAOE,EAE/D,GAIEquE,YAAA,EAAiB70C,WAAUpsB,KAA4C,CACzEA,KACIosB,EAAQ,CAACq1B,wBAAwBr1B,IAAU,IC7M7Cm1C,4BAA+B,MACjC,MAAMC,EAA2B,CAC7B,oBAAqB,GACrB,mBAAoB,GACpB,mBAAoB,GACpB,oBAAqB,IAEzB,MAAO,CACHC,MAAO,IAAKD,EAAQ,YAAa,UAAW,gBAAiB,UAAW,YAAa,WACrFtwB,KAAM,IAAKswB,EAAQ,YAAa,UAAW,gBAAiB,UAAW,YAAa,WAE5F,EAXqC,GAgB/BE,qBAAkD,CAAED,MAAO,UAAWvwB,KAAM,WAG5EywB,qCAAwB,IAAIr9D,IAAI,CAAC,GAAI,cAAe,qBAGpDs9D,UAA8B,CAChC,YAAa,cACb,gBAAiB,cACjB,YAAa,cACb,mBAAoB,EACpB,mBAAoB,GAGlBC,+BAAkB,IAAI7qE,QAUf8qE,aAAb,MAAaA,EAaT,WAAAjyE,CAAoBkxD,GAAiB/wD,KAAA+wD,OAAAA,EAXN/wD,KAAA+xE,oCAAA,IAAI3rE,IACHpG,KAAAgyE,qCAAA,IAAI5rE,IAEVpG,KAAAiyE,+BAAA,IAAI7rE,IAE8BpG,KAAAkyE,kBAAA,EAMF,CAG1D,UAAO,CAAIhsE,GACP,OAAOL,MAAMgsE,gBAAiB3rE,EAAA,IAAiB,IAAI4rE,EAAa7kB,WAAWtpC,IAAIzd,IACnF,CAEA,aAAYA,GACR,OAAOlG,KAAK+wD,OAAO7qD,SACvB,CAEA,KAAA0C,GACI5I,KAAK+xE,oCAAuB,IAAI3rE,IAChCpG,KAAKgyE,qCAAwB,IAAI5rE,IACjCpG,KAAKiyE,+BAAkB,IAAI7rE,IAC3B,MAAMmE,EAAkB8b,kBAAkBrmB,KAAKkG,UAAUo3B,iBACzDt9B,KAAKkyE,mBAAqB3nE,GAAmBsI,qBAAqBtI,KAAqB,EAC3F,CAEA,OAAA6e,CAAQkkB,EAAkBsf,GACtB,MAA0B,aAAnBA,EAAUvsD,KAAsBL,KAAKmyE,gBAAgBvlB,EAAUl/C,OAAS,CAtFrE,QAuFd,CAEA,OAAAmiD,CAAQviB,EAAkBsf,GACtB,OAAQA,EAAUvsD,MACd,IAAK,aACDL,KAAKoyE,oBACL,MACJ,IAAK,MACDpyE,KAAKqyE,aACL,MACJ,IAAK,QACDryE,KAAKsyE,eACL,MACJ,IAAK,WACDtyE,KAAKuyE,gBAAgB3lB,EAAUl/C,OAM3C,CAEA,SAAAhK,CAAUD,EAAiBmpD,GACvB,GAAuB,aAAnBA,EAAUvsD,KAAqB,CAC/B,MAAM+Q,EAASpR,KAAKiyE,gBAAgBn2E,IAAI2H,GACxC,OAAO2N,GAAU1C,YAAY0C,EACjC,CACA,OAAOpR,KAAKwyE,kBAAkB/uE,EAClC,CAIA,cAAAwqD,CAAexqD,EAAiBmpD,GAC5B,GAAuB,aAAnBA,EAAUvsD,MAAwBL,KAAKiyE,gBAAgBj2E,IAAIyH,GAE/D,OAAOzD,KAAKyyE,cAAc7lB,EAAUl/C,MACxC,CAMA,gBAAAglE,GACI,MAAMjvC,EAASzjC,KAAKyyE,cACpB,IAAK,MAAMhxE,KAAQ2Q,cAAe,CAC9B,MAAMhB,EAASpR,KAAKiyE,gBAAgBn2E,IAAI0W,eAAe/Q,SAClC,IAAjBgiC,EAAOhiC,IAAuB2P,IAAQqyB,EAAOhiC,GAAQiN,YAAY0C,GACzE,CACA,OAAOqyB,CACX,CAOA,YAAA6uC,GACI,MAAMK,EAAY3yE,KAAKkG,UAAUG,YAAYusE,eAC7C5yE,KAAK6yE,qBAAuBC,oBAAoBH,GAChDA,EAAU1nE,MAAM8nE,gBAAkB/yE,KAAK+wD,OAAO5tD,OAAO,oBAAsBnD,KAAKgzE,mBACpF,CAKA,eAAAb,CAAwBzkE,GACpB,MAAMuC,OAAEA,EAAAgM,QAAQA,GAAYjc,KAAK+wD,OAAOnD,aAClCyC,EAAS79C,eAAe9E,GACxBggD,EAAax8C,oBAAoBxD,GACjCiuB,EAAsC,GAC5C,IAAK,MAAMhqB,KAAU+7C,EAAWr8C,QAC5B,IAAK,MAAM8K,KAASlM,EAAO/O,OAAQwmB,GAC/BmV,wBAAwBlrB,EAAO1B,OAAQyX,EAAWzL,IAElD,IAAK,MAAMtX,KAAYgN,EAAOxB,WAAY,CACtC,IAAKnQ,KAAK+wD,OAAO7C,QAAQ/xC,EAAOxX,GAAW,SAE3C,MAAMi3B,EAAW,CACbluB,QACAtG,QAAS+U,EAAM1Y,GACfkB,WACAyR,KAAM68D,YAAYthE,GAClBC,cAAeD,EAAOC,eAE1B+pB,EAAUhzB,KAAKizB,GACf,MAAMpnB,EAAMq4C,UAAU1wC,EAAM1Y,GAAIkB,GAChC3E,KAAK+xE,qBAAqB/rE,IAAIwO,EAAK,IAAKxU,KAAK+xE,qBAAqBj2E,IAAI0Y,IAAQ,GAAKonB,GACvF,CAGR57B,KAAKgyE,sBAAsBhsE,IAAIqqD,EAAQ10B,GAEvC,MAAMvqB,OAAEA,GAAWs8C,EACbwlB,EAAcjjE,EAAOzO,KACtBkmB,GACGmV,wBAAwBzrB,EAAOnB,OAAQyX,EAAWzL,SACL,IAA7CkyC,aAAazmC,EAAWtW,EAAOzM,WAEjC2Q,EACF49D,GrHtGC,EACTxwE,EACA0T,EAAA,KAA+B,EAC/B+8D,GAAW,KAEX,IAAIC,EACJ,IAAK,MAAM99D,QAAEA,EAAAxD,UAASA,EAAA6C,MAAWA,KAAWS,qBAAqB1S,GAAQ,CACrE,IAAK0T,EAAKtE,GAAY,SAItB,MAAMuhE,EAAaznE,KAAK9M,MAAiC,KAA1BmP,MAAMqH,IAAU/G,GAAK,IAC9C+kE,EAAMF,GAAMC,aAAeA,EAE5BD,KAASD,EAAWC,EAAKz+D,QAAUA,EAAQ0+D,EAAaD,EAAKC,YAAeC,GAAOF,EAAKz+D,QAAUA,KAC7Fy+D,EAAO,CAAE99D,UAAS+9D,aAAY1+D,SAC5C,CACA,OAAOy+D,GAAM99D,SqHsFLi+D,CACIplB,aAAa+kB,EAAa9hE,EAAOzM,UACjCsuE,YAAY,CAAEnhE,UAAWV,EAAOU,YAChCV,EAAOM,UAKf,OAHI4D,GAAStV,KAAKiyE,gBAAgBjsE,IAAIqqD,EAAQ/6C,GAGvCA,EAAU,IAAI,IAAIhB,IAAIqnB,EAAUn4B,IAAKo4B,GAAaA,EAASx0B,WAAa,EACnF,CAGA,eAAAmrE,CAAwB7kE,GACpB,MAAM66B,EAAUvoC,KAAKyyE,cACfhvC,EAAS,CACX/1B,KACG1N,KAAKkyE,kBAAkBhxE,OAAQO,GAASqR,yBAAyBrR,GAAMC,SAASgM,KAEjF2D,EAAU,IAAIjL,IAChBq9B,EACKlnB,QAAS9a,GAASzB,KAAKgyE,sBAAsBl2E,IAAI0W,eAAe/Q,KAAU,IAC1E+B,IAAKmO,GAAW,CAACk7C,UAAUl7C,EAAOvK,QAASuK,EAAOhN,UAAWgN,KAEtE,IAAK,MAAMvK,QAAEA,EAAAzC,SAASA,KAAc0M,EAAQlO,SACxCnD,KAAKwzE,wBAAwBpsE,EAASzC,EAAU4jC,EAExD,CAGA,WAAAkqC,GACI,MAAMtyC,EAAWngC,KAAK+wD,OAAO5tD,OACvBwQ,EAAmB,CAAC,EAC1B,IAAK,MAAMlS,KAAQ2Q,cAAe,CAC9B,MAAM1P,EAAQy9B,EAAS3tB,eAAe/Q,IACjB,iBAAViB,IAAoBiR,EAAMlS,GAAQiB,EACjD,CACA,OAAOgR,qBAAqBC,EAAO3T,KAAKkyE,kBAAmBlyE,KAAKiyE,gBAAgBn2E,IAAI0W,eAAe,SACvG,CAKA,uBAAAghE,CAAgCpsE,EAAiBzC,EAA8B4jC,GAC3E,MAAMxyB,EAAgB/V,KAAK+wD,OAAO3C,gBAAgBhnD,EAASzC,GACrDqR,EAAsB,GACtB3E,EAAUrR,KAAK+xE,qBAAqBj2E,IAAI+wD,UAAUzlD,EAASzC,KAAc,GAC/E,IAAK,MAAM+I,MAAEA,EAAA0I,KAAOA,EAAAxE,cAAMA,KAAmBP,EAAS,CAClD,MAAMqE,EAAW6yB,EAAQ76B,GACnB+H,EAAczV,KAAKiyE,gBAAgBn2E,IAAI0W,eAAe9E,IACvDgI,GAAaD,GAElBO,EAAKrN,KAAK,CAAE8M,cAAaC,WAAUU,OAAMxE,iBAC7C,CACA5R,KAAK+wD,OAAO1C,YAAYjnD,EAASzC,EAAUqR,EAAKzR,OAASuR,aAAaC,EAAeC,GAAQD,EACjG,CAEA,iBAAAq8D,GACIpyE,KAAKkG,UAAUG,YAAYotE,cAAc,CACrCpzE,KAAML,KAAK+wD,OAAO5tD,OAAO,oBAAsBnD,KAAK0zE,mBAE5D,CAEA,eAAAA,GACI,OAAO1zE,KAAK+wD,OAAOnD,aAAac,YAAYruD,MAAQ,UACxD,CAKA,UAAAgyE,GACI,MAAMlyC,EAAWngC,KAAK+wD,OAAO5tD,OACvBwwE,EAAW3zE,KAAK+wD,OAAOnD,aAAae,IACpC/oC,EAASua,EAAS,aAAengC,KAAKwyE,kBAAkB,YACxDoB,EAAWrC,aAAavxE,KAAKkG,UAAU8iC,qBACvC2lB,EAAwB/oC,EACxB,IACOguD,KACAD,EACH,YAAaxzC,EAAS,kBAAoBwzC,IAAW,cAAgBC,EAAS,aAC9E,gBACIzzC,EAAS,sBAAwBwzC,IAAW,kBAAoBC,EAAS,kBAEjFhC,UACN5xE,KAAKkG,UAAUG,YAAYwtE,OAAOllB,EAAK,CAAErmD,UAAU,GACvD,CAKA,iBAAA0qE,GACI,OAAOhzE,KAAK6yE,oBAAsBnB,qBAAqB1xE,KAAKkG,UAAU8iC,oBAC1E,CAGA,iBAAAwpC,CAA0B/uE,GACtB,MAAMkrD,EAAM3uD,KAAK+wD,OAAOnD,aAAae,IAC/BilB,EAAWrC,aAAavxE,KAAKkG,UAAU8iC,qBAC7C,OAAQvlC,GACJ,IAAK,kBAAmB,CAEpB,MAAMirD,EAAa1uD,KAAK0zE,kBACxB,MAA6B,iBAAfhlB,EAA0BA,OAAa,CACzD,CACA,IAAK,WAED,YAAe,IAARC,GAAiD,IAA5BA,EAAI,qBAAkD,gBAArBA,EAAI,aACrE,IAAK,gBACD,OAAOzgD,eAAeygD,IAAM,cAAgBA,EAAI,aAAeilB,EAAS,aAC5E,IAAK,oBACD,OAAO1lE,eAAeygD,IAAM,kBAAoBA,EAAI,iBAAmBilB,EAAS,iBACpF,IAAK,kBACD,OAAO5zE,KAAKgzE,oBAChB,QACI,OAEZ,GAKEC,YAAethE,GACQ,UAArBA,EAAOG,UAA+BA,GAA4B,UAAdA,EAC/B,UAArBH,EAAOG,UAA+BA,GAA4B,UAAdA,EACxD,KAAa,EAKXghE,oBAAuBH,IACzB,MAAM5vB,EAAU+wB,iBAAiBnB,GAAWI,gBAC5C,OAAOpB,sBAAsB31E,IAAI+mD,GAAW,GAAKA,GC1V/CgxB,SAAYrmE,IAAA,CACdzK,KAAM,QACN+rD,WAAY,CAAC,CAAE3uD,KAAM,WAAYqN,YAiCxBsmE,sCAAyC/iB,eAClD,2BACA,kBA5BwD,MAIxD,MAAMgjB,EAAgC,CAAE33C,YAAa,CAAC,UAAWpsB,WAAY,CAAC,WACxEgkE,EAA2B,IAAKl1C,0BAA0B,SAAU9uB,WAAY,CAAC,SACjFikE,EAAyC,CAAElxE,KAAM,QAAS+rD,WAAY,CAAC,CAAE3uD,KAAM,SACrF,MAAO,CACH,oBAAqB8wD,UAAU,YAAa8iB,GAC5C,qBAAsB9iB,UAAU,YAAa8iB,GAC7C,oBAAqB9iB,UAAU,aAAc+iB,MAC1CzhE,kBAAkBshE,UACrB,gBAAiB,CAAE9wE,KAAM,QAAS+rD,WAAY,CAAC,CAAE3uD,KAAM,eACvD,kBAAmB,CAAE4C,KAAM,OAAQ+rD,WAAY,CAAC,CAAE3uD,KAAM,gBACxD,WAAY,CAAE4C,KAAM,SAAU+rD,WAAY,CAAC,CAAE3uD,KAAM,SACnD,gBAAiB8zE,EACjB,oBAAqBA,EACrB,kBAAmB,CAAElxE,KAAM,QAAS+rD,WAAY,CAAC,CAAE3uD,KAAM,WAEjE,EAnB4D,GA8BvDmD,GAAQsuE,aAAanuD,IAAIngB,ICwBjB4wE,yBAAb,MAAaA,UAAiC1nD,4BAyB1C,gBAAa5wB,CAAI0H,EAAgB28B,GAE7B,aADM5iB,oBAAoB/Z,GACnB+3B,eACH/3B,EACA4wE,EAAA,IACM,IAAIA,EAAyB5wE,EAAK28B,GACxCA,GAAA,CAAcvb,GAAaA,EAAS8E,aAAayW,IAEzD,CAEA,WAAAtgC,CAAoB2D,EAAgB28B,GAChCj5B,MAAM1D,EAAK28B,EACf,CAGA,cAAYk0C,GACR,OAAOL,uBAAuBljB,SAAS9wD,KAAKkG,UAChD,CAKA,mBAAA0iB,GAEI,OAAO,GACX,CAKA,sBAAAE,GACI9oB,KAAKutD,sBAAS,IAAIj5C,IAClBtU,KAAKs0E,iBAAmB,IAAItD,eAAehxE,KAAKqG,YAAarG,KAAKqoB,eAAA,CAAiB7T,EAAKk8C,IACpF1wD,KAAKyvD,SAASj7C,EAAKk8C,IAEvB,MAAMzgD,OAAEA,EAAAgM,QAAQA,GAAYjc,KAAKq0E,WAAWzmB,aAG5C,OAFA5tD,KAAKs0E,eAAe1rE,MAAMqH,EAAQgM,GAClC61D,aAAanuD,IAAI3jB,KAAKkG,WAAWosE,eAC1B,CAAC,CACZ,CAKA,YAAA9oD,CAAuB2W,GACnB,MAAMnkB,EAAOu4D,iBAAiBp0C,GAK9B,OAJA6zC,uBAAuBhsE,MAAMhI,KAAKkG,UAAW8V,GAC7Chc,KAAKkG,UAAUsuE,cAAc1C,aAAanuD,IAAI3jB,KAAKkG,WAAWwsE,oBAE1D1yE,KAAKkG,UAAUuX,UAAUzd,KAAKs0E,eAAejD,UAC1C/tE,OAAOC,KAAKyY,GAAMzX,OAASyX,OAAO,CAC7C,CAKA,gBAAA8N,GAEI9pB,KAAKs0E,eAAe9tE,OACxB,CAYA,UAAIyJ,GACA,MAAO,CAAE0gE,MAAQA,GAAU,IAAIF,eAAezwE,KAAKs0E,eAAgB3D,GACvE,CAMA,UAAA3mD,CAAWvmB,GAEP,OADAzD,KAAKy0E,WAAWhxE,GACTzD,KAAKG,SAASsD,IAAOuwE,uBAAuB92C,QAAQl9B,KAAKkG,UAAWzC,EAC/E,CAKA,aAAAwmB,CAAcxmB,EAAYf,GACtB1C,KAAKy0E,WAAWhxE,GAChBzD,KAAKuoB,YAAY,IAAKvoB,KAAKG,OAASsD,CAAAA,GAAKf,GAC7C,CAKA,eAAAwnB,CAAgBzmB,GACZzD,KAAKy0E,WAAWhxE,GAChBzD,KAAKuoB,YAAY/rB,KAAKwD,KAAKG,OAAQsD,GACvC,CA8BA,YAAAixE,CAAajxC,GACT,MAAMtD,EAA2C,CAAC,EAClD,IAAK,MAAO1+B,EAAMiB,KAAUY,OAAOsD,QAAQ68B,GAAS,CAChD,IAAKpxB,eAAe5Q,GAChB,MAAM,IAAIE,MAAM,uBAAuBF,sBAAyB2Q,cAAcxQ,KAAK,UAEvF,QAAc,IAAVc,EAAqB,SAEzB,MAAMe,EAAK+O,eAAe/Q,GAC1B6G,SAAS7E,EAAIf,GACby9B,EAAS18B,GAAMf,CACnB,CACA1C,KAAK0pB,aAAayW,EACtB,CAUA,WAAAw0C,GACI,OAAOC,gBAAgB50E,KAAKqG,YAAY4X,WAC5C,CAWA,UAAIyM,GACA,OAAO1qB,KAAK4qB,aAAa,GAC7B,CAEA,UAAA6pD,CAAmBhxE,GACf,KAAMA,KAAM6sE,iBACR,MAAM,IAAI3uE,MAAM,yBAAyB8B,qDAEjD,CAEA,QAAAgsD,CAAiBj7C,EAAak8C,GACtB1wD,KAAKutD,OAAOvxD,IAAIwY,KAEpBxU,KAAKutD,OAAOtyC,IAAIzG,GAChBnJ,QAAQC,KAAK,8BAA8BolD,KAC/C,GAGEpoD,SAAA,CAAY7E,EAA8Bf,IAC5C22B,kBAAkB,CAAE51B,QAAO6sE,gBAAgB7sE,IAAOf,GAGhD6xE,iBAAoBp0C,GACtB78B,OAAOqP,YACHrP,OAAOsD,QAAQu5B,GAAY,CAAC,GAAGj/B,OAAA,EAAQ,CAAGwB,UAAqB,IAAVA,ICxPhDmyE,WAAa,CACtB,WAAY,CACRpzE,KAAM,qBACNyB,YACI,wHACJi9B,SAAU,CACN20C,mBAAoB,CAAE,oBAAqB,GAAK,qBAAsB,GAAK,oBAAqB,IAChGC,QAAS,CAAE,6BAA6B,EAAO,wBAAwB,GACvExjB,KAAM,CAAE,wBAAwB,EAAO7pB,QAAS,MAGxD,gBAAiB,CACbjmC,KAAM,gBACNyB,YAAa,8EACbi9B,SAAU,CACN20C,mBAAoB,CAAE,oBAAqB,IAAK,qBAAsB,IAAK,oBAAqB,KAChGC,QAAS,CAAE,6BAA6B,EAAM,wBAAwB,GACtExjB,KAAM,CAAE,wBAAwB,EAAO7pB,QAAS,MAGxDstC,QAAS,CACLvzE,KAAM,UACNyB,YACI,yIACJi9B,SAAU,CACN20C,mBAAoB,CAAE,oBAAqB,IAC3CC,QAAS,CACL,6BAA6B,EAC7B,wBAAwB,EACxB,4BAA4B,EAC5B,mCAAmC,GAEvCxjB,KAAM,CAAE,wBAAwB,EAAO7pB,QAAS,MAGxDutC,MAAO,CACHxzE,KAAM,QACNyB,YAAa,2FACbi9B,SAAU,CACN20C,mBAAoB,CAAE,kBAAmB,QAAS,YAAY,MAyB7DI,4BAA+B5xE,OAAOC,KAAKsxE,YAQ3CM,kCAA2DD,aAAa1xE,IAAKC,IAAA,CACtFA,QACGmxE,gBAAgBC,WAAWpxE,OAY5B2xE,gBAAA,CACFzjE,EACAioB,EACAz2B,EACAkyE,KAEA,IAAK,MAAM5xE,KAAM4xE,EAAUr7C,UAAUroB,EAAQioB,EAAWn2B,GAExD,IAAK,MAAOA,EAAIf,KAAUY,OAAOsD,QAAQzD,GAAU,CAAC,GAAI02B,QAAQloB,EAAQioB,EAAWn2B,EAAUf,IAyBpF4yE,eAAiB93D,MAC1Bha,EACAC,EACAb,EAA+B,CAAC,KAEhC,MAAM2yE,EAASV,WAAWpxE,GAC1B,IAAK8xE,EACD,MAAM,IAAI5zE,MAAM,uBAAuB8B,sBAAuByxE,aAAatzE,KAAK,UAEpF,MAAMu+B,EAA8Bo1C,EAAOp1C,UACpCq1C,EAAST,EAASxjB,SAAcpoC,QAAQya,IAAI,CAC/CwwC,yBAAyBt4E,IAAI0H,GAC7Bu8B,cAAcjkC,IAAI0H,GAClBmvD,WAAW72D,IAAI0H,KAEb6xE,EAAYv7C,GAA0Bl3B,EAAQ6yE,MAAQ,GAtD1D,CAAiB37C,GAAmC,IACnD,IAAIxlB,IACHhR,OAAOH,OAAyB0xE,YAAYt4D,QAAA,EAAW4jB,cAAe78B,OAAOC,KAAK48B,EAASrG,IAAW,CAAC,MAoD1C47C,CAAc57C,GAC/Es7C,gBACII,EACAhF,gCACArwC,EAAS20C,mBACTO,EAAS,uBAEbD,gBAAgBL,EAASvzC,qBAAsBrB,EAAS40C,QAASM,EAAS,YAC1ED,gBAAgB7jB,EAAMlF,kBAAmBlsB,EAASoxB,KAAM8jB,EAAS,UC1IxDM,6BAAgC1kB,eAAe,gBAAiB,0BAxB1B,MAC/C,MAAM2kB,EAAkBnkB,wBAAwB,aAC1CokB,EAAA,CACF5yE,EACA0B,KAAA,CAEA1B,OACA+rD,WAAY,CAAC,CAAE3uD,KAAM,eAAgBsE,WAAUsL,OAAQ,CAAC2lE,OAE5D,MAAO,CACH,2BAA4BC,EAAiB,OAAQ,oBACrD,mCAAoCA,EAAiB,SAAU,oCAC/D,iCAAkCA,EAAiB,SAAU,0BAC7D,4BAA6B,CAAE5yE,KAAM,SAAU+rD,WAAY,CAAC,CAAE3uD,KAAM,UAAW4P,OAAQ,CAAC2lE,MACxF,gCAAiCxkB,UAAU,yBAA0BwkB,GACrE,mCAAoCxkB,UAAU,4BAA6BwkB,GAC3E,gCAAiCxkB,UAAU,yBAA0BwkB,GAE7E,EAlBmD,ICN7CE,YAAe15C,IACjB,MAAM25C,EAAuB,IAAKtkB,wBAAwBr1B,GAAQlsB,WAAY,CAAC,SACzED,EAAA,CAAU0sB,EAAkBwI,KAAA,IAC3B4wC,EACHvlE,WAAY,CAAC20B,EAAU,GAAGxI,YAAqBA,MAC3CwI,EAAU,CAAC,EAAI,CAAE10B,WAAY,CAAC,cAEtC,MAAO,CAGHulE,WAAar5C,GAAqBy0B,UAAU,aAAcnhD,EAAO0sB,GAAU,GAAQ1sB,EAAO0sB,GAAU,IACpGs5C,YAAA,IAAmB7kB,UAAU,aAAcnhD,EAAO,eAAe,IACjE4mD,YAAA,IAAmB1F,UAAU,aAAc4kB,KAQtCG,qCAAwC,MACjD,MAAMC,EAAOL,YAAY,eACzB,MAAO,CACH,2BAA4BK,EAAKH,WAAW,aAC5C,2BAA4BG,EAAKH,WAAW,aAC5C,+BAAgCG,EAAKH,WAAW,iBAChD,iCAAkCG,EAAKH,WAAW,mBAClD,6BAA8BG,EAAKF,cACnC,2BAA4BE,EAAKtf,cAEzC,EAVqD,GAgBxCuf,0CAA6C,MACtD,MAAM3L,EAAYqL,YAAY,oBAC9B,MAAO,CACF,CAAAzyB,qBAAqBC,OAAQmnB,EAAUuL,WAAW,aAClD,CAAA3yB,qBAAqBE,UAAWknB,EAAUuL,WAAW,gBACrD,CAAA3yB,qBAAqBG,OAAQinB,EAAUuL,WAAW,aAClD,CAAA3yB,qBAAqBI,YAAagnB,EAAUwL,cAC7C,gCAAiCxL,EAAU5T,cAEnD,EAT0D,GAY7Cwf,+BAAkCplB,eAC3C,oBACA,gBACAilB,uBAISI,oCAAuCrlB,eAChD,yBACA,qBACAmlB,4BC3DSG,gBAAkB,CAC3BvC,uBACAniB,SACAwkB,gBACAC,qBACAX,eASSa,iCAAiE,KAC1ED,gBAAgBh6D,QAASrc,GAA8BA,EAAM2wD,cADa,GAmBjE4lB,cAAA,CAAiBjzE,EAAgBC,KAE1C,MAAMf,EADQ6zE,gBAAgB/0E,KAAMkmB,GAAcA,EAAU1rB,IAAIyH,KAC3CqtD,SAASttD,GAAK05B,QAAQz5B,GAC3C,MAAwB,iBAAVf,EAAqBA,OAAQ,GChClCg0E,4BAA8B,CAAC,SAAU,SAAU,QAAS,QAgB5DC,gCAAkC,CAAC,YAAa,iBEHzDC,WAAY,EAWHC,kBAAA,KACLD,WAAiC,oBAAb98D,UAlCtB,MACF,MAAMg9D,EAAQh9D,SAASK,cAAc,OACrC28D,EAAMC,UAAY,2BAClBj9D,SAASgD,gBAAgBjS,OAAOisE,GAChC,MAAMjzB,EAAoD,SAA1CiwB,iBAAiBgD,GAAOE,cAExC,OADAF,EAAMn6E,SACCknD,GA4B6CozB,KAIpDL,WAAY,EACZM,OAAO,oCACFl4D,KAAA,EAAQrb,QAASwzE,KA/BpB,CAAgBA,IAClB,MAAMC,EAAa,sBAAsBD,KACzC,GAAIE,QAAQr7E,IAAI8d,SAAU,sBAAuB,CAC7C,MAAMw9D,EAAQ,IAAIC,cAClBD,EAAME,YAAYJ,GAClBt9D,SAAS29D,mBAAqB,IAAI39D,SAAS29D,mBAAoBH,EACnE,KAAO,CACH,MAAMrsE,EAAQ6O,SAASK,cAAc,SACrClP,EAAMysE,YAAcN,EACpBt9D,SAAS69D,KAAK9sE,OAAOI,EACzB,GAqBgC2sE,CAAaT,IACxCU,QAAA,KACGjB,WAAY,IAEfkB,MAAO7zD,GAAU5Y,QAAQ4Y,MAAM,mCAAoCA,MEkL/D8zD,UAAb,MAwFI,uBAAI/uC,GACA,OAAOhpC,KAAKg4E,oBAChB,CA0BA,aAAI50C,GACA,OAAOpjC,KAAKi4E,UAChB,CAMA,aAAAzD,CAAc/wC,GACV,IAAIvnC,QAAQunC,EAAQzjC,KAAKi4E,YAAzB,CAEAj4E,KAAKi4E,WAAax0C,EAClB,IAAK,MAAMnjC,KAAWN,KAAKk4E,kBAAmB53E,EAAQN,KAAKi4E,WAHrB,CAI1C,CAMA,kBAAAz0C,CAAmBljC,GACfN,KAAKk4E,kBAAkBvvE,KAAKrI,EAChC,CAUA,sBAAIiuD,GACA,OAAOvuD,KAAKm4E,iBAChB,CAOA,qBAAA3pB,CAAsBruB,EAA6BwjB,GAE/C,GADA3jD,KAAKm4E,kBAAoBh4C,EACpBwjB,EAAQ51B,KAEb,IAAK,MAAMztB,KAAWN,KAAKo4E,0BAA2B93E,EAAQqjD,EAClE,CAQA,oBAAAC,CAAqBtjD,GACjBN,KAAKo4E,0BAA0BzvE,KAAKrI,EACxC,CAsFA,WAAAT,CAAYmL,GH3dH,IAAmBqtE,EG2OjBr4E,KAAAyd,UAAA,EA6E+Bzd,KAAAi4E,WAAA,CAAC,EACqCj4E,KAAAk4E,kBAAA,GA4CvBl4E,KAAAm4E,kBAAA,CAAC,EACoCn4E,KAAAo4E,0BAAA,GAiCjCp4E,KAAAs4E,oBAAA,GAQ9Bt4E,KAAAu4E,qBAAA,EAOcv4E,KAAAw4E,6BAAA,IAAIlkE,IAkLrCtU,KAAAq9B,SAAA,CAAApyB,EAAmBrI,EAA2B,CAAC,KACvD,MAAMtB,EAA8B,CAAEmnB,WAAY7lB,EAAQ6lB,aAAc,GACxEzoB,KAAKyd,UAAW,EAChB,MAAMg7D,IAAkBz4E,KAAKu4E,qBAIvBG,EAAiBp3E,EAAQmnB,WAAaxd,ErI9cvC,EAA0BA,EAAmB0tE,IAElDA,GACyB,iBAAlBA,GACgB,aAAvBA,EAAct4E,MACds4E,EAAc/tE,UAEO,iBAAVK,GAAsC,aAAfA,EAAM5K,OAAwB4K,EAAML,SAC3D,CACHvK,KAAM,WACNoD,GAAqB,iBAAVwH,EAAqBA,EAAQA,EAAMxH,GAC9CmH,QAAS+tE,EAAc/tE,SAI5BK,EqI+biD2tE,CAAuB3tE,EAAOjL,KAAK64E,QAAQ5tE,OAG/F,OAFAjL,KAAK64E,QAAU,IAAK74E,KAAK64E,QAAS5tE,MAAOytE,GACzC14E,KAAKg4E,qBAAuB/xD,uBAAuByyD,GAC5C14E,KAAK84E,WAAWx3E,EAASm3E,IA2IUz4E,KAAAs9B,cAAA,IACnCt9B,KAAK64E,QAAQ5tE,MAlQpBjL,KAAK64E,QAAU56E,gBAAgB+M,QACJ,IAAvBhL,KAAK64E,QAAQ5tE,QACbjL,KAAK64E,QAAU,IAAK74E,KAAK64E,QAAS5tE,MrIjeY,kBqImelDjL,KAAKg4E,qBAAuB/xD,uBAAuBjmB,KAAK64E,QAAQ5tE,OAChE4rE,oBAKAl3E,eAAe,GAEfK,KAAK+4E,qBAAuBhuE,gBAAgB/K,KAAK64E,SACjD74E,KAAKqG,YAAc,IAAI3K,MHxepB,CAEHs9E,eAAe,EACfC,uBAAwB,GACxBC,uCAAuC,MALfb,EGyemBr4E,KAAK64E,SHle7BM,SAEnBluE,MAAOF,gBAAgBstE,GACvBe,mBAAoB,CAAEC,SAAS,GAC/Bp6D,iBAAkBA,iBAAiBo5D,KG+dnCr4E,KAAK0jB,uBAAyBjJ,uBAAuBkJ,IAAI3jB,KAAKqG,aAG9DrG,KAAKqG,YAAYqX,KAAK,aAAA,KAClB1d,KAAKs5E,oBACLt5E,KAAUu5E,oBAEdv5E,KAAKooB,aAAe,IAAImL,YAAYvzB,KAAKqG,YAAarG,KAAK64E,SAASnuD,OACxE,CAmGA,gBAAcouD,CAAWx3E,EAA6Bm3E,GAGlD,SADMz4E,KAAKw5E,eAAe,uBAAwBl4E,GAC9CtB,KAAKy5E,aAAahB,GAAgB,OAOtC,SADMz4E,KAAK05E,mBAAmB5B,MAAA,SAC1B93E,KAAKy5E,aAAahB,GAAgB,OAEtC,MAAMkB,EAAY35E,KAAK45E,kBAAkBt4E,EAAQmnB,YACjDzoB,KAAK05E,kBAAoBC,EACzB,UACUA,CACV,CAAA,QACQ35E,KAAK05E,oBAAsBC,IAC3B35E,KAAK05E,uBAAoB,EAEjC,CAEA,IAAI15E,KAAKy5E,aAAahB,GAAtB,CAEA,UACUz4E,KAAKu5E,gBAAgBj4E,EAC/B,CAAA,MAAS2iB,GACL5Y,QAAQ4Y,MAAMA,EAClB,CAEKjkB,KAAKy5E,aAAahB,IAAgBz4E,KAAK0jB,uBAAuBlH,eAR7B,CAS1C,CAYA,sBAAco9D,CAAiBC,GAC3B,MAAM5uE,EAAQF,gBAAgB/K,KAAK64E,SACnC,GAAI38E,QAAQ+O,EAAOjL,KAAK+4E,sBAAuB,OAE/C,MAAMJ,EAAgB34E,KAAK+4E,qBAC3B/4E,KAAK+4E,qBAAuB9tE,QAOtB,IAAIke,QAAA,CAAeC,EAAS0wD,KAC9B,MAAMC,EAAA,KACF/5E,KAAKqG,YAAY9F,IAAI,QAASy5E,GAC9Bh6E,KAAKs5E,oBACLlwD,KAEE4wD,EAAWC,IAGT,aAAcA,IAElBj6E,KAAKqG,YAAY9F,IAAI,aAAcw5E,GACnC/5E,KAAKqG,YAAY9F,IAAI,QAASy5E,GAE9Bh6E,KAAK+4E,qBAAuBJ,EAE5BmB,EAAOG,EAAMh2D,iBAAiBtiB,MAAQs4E,EAAMh2D,MAAQ,IAAItiB,MAAMs4E,EAAMh2D,MAAMysC,YAE9E1wD,KAAKqG,YAAYqX,KAAK,aAAcq8D,GACpC/5E,KAAKqG,YAAYjG,GAAG,QAAS45E,GAC7Bh6E,KAAKqG,YAAYg3B,SAASpyB,EAAO,CAC7B3C,UAAU,EACV4xE,eAAA,CAAiBn+D,EAAUC,IACvBhc,KAAK0jB,uBAAuB5H,UAAU+9D,EAAoB99D,OAAW,EAAWC,MAGhG,CAEA,YAAAy9D,CAAqBhB,GACjB,OAAOA,IAAkBz4E,KAAKu4E,oBAClC,CAIA,iBAAAe,GACIt5E,KAAKw4E,cAAgB,IAAIlkE,IACrBtU,KAAKqG,YACA4X,WACAhO,OAAOzM,IAAK2Y,GAAUA,EAAM1Y,IAC5BvC,OAAQkG,IAAapH,KAAK0jB,uBAAuB7H,UAAUzU,IAExE,CAMA,aAAAqnD,CAAcrnD,GACV,OAAOpH,KAAKw4E,cAAcx8E,IAAIoL,EAClC,CAMA,oBAAcoyE,CACVW,EACA74E,GAEA,MAAMwrB,EAAW,IAAI9sB,KAAKs4E,qBAAqB3T,KAAA,CAAMyV,EAAGrsE,KAAOqsE,EAAE3zE,UAAY,IAAMsH,EAAEtH,UAAY,IACjG,IAAK,MAAMnG,KAAWwsB,EAClB,UACUxsB,EAAQ65E,KAAY74E,GAC9B,CAAA,MAAS2iB,GACL5Y,QAAQ4Y,MAAMA,EAClB,CAER,CA4BA,mBAAAo2D,CAAoBC,GAEhBt6E,KAAK64E,QAAU,IAAK74E,KAAK64E,QAAS,oBAAqByB,GAEvDt6E,KAAKqG,YAAYk0E,oBAAoBt7D,iBAAiBjf,KAAK64E,SAC/D,CAEA,YAAA2B,CAAqBC,GACjBz6E,KAAK64E,QAAU,IAAK74E,KAAK64E,QAAS4B,YAClC,MAAMC,EAAcD,GAAU/4E,SAAS,KAAO+4E,EAASzhC,MAAM,KAAK,GAAKyhC,EACvEz6E,KAAKqG,YAAY4X,WAAWhO,OAAO+R,QAAS7F,IACxC,GAAmB,WAAfA,EAAM9b,MDzvBT,CAAsB8b,IAC/B,MAAM2gC,EAAa3gC,EAAMT,SAAS,eAAiB,GACnD,QAAOohC,IAGa,WAAdA,GAC0B,IAArBA,EAAUv4C,QAAiC,SAAjBu4C,EAAU,IAGf,IAArBA,EAAUv4C,QACPF,MAAMC,QAAQw4C,EAAU,KACG,iBAApBA,EAAU,GAAG,IACpBA,EAAU,GAAG,GAAGp7C,SAAS,UACzB2C,MAAMC,QAAQw4C,EAAU,KACxBA,EAAU,GAAGp7C,SAAS,UC2uBGi5E,CAAmBx+D,GAAQ,CACtD,MAAMy+D,EAA0CF,EAC1C,CAAC,WAAY,CAAC,MAAO,QAAQA,KAAgB,CAAC,MAAO,SACrD,CAAC,MAAO,QACd16E,KAAKqG,YAAY+a,kBAAkBjF,EAAM1Y,GAAI,aAAcm3E,EAAgB,CAAEtyE,UAAU,GAC3F,GAER,CAwDA,WAAAuyE,CAAYJ,GACJz6E,KAAKyd,SACLzd,KAAKw6E,aAAaC,GAElBz6E,KAAKqG,YAAYqX,KAAK,YAAA,IAAmB1d,KAAK66E,YAAYJ,GAElE,CAeA,mBAAAK,CAAoBn6D,GAChB3gB,KAAK64E,QAAU,IAAK74E,KAAK64E,QAASl4D,oBAClC3gB,KAAKqG,YAAYk0E,oBAAoBt7D,iBAAiBjf,KAAK64E,UACvD74E,KAAKyd,UACLO,kBAAkBhe,KAAKqG,YAE/B,CAiBA,kBAAA00E,CAAmB56E,GACfH,KAAKooB,aAAasB,aAAavpB,EACnC,CAiDA,OAAA66E,GACI,OAAOh7E,KAAKqG,YAAY40E,YAAYC,UAAUpwC,MAClD,CAGA,qBAAcyuC,CAAgBj4E,GAG1B,IAAK,MAAM6a,KAASsT,sBAAsBzvB,KAAKqG,YAAa,CxG72BzB,uBAWL,kBAjCH,cwGw4BvBrG,KAAKqG,YAAY+a,kBAAkBjF,EAAM1Y,GAAI,aAAc,OAAQ,CAAE6E,UAAU,IAM/Eie,cAAcvmB,KAAK64E,QAAQ5tE,SAAWmb,0BAA0BpmB,KAAK64E,QAAQ5tE,SAC7EjL,KAAKg4E,qB1HtLJ,CACT/sE,IAEA,MAAMkwE,EAAalwE,GAAOgF,QAAQzO,KAC7B2a,GAAiE,eAAhBA,GAAO9b,MAE7D,IAAK86E,EAAY,OAIjB,MAAO1qB,GAASr7C,qBAAqB+lE,EAAW95D,QAAQ,qBAClDrO,EAAYy9C,GAASvhD,kBAAkBuhD,EAAMn7C,SACnD,YAAkB,IAAdtC,EAEGA,EAtBsB,IAsBiB,OAAS,aAFvD,G0H2KYooE,CAA0Bp7E,KAAKqG,YAAY4X,aAAeje,KAAKg4E,sB1H9JlCx6D,OACzCxS,EACAqwE,EACAh1E,KAEA,GAAIA,EAAYi1E,YAAYhpE,KAAMipE,GAAkB,kBAAPA,EAAO93E,IAChD,OAEJ,MAAMyG,EAAmB,IAAIsxE,gBAC7BvxE,mBAAmBC,EAAkBc,EAAUb,QAC/CD,EAAiBlE,IAAI,MAAO,OAAOq1E,KACnCnxE,EAAiBlE,IAAI,aAAc,KACnCkE,EAAiBlE,IAAI,aAAc,WACnCK,EAAYo1E,UH1xBwB,gBG4xBhC,GAAGzwE,EAAUE,sDAAsDhB,IACnE,CAAE5B,UAAU,K0HmJZozE,CAA8B17E,KAAK64E,QAAS74E,KAAKg4E,qBAAsBh4E,KAAKqG,aAI5ErG,KAAK64E,QAAQ4B,UAAYz6E,KAAKw6E,aAAax6E,KAAK64E,QAAQ4B,UAExDz6E,KAAKyd,UAAW,EACZnc,SAEMtB,KAAKw5E,eAAe,iBAAkBl4E,EAEpD,CAmIA,qBAAAgF,CAAsBhG,GAClBN,KAAKs4E,oBAAoB3vE,KAAKrI,GAC9B,IAAIq7E,GAAW,EACf,MAAA,KACI,GAAIA,EAAU,OAEdA,GAAW,EACX,MAAM/yE,EAAQ5I,KAAKs4E,oBAAoBzvE,QAAQvI,IACjC,IAAVsI,GAAc5I,KAAKs4E,oBAAoBxvE,OAAOF,EAAO,GAEjE,GCtkCSgzE,iBAAmB,CAAC,WAAY,QAAS,OAAQ,WAAY,oBC0D7DC,oCAA+Dz4E,QA1D7C,CAC3B,oBAAqB,CACjBH,KAAM,SACNC,YAAa,0FACbS,SAAS,GAEb,mBAAoB,CAChBV,KAAM,OACNC,YACI,qHACJN,QAASg5E,kBAEb,2BAA4B,CACxB34E,KAAM,SACNC,YACI,qGACJlB,MAAO,CAAEI,IAAK,EAAGC,IAAK,IAAKwB,KAAM,EAAGC,OAAQ,SAEhD,yBAA0B,CACtBb,KAAM,SACNC,YACI,0LACJlB,MAAO,CAAEI,IAAK,EAAGC,IAAK,EAAGwB,KAAM,IAAMC,OAAQ,SAEjD,oBAAqB,CACjBb,KAAM,SACNC,YACI,2GACJlB,MAAO,CAAEI,IAAK,GAAIC,IAAK,GAAIwB,KAAM,EAAGC,OAAQ,SAEhD,wBAAyB,CAAEb,KAAM,QAASC,YAAa,gCACvD,2BAA4B,CAAED,KAAM,QAASC,YAAa,6BAC1D,wBAAyB,CAAED,KAAM,QAASC,YAAa,6CACvD,oBAAqB,CACjBD,KAAM,SACNC,YACI,6JAER,yBAA0B,CACtBD,KAAM,SACNC,YACI,qGACJlB,MAAO,CA5CmCI,IAAK,GAAKC,IAAK,EAAGwB,KAAM,GAAKC,OAAQ,YA6C/EH,QAAA,KAsBKm4E,8BAA2DD,qBAAqBr4E,IAAA,EAAOC,QAASA,GClBhGs4E,cAAb,MAAaA,UAAsBrvD,4BAoB/B,UAAO5wB,CAAI0H,EAAgBrD,GACvB,OAAOg9B,oBAAoB35B,EAAKu4E,EAAA,IAAqB,IAAIA,EAAcv4E,EAAKrD,GAASA,EAAQ,CACzF4a,S3G5CuB,Y2G6CvBpQ,KAAM,WAEd,CAEA,WAAA9K,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,EACf,CAKA,sBAAA2oB,GACI,MAAM0oC,EAAY9zB,sBAAsB19B,KAAKqG,Y3GzDlB,Y2GyDoD01E,EAAct6E,MAEvFu6E,EAAah8E,KAAKqG,YAAY41E,aAIpC,MAHgB,sBAAZD,GAAY1/D,SACZtc,KAAKg8E,WAAaA,GAEf,CAAExqB,YACb,CAKA,YAAAhoC,CAAuBrpB,GAInB,OAHAH,KAAKk8E,cAAc/7E,GAAQqxD,WAAWjuC,UAAW,GACjDoyD,cAAc3tE,MAAMhI,KAAKkG,UAAW/F,GACpCH,KAAKm8E,cAAch8E,GAAQi8E,WAAW74D,QAASpjB,GAAQi8E,WAAWC,cAAA,GAC3Dl8E,CACX,CAKA,UAAA6pB,CAAWvmB,GACP,OAAOyD,MAAM8iB,WAAWvmB,IAAOkyE,cAAcz4C,QAAQl9B,KAAKkG,UAAWzC,EACzE,CAKA,mBAAA64E,CAAoB/4D,GAChBvjB,KAAKk8E,cAAc34D,GACnBvjB,KAAKu8E,YAAY,CAAE/qB,UAAW,IAAKxxD,KAAKG,QAAQqxD,UAAWjuC,YAC/D,CAOA,kBAAAi5D,GACI,OAAOx8E,KAAKG,QAAQqxD,WAAWjuC,UAAW,CAC9C,CAKA,mBAAAk5D,CAAoBl5D,GAChBvjB,KAAKm8E,cAAc54D,EAASvjB,KAAKG,QAAQi8E,WAAWC,cAAA,GACpDr8E,KAAKu8E,YAAY,CAAEH,UAAW,IAAKp8E,KAAKG,QAAQi8E,UAAW74D,YAC/D,CAOA,kBAAAm5D,GACI,OAAO18E,KAAKG,QAAQi8E,WAAW74D,SAA6C,OAAlCvjB,KAAKqG,YAAY41E,YAC/D,CAYA,UAAIvxD,GACA,OAAO1qB,KAAK2qB,iBAChB,CAEA,aAAAuxD,CAAsB34D,GACdvjB,KAAKkG,UAAUuX,UACfzd,KAAK0mB,kBAAkB8qC,UAAUhiC,iBAAiBjM,EAE1D,CAEA,aAAA44D,CAAsB54D,EAA8B84D,GAC3Cr8E,KAAKkG,UAAUuX,gBAEJ,IAAZ8F,EACIvjB,KAAK28E,qBACL38E,KAAKqG,YAAYu2E,WAAW58E,KAAKg8E,YAE9Bz4D,GAEP,IAAIqL,gB3GvIiB,oB2GuIkB5uB,KAAK0mB,kBAAkB8qC,UAAUl1C,OAAOtB,MAAM8T,iBACjF9uB,KAAKqG,aAETrG,KAAKqG,YAAYu2E,WAAW,CAAEtgE,O3G1IT,oB2G0IoC+/D,kBAEzDr8E,KAAKqG,YAAYu2E,WAAW,MAEhC58E,KAAK28E,yBAAkC,IAAZp5D,EAC/B,CAEA,WAAAg5D,CAAoBM,GAChB78E,KAAKG,OAAS,IAAKH,KAAKG,UAAW08E,GACnC78E,KAAKypB,kBACT,GC1JSqzD,sBAAwB,CAAC,eAAgB,OAAQ,aAAc,UAAW,UAmS1EC,wBAA0B,CAEnCx5D,SAAS,EAETy5D,YAAa,aAEbC,aAAc,kBAEdC,QAAS,CACLC,gBAAiB,CACb50C,QAAS,eACTvjB,OAAQ,CAAEo4D,gBAAiB,IAAMC,gBAAiB,EAAGC,UAAW,oBAEpEC,MAAO,CACHh1C,QAAS,eACTvjB,OAAQ,CAAEo4D,gBAAiB,IAAMC,gBAAiB,EAAGC,UAAW,oBAEpEE,WAAY,CACRj1C,QAAS,eACTvjB,OAAQ,CAAEo4D,gBAAiB,IAAMC,gBAAiB,EAAGC,UAAW,oBAEpEG,cAAe,CACXl1C,QAAS,eACTvjB,OAAQ,CAAEo4D,gBAAiB,IAAMC,gBAAiB,EAAGC,UAAW,oBAIpEI,cAAe,CACXn1C,QAAS,eACTvjB,OAAQ,CAAEo4D,gBAAiB,IAAMC,gBAAiB,EAAGC,UAAW,kBAOxEK,cAAe,CACX/tE,KAAM,CAAE+0B,QAAS,GACjB90B,KAAM,CAAE80B,QAAS,GAAK5f,MAAO,GAC7BgjB,UAAW,WC9Ub61C,mCAAsC15C,GAAwC,SAAdA,EAAuB,UAAY,UAKnG25C,eAAyD,CAC3DC,aAAc,CAAC,UAAW,UAAW,WACrCC,KAAM,CAAC,UAAW,UAAW,WAC7BC,WAAY,CAAC,UAAW,UAAW,WACnCC,QAAS,CAAC,UAAW,UAAW,UAAW,WAC3CC,OAAQ,CAAC,UAAW,UAAW,UAAW,YAQjCC,sBAAqE,CAC9EhB,gBAAiB,CAAE/6E,IAAK,EAAGC,IAAK,KAChCk7E,MAAO,CAAEn7E,IAAK,EAAGC,IAAK,KACtBm7E,WAAY,CAAEp7E,IAAK,EAAGC,IAAK,IAC3Bo7E,cAAe,CAAEr7E,IAAK,EAAGC,IAAK,KAC9Bq7E,cAAe,CAAEt7E,IAAK,EAAGC,IAAK,MAc5B+7E,iBAAoBnU,GACX,kBAAXA,EAA6B,eAAiB,kBAKrCoU,gBAAmBl+E,GACrBA,GAAQ88E,cAAgBF,wBAAwBE,aAY9CqB,kBAAA,CACTrU,EACAsU,KAEA,GAAIA,GAAcC,WAAY,OAAOD,EAAaC,WAElD,MAAM/6C,EAASo6C,eAAeU,GAAch2C,SAAW,gBACjDm8B,EA9BJ,CAAiBuF,GAAuD,UAAXA,GAAiC,kBAAXA,EA8BrEwU,CAAcxU,GAAU,IAAIxmC,GAAQvtB,UAAYutB,EAChE,MAAO,CACH65C,UAAWc,iBAAiBnU,GAC5Bv+D,MAAOg5D,EAAQlhE,IAAA,CAAKkK,EAAO9E,KAAA,CAAalG,MAAQkG,GAAS87D,EAAQngE,OAAS,GAAM,IAAKmJ,aAMvF/R,uBAA4E,CAC9EgjF,QAAS,cACT,aAAc,cACd,aAAc,mBACd,YAAa,cACb,YAAa,oBAGXC,WAAA,CACFz+E,EACAsO,IAEAo1B,gBAAgBF,oBAAoBxjC,GAAQ6pC,kBAAmBv7B,IAAS9S,uBAAqB8S,IAM3FowE,mBAAsBnzE,GACxB,IAAIA,GACCi5D,KAAA,EAAOyV,IAAKrsE,KAAOqsE,EAAIrsE,GACvB7M,OAAA,EAASwB,GAAQkG,EAAOk2E,IAAWA,EAAOl2E,EAAQ,KAAK,KAAOlG,GAC9DooC,OAGHi0C,qBAAA,CACF9U,EACAsU,EACAS,KAEA,MAAM1B,UAAEA,EAAA5xE,MAAWA,GAAU4yE,kBAAkBrU,EAAQsU,GAEvD,IAAIlyE,EAEJ,GAAkB,oBAAdixE,EAAiC,CAEjC,MAAMl7E,IAAEA,EAAAC,IAAKA,GAAQ87E,sBAAsBlU,GAC3C59D,EAASX,EAAMlI,IAAA,EAAOd,QAAOgL,WAAY,CAACtL,EAAOM,EAAQ,KAAQL,EAAMD,GAAMsL,GACjF,MAAO,GAAkB,iBAAd4vE,EAA8B,CAErC,MAAMl7E,IAAEA,EAAAC,IAAKA,GAAQ28E,GAAiBb,sBAAsBlU,GAC5D59D,EAASX,EAAMlI,IAAA,EAAOd,QAAOgL,WAAY,CAACtL,EAAOM,EAAQ,KAAQL,EAAMD,GAAMsL,GACjF,MAEIrB,EAASX,EAAMlI,IAAA,EAAOd,QAAOgL,WAAY,CAAChL,EAAOgL,IAGrD,MAAO,CAAC,cAAe,CAAC,UAAW,CAAC,MAAOu8D,MAAY4U,mBAAmBxyE,KAIxE4yE,4BAAA,CACFhV,EACAsU,KAEA,MAAMjB,UAAEA,EAAA5xE,MAAWA,GAAU4yE,kBAAkBrU,EAAQsU,GAEvD,IAAIW,EAEJ,GAAkB,oBAAd5B,GAAiD,iBAAdA,EAEnC4B,EAAexzE,EAAMlI,IAAA,EAAOd,QAAOgL,WAAY,CAAC,GAAOhL,EAAQ,IAAO,GAAKgL,QACxE,CAGH,MAAMyxE,EAAWvzE,KAAKxJ,OAAOsJ,EAAMlI,IAAK+K,GAAMA,EAAE7L,QAE1CV,EADW4J,KAAKvJ,OAAOqJ,EAAMlI,IAAK+K,GAAMA,EAAE7L,QACvBy8E,GAAY,EACrCD,EAAexzE,EAAMlI,IAAA,EAAOd,QAAOgL,WAAY,CAAC,IAAQhL,EAAQy8E,GAAYn9E,EAAS,GAAK0L,GAC9F,CAEA,MAAO,CAAC,cAAe,CAAC,UAAW,CAAC,mBAAoB,EAAG,mBAAoBmxE,mBAAmBK,KAIhGE,sBAAA,CACFnV,EACAoV,EACAL,KAEA,MAAMM,EAAgBvC,wBAAwBG,QAAQjT,GAAQjlD,OACxDu6D,EAAYF,GAAchC,iBAAmBiC,EAAcjC,iBAAmB,EAEpF,GAAgC,QAA5BgC,GAAc/B,UAAqB,CACnC,MAAMkC,EAAgBH,EAAaG,eAAA,EACnC,GAAe,UAAXvV,EAAoB,CACpB,MAAM5nE,IAAEA,GAAQ87E,sBAAsBlU,GACtC,MAAO,CAAC,MAAOsV,EAAW,CAAC,IAAK,CAAC,IAAKl9E,EAAK,CAAC,WAAY,CAAC,MAAO,SAAU,IAAKm9E,GACnF,CACA,MAAO,CAAC,MAAOD,EAAW,CAAC,IAAK,CAAC,WAAY,CAAC,MAAOtV,GAAS,GAAIuV,GACtE,CAGA,MAAMC,EAAYJ,GAAcjC,iBAAmBkC,EAAclC,iBAAmB,KAE9Eh7E,IAAEA,EAAAC,IAAKA,GACK,kBAFAg9E,GAAc/B,WAAagC,EAAchC,WAEvB0B,GAAiBb,sBAAsBlU,GAAWkU,sBAAsBlU,GACtGjoE,EAAQK,EAAMD,GAAO,EAE3B,MAAe,UAAX6nE,EACO,CAAC,MAAOsV,EAAW,CAAC,IAAK,CAAC,IAAK,CAAC,IAAKl9E,EAAK,CAAC,WAAY,CAAC,MAAO,SAAU,IAAKL,GAAQy9E,IAE1F,CAAC,MAAOF,EAAW,CAAC,IAAK,CAAC,IAAK,CAAC,IAAK,CAAC,WAAY,CAAC,MAAOtV,GAAS,GAAI7nE,GAAMJ,GAAQy9E,KASnFC,sBAAA,CACTt4E,EACAjH,EACA6+E,KAEA,MAAM/U,EAASoU,gBAAgBl+E,GACzBo+E,EAAep+E,GAAQ+8E,UAAUjT,IACjC7nE,IAAEA,EAAAC,IAAKA,GAAQ28E,GAAiBb,sBAAsBlU,GAE5D,OAAO/jC,mBACH,CACIziC,GAAI2D,EACJ/G,KAAM,UACNib,SAAUsjE,WAAWz+E,EAAQ,WAC7BkhB,MAAO,CACH,iBAAkB,CAAC,cAAe,CAAC,UAAW,CAAC,MAAO4oD,GAAS7nE,EAAK,EAAGC,EAAK,GAC5E,oBAAqB,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,GAAI,GAAK,GAAI,EAAG,GAAI,GAC/E,gBAAiB48E,4BAA4BhV,EAAQsU,GACrD,iBAAkB,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,EAAG,EAAG,EAAG,EAAG,GAAI,GAAI,GAAI,GAAI,GAAI,KACxF,kBAAmB,CAAC,cAAe,CAAC,UAAW,CAAC,QAAS,EAAG,GAAK,GAAI,GAAK,GAAI,KAGtFp+E,GAAQ8P,QAAQ0uE,UAKlBgB,uBAAA,CACFv4E,EACAjH,EACA6+E,EACAvwE,EACAw3B,KAEA,MAAMgkC,EAASoU,gBAAgBl+E,GAC/B,OAAO+lC,mBACH,CACIziC,GAAI2D,EACJ/G,KAAM,OACNib,SAAUsjE,WAAWz+E,EAAQsO,GAC7B4S,MAAO,CACH,aAAc09D,qBAAqB9U,EAAQ9pE,GAAQ+8E,UAAUjT,GAAS+U,GACtE,eAAgB,KAGxB/4C,IAKF25C,4BAAA,CACFx4E,EACAjH,EACA6+E,EACAvwE,EACAw3B,KAEA,MAAMgkC,EAASoU,gBAAgBl+E,GACzBo+E,EAAep+E,GAAQ+8E,UAAUjT,GACvC,OAAO/jC,mBACH,CACIziC,GAAI2D,EACJ/G,KAAM,iBACNib,SAAUsjE,WAAWz+E,EAAQsO,GAC7B4S,MAAO,CACH,uBAAwB09D,qBAAqB9U,EAAQsU,EAAcS,GACnE,wBAAyBI,sBAAsBnV,EAAQsU,GAAcv5D,OAAQg6D,GAC7E,sBAAuB,EACvB,yBAA0B,MAGlC/4C,IAUK45C,sBAAA,CACTz4E,EACAjH,EACA6+E,IAEAW,uBAAuBv4E,EAASjH,EAAQ6+E,EAAe,aAAc7+E,GAAQ8P,QAAQ6vE,aAM5EC,2BAAA,CACT34E,EACAjH,EACA6+E,IAEAY,4BAA4Bx4E,EAASjH,EAAQ6+E,EAAe,aAAc7+E,GAAQ8P,QAAQ+vE,kBAQjFC,yBAAA,CACT74E,EACAjH,EACA6+E,IAEAW,uBAAuBv4E,EAASjH,EAAQ6+E,EAAe,YAAa7+E,GAAQ8P,QAAQiwE,YAM3EC,8BAAA,CACT/4E,EACAjH,EACA6+E,IAEAY,4BAA4Bx4E,EAASjH,EAAQ6+E,EAAe,YAAa7+E,GAAQ8P,QAAQmwE,iBAKvFC,oBAAuBC,IACzB,MAAMC,EAAWxD,wBAAwBY,cACzC,MAAO,CACHjwE,MAAO4yE,GAAQ5yE,MACfkC,KAAM,IAAK2wE,EAAS3wE,QAAS0wE,GAAQ1wE,MACrCC,KAAM,IAAK0wE,EAAS1wE,QAASywE,GAAQzwE,QAQhC2wE,yBAAA,CACTp5E,EACAjH,EACA+jC,KAEA,MAAMx2B,MAAEA,EAAAkC,KAAOA,GAASywE,oBAAoBlgF,GAAQw9E,eAEpD,OAAOz3C,mBACH,CACIziC,GAAI2D,EACJ/G,KAAM,OACNib,SAAUmf,iBAAiBI,YAC3BxZ,MAAO,CACH,aAAc3T,GAASkwE,mCAAmC15C,GAC1D,eAAgBt0B,EAAK+0B,UAG7BxkC,GAAQ8P,QAAQwwE,aAQXC,yBAAA,CACTt5E,EACAjH,EACA+jC,KAEA,MAAMx2B,MAAEA,EAAAmC,KAAOA,GAASwwE,oBAAoBlgF,GAAQw9E,eAEpD,OAAOz3C,mBACH,CACIziC,GAAI2D,EACJ/G,KAAM,OACNib,SAAUmf,iBAAiBI,YAC3BxZ,MAAO,CACH,aAAc3T,GAASkwE,mCAAmC15C,GAC1D,eAAgBr0B,EAAK80B,QACrB,aAAc90B,EAAKkV,QAG3B5kB,GAAQ8P,QAAQ0wE,aAWlBC,6BAAA,CACFx5E,EACAkU,EACA2qB,IAEAC,mBACI,CACIziC,GAAI2D,EACJ/G,KAAM,OACNib,WACApa,OAAQukC,oBACRpkB,MAAO,CACH,aAvYQ,UAwYR,aAAc,CAAC,OAAQmkB,kBAAmB,EAAG,GAC7C,eAAgB,CAAC,OAAQA,kBAAmB,EAAG,MAGvDS,GAOK46C,yBAAA,CACTz5E,EACAjH,IAEAygF,6BAA6Bx5E,EAASw3E,WAAWz+E,EAAQ,cAAeA,GAAQ8P,QAAQ6wE,gBAM/EC,4BAAA,CACT35E,EACAjH,IAEAygF,6BAA6Bx5E,EAASw3E,WAAWz+E,EAAQ,aAAcA,GAAQ8P,QAAQ+wE,eAMrFC,iCAAA,CACFh5E,EACAg+B,IAEAC,mBACI,IACOj+B,EACH/G,OAAQukC,oBACRpkB,MAAO,IACApZ,EAAKoZ,MAER,uBAAwB,CAAC,OAAQmkB,kBAlbzB,qBAmbR,wBAAyB,CAAC,IAAKv9B,EAAKoZ,QAAQ,yBAAqD,IACjG,yBAA0B,KAGlC4kB,GAQKi7C,oCAAA,CACT95E,EACAjH,EACA6+E,IAEAiC,iCACIlB,2BAA2B34E,EAASjH,EAAQ6+E,GAC5C7+E,GAAQ8P,QAAQkxE,2BAQXC,uCAAA,CACTh6E,EACAjH,EACA6+E,IAEAiC,iCACId,8BAA8B/4E,EAASjH,EAAQ6+E,GAC/C7+E,GAAQ8P,QAAQoxE,0BCndlBC,UAAY,0WCGLC,sCAAmD,CAAEn/E,IAAK,EAAGC,IAAK,IAAKwB,KAAM,GAAKC,OAAQ,QAUjG09E,YAAuC,CACzC,cACA,CAAC,cAAe,KAChB,CAAC,QACD,EACA,EACA,EACA,IACA,GACA,IACA,GACA,MAEEC,cAAyC,CAC3C,cACA,CAAC,cAAe,KAChB,CAAC,QACD,EACA,IACA,EACA,KACA,GACA,IACA,GACA,MAIEC,eAAkBj+C,GAAgE,CACpF,QACA,CAAC,MAAO,oBACR,UACAA,EAAOk+C,QACP,QACAl+C,EAAO6f,MACP,WACA7f,EAAO8f,SACP,QACA9f,EAAO+f,MACP,aACA/f,EAAOggB,WACPhgB,EAAO+f,OAILo+B,cAAyC,CAC3C,QACA,CAAC,MAAO,oBACR,UACA,qCACA,aACA,wCACA,IAYEC,sCAAyC39C,GAAwC,SAAdA,EAAuB,OAAS,OAiCnG49C,WAAsC,CACxC,cACA,CAAC,cAAe,KAChB,CAAC,QACD,EACA,IACA,EACA,IACA,GACA,IACA,GACA,IAKEC,kBAA6C,CAC/C,MACA,CAAC,KAAM,CAAC,gBAAiB,YAAY,GACrCt8C,qBAKEu8C,YAAuC,CAAC,OAAQD,kBAzBjC,EAyBkE,GAIjFE,eAAA,CAAkBl9D,EAAgC8xC,IACpC,OAAhBA,EAAuB9xC,EAAQvY,WAAWu1E,kBAAmBh9D,EAAO8xC,GAElEqrB,aAAwC,CAC1C,KACA,CAAC,MAAO,oBACR,CAAC,UAAW,CAAC,QAAS,WAAY,WAEhCC,eAA0C,CAC5C,KACA,CAAC,MAAO,oBACR,CAAC,UAAW,CAAC,UAAW,gBAsBfC,iCAAA,CACTrnE,EACAO,EACAitB,EACA/B,EACA9F,KAEA,MAAMm2B,EAAcrwB,GAAWqwB,aAAe,KACxC5mD,EAAgC,GAoFtC,OAnFkB,OAAdu2B,GACAv2B,EAAOtH,KACHu9B,mBACI,CAIIziC,GAAI,GAAGsX,mBACP1a,KAAM,OACN2gB,QAAS,EACTtF,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C2F,MAAO,CACH,aAAcmlB,EAAUppB,aACxB,aAAc0kE,WACd,YAtFN,EAuFM,eAAgBE,aAEpB1mE,YAEJolB,GAAW2hD,gBAIvBpyE,EAAOtH,KACHu9B,mBACI,CACIziC,GAAI,GAAGsX,YACP1a,KAAM,OACN2gB,QAAS,EACTtF,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C2F,MAAO,CACH,aAAcqgE,eAAen5C,EAAQpD,SACrC,aAAc88C,eAAeR,cAAe5qB,IAEhDv7C,YAEJolB,GAAWyE,SAEfe,mBACI,CACIziC,GAAI,GAAGsX,gBACP1a,KAAM,OACN2gB,QAAS,EACT9f,OAAQghF,aACRxmE,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C2F,MAAO,CACH,aAAcqgE,eAAen5C,EAAQ7nC,OACrC,aAAcuhF,eAAeT,YAAa3qB,IAE9Cv7C,YAEJolB,GAAW4hD,YAEfp8C,mBACI,CAGIziC,GAAI,GAAGsX,kBACP1a,KAAM,OACN2gB,QAAS,GACT9f,OAAQghF,aACRxmE,OAAQ,CAAE,WAAY,OAAQ,YAAa,SAC3C2F,MAAO,CACH,eDjQ+B,wCCkQ/B,aAAc4gE,eAAeT,YAAa3qB,IAE9Cv7C,YAEJolB,GAAW6hD,cAEfr8C,mBACI,CACIziC,GAAI,GAAGsX,kBACP1a,KAAM,OACN2gB,QAAS,EACT9f,OAAQihF,eACRzmE,OAAQ,CAAE,WAAY,QAAS,YAAa,SAC5C2F,MAAO,CAAE,eAAgBugE,cAAe,aAAcK,eAAeT,YAAa3qB,IAClFv7C,YAEJolB,GAAW8hD,eAGZvyE,GAKLwyE,UAAqC,CAAC,OAAQ,CAAC,QAAS,GAAK,GAAI,GAAK,GAAI,GAAK,GAAI,GACnFC,cAAyC,CAAC,OAAQ,CAAC,QAAS,KAAM,GAAI,KAAM,GAAI,KAAM,GAAI,IAK1FC,qBAAgD,CAClD,QACA,CAAC,MAAO,YACR,QACA,yBACA,WACA,6BACA,MACA,wBACA,SACA,8BACA,OACA,yBACA,QACA,0BACA,cACA,gCACA,YACA,8BACA,OACA,yBACA,WACA,6BACA,sBACA,wCACA,IAKEC,uBAAkD,CACpD,MACA,CAAC,KAAM,CAAC,MAAO,oBAAqB,cACpC,CACI,MACA,CAAC,IAAK,CAAC,MAAO,mBACd,CACI,OACA,CAAC,QACD,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,GACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,GACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,IACA,KAKNC,kBAA6C,CAC/C,MACA,CAAC,KAAM,CAAC,MAAO,YAAa,OAC5B,CAAC,KAAM,CAAC,MAAO,oBAAqB,WACpC,CAAC,KAAM,CAAC,MAAO,oBAAqB,cACpC,CACI,OACA,CAAC,QACD,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,GACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,GACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,IACA,IAIFC,mBAA8C,CAAC,KAAM,CAAC,MAAO,oBAAqB,cAKlFC,SAAoC,CACtC,SACA,eACA,CAAC,MAAO,oBACR,CACI,OACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,KACjC,SACA,CAAC,IAAK,CAAC,MAAO,kBAAmB,MACjC,UACA,WAKFC,SAAoC,CACtC,OACA,CAAC,KAAM,CAAC,MAAO,kBAAmB,IAClC,QACA,CAAC,KAAM,CAAC,MAAO,kBAAmB,MAClC,CACI,SACA,CAAC,YAAa,CAAC,QAAS,CAAC,IAAK,CAAC,YAAa,CAAC,MAAO,mBAAoB,QACxE,OACA,CACI,OACA,CAAC,KAAM,CAAC,IAAK,CAAC,QAAS,CAAC,IAAK,CAAC,YAAa,CAAC,MAAO,mBAAoB,KAAM,IAAK,GAClF,GACA,CACI,SACA,CAAC,YAAa,CAAC,QAAS,CAAC,IAAK,CAAC,IAAK,CAAC,YAAa,CAAC,MAAO,mBAAoB,IAAK,MACnF,UAIZ,CAAC,SAAU,CAAC,QAAS,CAAC,IAAK,CAAC,MAAO,kBAAmB,KAAM,SAK1DC,SAAoC,CAAC,OAAQ,CAAC,MAAO,kBAAmB,CAAC,KAAK,EAAI,CAAC,MAAO,mBAAoB,GAUvGC,mCAAA,CACTnoE,EACAO,EACAolB,IAC0B,CAC1BwF,mBACI,CACIziC,GAAI,GAAGsX,oBACP1a,KAAM,SACN2gB,QAAS,GACT9f,OAAQ0hF,uBACRlnE,OAAQ,CACJ,mBAAoB,QACpB,sBAAsB,EACtB,cAAe,eACf,cAAe,EAAC,EAAI,GACpB,aAAcinE,qBACd,YAAaF,UACb,kBAAmBQ,UAEvB3nE,YAEJolB,GAAWyiD,gBAEfj9C,mBACI,CACIziC,GAAI,GAAGsX,eACP1a,KAAM,SACN2gB,QAAS,GACT9f,OAAQ2hF,kBACRnnE,OAAQ,CACJ,mBAAoB,QACpB,cAAe,cACf,cAAe,CAAC,EAAG,GACnB,aAAcqnE,SACd,YAAaN,UACb,eAAgB,EAChB,cAAe,cACf,aAAcO,SACd,YAAa,CAAC,0BACd,cAAe,CAAC,KAAK,KACrB,YAAaN,cACb,kBAAmBO,UAEvB5hE,MAAO,CAAE,aAAc,sBACvB/F,YAEJolB,GAAW0iD,WAEfl9C,mBACI,CACIziC,GAAI,GAAGsX,uBACP1a,KAAM,SACN2gB,QAAS,GACT9f,OAAQ4hF,mBACRpnE,OAAQ,CACJ,mBAAoB,QACpB,sBAAsB,EACtB,cAAe,eACf,cAAe,EAAC,EAAI,GACpB,aAAc,gCACd,YAAa+mE,WAEjBnnE,YAEJolB,GAAW2iD,mBCpeNC,eAAiB,CAC1B,WACA,gBACA,QACA,aACA,UACA,eACA,YACA,iBACA,WACA,gBACA,SACA,UACA,SAgCSC,wBAA0B,CAAC,eAAgB,cAAe,iBAyB1DC,yBAA2B,CAAC,UAAW,WAAY,SCvC1DC,oBAAiC,CAAErhF,IAAK,EAAGC,IAAK,IAAMwB,KAAM,GAAIC,OAAQ,YACxE4/E,iCAA8C,CAAEthF,IAAK,EAAGC,IAAK,IAAKwB,KAAM,GAAKC,OAAQ,YACrF6/E,oBAAiC,CAAEvhF,IAAK,EAAGC,IAAK,GAAIwB,KAAM,GAAKC,OAAQ,YAGvE8/E,oCAAuB/gF,kBAAkB,yBAA0B,kBAAmBygF,gBACtFO,uCAA0BhhF,kBAAkB,4BAA6B,gCAAiC,IACzG0gF,2BACAC,2BAIDM,0BAA4B,CAC9B,8BAA+BF,qBAAqB5gF,KACpD,gCAAiC4gF,qBAAqBzgF,OACtD,iCAAkC0gF,wBAAwB7gF,KAC1D,mCAAoC6gF,wBAAwB1gF,QAG1D4gF,wCAA2BlhF,kBAC7B,6BACA,sBACAnF,+BAEEsmF,gCAAmBnhF,kBACrB,qBACA,kCACAF,SADA,CAC0B,CAAC,UAAW,QAAS,WAAY,QAAS,gBAGlEshF,oBAAsB,CACxB1gE,QAAS,CACLtgB,KAAM,SACNC,YAAa,8CACbS,SAAS,MAEVmgF,0BACH,uBAAwB,CACpB7gF,KAAM,OACNC,YAAa,oFACbN,QAASf,iBAEb,cAAe,CAAEoB,KAAM,QAASC,YAAa,2DAC7C,cAAe,CAAED,KAAM,QAASC,YAAa,qDAC7C,kBAAmB,CAAED,KAAM,QAASC,YAAa,yDACjD,oBAAqB,CAAED,KAAM,QAASC,YAAa,2DACnD,gBAAiB,CAAED,KAAM,QAASC,YAAa,mCAC/C2zD,YAAa,CACT5zD,KAAM,SACNC,YAAa,2BACblB,MAAO+B,kBACPJ,QAAS,IAIXugF,yBAA2B,CAC7B3gE,QAAS,CACLtgB,KAAM,SACNC,YAAa,kEACbS,SAAS,GAEb,gBAAiB,CACbV,KAAM,SACNC,YAAa,yEAEd4gF,0BACH,kCAAmCC,yBAAyB/gF,KAC5D,oCAAqC+gF,yBAAyB5gF,OAC9D,0BAA2B6gF,iBAAiBhhF,KAC5C,4BAA6BghF,iBAAiB7gF,OAC9C,+BAAgC,CAC5BF,KAAM,SACNC,YAAa,2CACbS,SAAS,GAEb,iCAAkC,CAC9BV,KAAM,SACNC,YACI,6IACJlB,MA5E+B,CAAEI,IAAK,EAAGC,IAAK,IAAKwB,KAAM,EAAGC,OAAQ,aA8ExE,eAAgB,CAAEb,KAAM,QAASC,YAAa,+DAC9C,kBAAmB,CACfD,KAAM,QACNC,YAAa,kEAEjB,eAAgB,CAAED,KAAM,QAASC,YAAa,+DAC9C,gBAAiB,CAAED,KAAM,QAASC,YAAa,6CAC/C2zD,YAAa,CACT5zD,KAAM,SACNC,YAAa,+BACblB,MAAO+B,kBACPJ,QAAS,IAIXwgF,+BAAiC,CACnC5gE,QAAS,CACLtgB,KAAM,SACNC,YAAa,8DACbS,SAAS,GAEb,yBAA0B,CACtBV,KAAM,QACNC,YACI,yHAER,wBAAyB,CACrBD,KAAM,SACNC,YACI,0GACJlB,MAAOu/E,sCACP59E,QF3CsC,ME0DxCygF,4CAA+C,MAAA,CAE7C7gE,QAAS,CACLtgB,KAAM,SACNC,YAAa,kDACbS,QAASo5E,wBAAwBx5D,SAErCy5D,YAAa,CACT/5E,KAAM,OACNC,YAAa,4FACbN,uBAASD,SAAT,CAA6C,CACzC,aACA,aACA,YACA,YACA,YAEJgB,QAASo5E,wBAAwBC,aAErCC,aAAc,CACVh6E,KAAM,OACNC,YAAa,qEACbN,QAASzF,wBACTwG,QAASo5E,wBAAwBE,cAErC,sBAAuB,CACnBh6E,KAAM,QACNC,YAAa,6FAEjB,6BAA8B,CAC1BD,KAAM,SACNC,YAAa,8BACblB,MAAOgC,cACPL,QAASo5E,wBAAwBY,cAAc/tE,KAAK+0B,SAExD,6BAA8B,CAC1B1hC,KAAM,SACNC,YAAa,uCACblB,MAAOgC,cACPL,QAASo5E,wBAAwBY,cAAc9tE,KAAK80B,SAExD,2BAA4B,CACxB1hC,KAAM,SACNC,YAAa,gDACblB,MAAO2hF,oBACPhgF,QAASo5E,wBAAwBY,cAAc9tE,KAAKkV,OAExD,0BAA2B,CACvB9hB,KAAM,OACNC,YAAa,4EACbN,uBAASD,SAAT,CAA+E,CAAC,SAAU,aAC1FgB,QAASo5E,wBAAwBY,cAAc51C,aAnDN,GAuD/Cs8C,sBAIF,CACA97C,QAAU0hC,IAAA,CACNhnE,KAAM,OACNC,YAAa,oBAAoB+mE,kGACjCrnE,QAASk6E,sBACTn5E,QAASo5E,wBAAwBG,QAAQjT,GAAQ1hC,UAErD,mBAAqB0hC,IAAA,CACjBhnE,KAAM,OACNC,YAAa,cAAc+mE,+GAC3BrnE,uBAASD,SAAT,CAAwE,CACpE,kBACA,eACA,QAEJgB,QAASo5E,wBAAwBG,QAAQjT,GAAQjlD,OAAOs4D,YAE5D,yBAA2BrT,IAAA,CACvBhnE,KAAM,SACNC,YAAa,2BAA2B+mE,uFACxCjoE,MAAOyhF,oBACP9/E,QAASo5E,wBAAwBG,QAAQjT,GAAQjlD,OAAOo4D,kBAE5D,yBAA2BnT,IAAA,CACvBhnE,KAAM,SACNC,YAAa,0BAA0B+mE,wBACvCjoE,MAAOyhF,oBACP9/E,QAASo5E,wBAAwBG,QAAQjT,GAAQjlD,OAAOq4D,kBAE5D,uBAAyBpT,IAAA,CACrBhnE,KAAM,SACNC,YAAa,kCAAkC+mE,qCAC/CjoE,MAAO0hF,iCACP//E,QAAA,IAEJ,cAAgBsmE,IAAA,CACZhnE,KAAM,SACNC,YAAa,0BAA0B+mE,oCAAyCA,4BAChFjoE,MAAO,IAAKm8E,sBAAsBlU,GAASnmE,OAAQ,cAEvD,cAAgBmmE,IAAA,CACZhnE,KAAM,SACNC,YAAa,0BAA0B+mE,oCAAyCA,4BAChFjoE,MAAO,IAAKm8E,sBAAsBlU,GAASnmE,OAAQ,eAIrDwgF,4BAAA,KACF,MAAMC,EAAmBjhF,OAAOC,KAAK8gF,uBACrC,MAAO,IACAjhF,QAAQghF,iCACRjnF,wBAAwBof,QAAS0tD,GAChCsa,EAAiB/gF,IACZwoC,IAAA,CACGvoC,GAAI,WAAWwmE,KAAUj+B,OACtBq4C,sBAAsBr4C,GAAMi+B,SAkBtCua,wCAAuEphF,QAAQ6gF,qBAO/EQ,kCAAmED,yBAAyBhhF,IAAA,EAClGC,QAASA,GAcHihF,6CACOthF,QAAQ8gF,0BAOfS,uCACOD,8BAA8BlhF,IAAA,EAAOC,QAASA,GAarDmhF,mDACOxhF,QAAQ+gF,gCAOfU,6CACOD,oCAAoCphF,IAAA,EAAOC,QAASA,GAa3DqhF,iDACOR,8BAOPS,2CACOD,kCAAkCthF,IAAA,EAAOC,QAASA,GC/WzDuhF,qBACTC,IAGO,CACH5kF,KAAM,oBACNmnB,SAHUy9D,EAAUz9D,SAASjL,QAAS1F,GAAYA,EAAQ1G,YAAY+0E,WAAWhnE,OAAS,IAG1E1a,IAAA,CAAKgb,EAAM5V,KACvB,MAAOu8E,EAAKC,GAAO5mE,EAAK6mE,WAClB5hF,EAAK,QAAQmF,KAASu8E,KAAOC,IAEnC,MAAO,CACH/kF,KAAM,UACNoD,KACAqT,SAAU,CACNzW,KAAM,QACNsqC,YAAa,CAACw6C,EAAKC,IAEvBj1E,WAAY,CACR1M,KACA85E,MAAO/+D,EAAK++D,OAAS,EACrBE,cAAej/D,EAAKi/D,eAAiB,EACrCN,gBAAiB3+D,EAAK2+D,iBAAmB,EACzCK,WAAYh/D,EAAKg/D,YAAc,EAC/BE,cAAel/D,EAAKk/D,eAAiB,QAiFnD4H,wBAAA,CACFpnE,EACAqnE,EACAt8C,IAjCE,EACFu8C,EACAD,EACAt8C,KAEA,MAAMzhB,EAA+D,GAErE,IAAK,IAAIi+D,EAAY,EAAGA,EAAYD,EAAejhF,OAAQkhF,IAAa,CACpE,MAAMC,EAAOF,EAAeC,GAC5B,IAAKC,EAAM,SACX,MAAMC,cAAEA,EAAAC,SAAeA,EAAAC,YAAUA,EAAAC,cAAaA,EAAAC,iBAAeA,EAAA93C,MAAkBA,GAAUy3C,EACnFjiF,EAAK,GAAGwlC,KAAYw8C,IAC1Bj+D,EAAS7e,KAAK,CACVtI,KAAM,UACNoD,KACAqT,SAAUyuE,EAAK/9D,SAASi+D,GAAW3uE,SACnC3G,WAAY,CACR1M,KACA05E,gBAAiBvxE,KAAK9M,MAAM6mF,EAAgB13C,GAC5CsvC,MAAO3xE,KAAK9M,MAAM8mF,EAAW33C,GAC7BwvC,cAAe7xE,KAAK9M,MAAM+mF,EAAc53C,GACxCuvC,WAAY5xE,KAAK9M,MAAOgnF,EAAgB73C,EAAS,IAAM,GACvDyvC,cAAe9xE,KAAK9M,MAAMinF,EAAmB93C,KAGzD,CAEA,OAAOzmB,GAQPw+D,CAtEE,EACF9nE,EACAqnE,KAEA,MAAMC,EAAmD,GAEzD,IAAK,MAAMhnE,KAAQN,EACf,IAAK,IAAItV,EAAQ,EAAGA,EAAQ28E,EAAK/9D,SAASjjB,OAAQqE,IAC9C,GAAIzJ,sBAAsBqf,EAAK6mE,WAAYE,EAAK/9D,SAAS5e,IAAS,CAC9D,IAAI88E,EAAOF,EAAe58E,QACb,IAAT88E,IACAA,EAAO,CACHC,cAAe,EACfC,SAAU,EACVC,YAAa,EACbC,cAAe,EACfC,iBAAkB,EAClB93C,MAAO,GAEXu3C,EAAe58E,GAAS88E,GAE5BA,EAAKC,eAAiBnnE,EAAK2+D,iBAAmB,EAC9CuI,EAAKE,UAAYpnE,EAAK++D,OAAS,EAC/BmI,EAAKG,aAAernE,EAAKi/D,eAAiB,EAC1CiI,EAAKI,eAAiBtnE,EAAKg/D,YAAc,EACzCkI,EAAKK,kBAAoBvnE,EAAKk/D,eAAiB,EAC/CgI,EAAKz3C,QACL,KACJ,CAIR,OAAOu3C,GAsCkBS,CAAoB/nE,EAAOqnE,GAAOA,EAAMt8C,GAS/Di9C,oBAAA,CACFjB,EACAkB,EACAC,EACAn9C,KAEA,MAAMo9C,EAAmF,GAEzF,IAAK,IAAIC,EAAe,EAAGA,EAAerB,EAAUz9D,SAASjjB,OAAQ+hF,IAAgB,CACjF,MAAMC,EAAmBtB,EAAUz9D,SAAS8+D,GACtCpoE,EAAQqoE,EAAiBp2E,YAAY+0E,WAAWhnE,OAAS,GAE/D,GAAqB,IAAjBA,EAAM3Z,OAAc,SAExB,MAAM0mC,EAAO3tC,gBAAgBipF,GAC7B,IAAKt7C,EAAM,SAQX,MAAMs6C,EAAOa,EAAY,CALrBn7C,EAAK,GApBQ,KAqBbA,EAAK,GArBQ,KAsBbA,EAAK,GAtBQ,KAuBbA,EAAK,GAvBQ,MAyBoBk7C,GACrCE,EAAS19E,QAAQ28E,wBAAwBpnE,EAAOqnE,EAAM,GAAGt8C,KAAYq9C,KACzE,CAEA,MAAO,CAAEjmF,KAAM,oBAAqBmnB,SAAU6+D,IAgBrCG,mBAAA,CACTvB,EACAkB,EAAa,MAEbD,oBAAoBjB,EAAWkB,EAAA,CAAal7C,EAAMw7C,IAAOnnF,QAAQ2rC,EAAMw7C,EAAI,CAAEC,MAAO,eAAiB,OAe5FC,sBAAA,CACT1B,EACAkB,EAAa,KAEbD,oBAAoBjB,EAAWkB,EAAA,CAAal7C,EAAMw7C,IAAOjnF,WAAWyrC,EAAMw7C,EAAI,CAAEC,MAAO,eAAiB,UCjItGE,iBAAoBzmF,GACoE,cAAzFA,GAAQw9E,eAAe51C,WAAag1C,wBAAwBY,cAAc51C,WAIzE8+C,YAAwD,CAAC,UAAW,SAAU,WAmCvEC,2BAAb,MAAaA,UAAmCj7D,2BA0D5C,mBAAa9lB,CACTG,EACA/F,GAKA,aAHMod,oBAAoBrX,GAGnB,IAAI4gF,EAA2B5gF,EAAW/F,GAAU,CAAC,EAChE,CAEA,WAAAN,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,GAlDsCH,KAAA+mF,cAAA,KACkC/mF,KAAAyI,sBAAA,EAkDvF,CAKA,sBAAAqgB,CACI3oB,EACA6mF,GAEA,MAAMp+E,EAAQ5I,KAAKkoB,cACb++D,EAAkB,0BAA0Br+E,IAC5Cs+E,EAAkB,0BAA0Bt+E,IAC5Cu+E,EAAiB,yBAAyBv+E,IAC1Cw+E,EAAiB,yBAAyBx+E,IAE1Cy+E,EAAiB,GAAGJ,UACpBK,EAAiB,GAAGJ,SACpBK,EAAsB,GAAGL,cACzBM,EAAoB,GAAGN,YACvBO,EAA+B,GAAGP,wBAClCQ,EAAoB,GAAGP,SACvBQ,EAAyB,GAAGR,cAC5BS,EAAuB,GAAGT,YAC1BU,EAAkC,GAAGV,wBACrCW,EAAoB,GAAGV,SACvBW,EAAoB,GAAGX,SAGvBljD,EAAYlkC,KAAKkkC,UACvBlkC,KAAKgoF,iBAAmBtI,sBAAsB2H,EAAgBlnF,GAC9DH,KAAKioF,iBAAmBpI,sBAAsByH,EAAgBnnF,GAC9DH,KAAKkoF,sBAAwBnI,2BAA2BwH,EAAqBpnF,GAC7EH,KAAKmoF,oBAAsBlI,yBAAyByH,EAAmBvnF,GACvEH,KAAKooF,yBAA2BjI,8BAA8BwH,EAAwBxnF,GACtFH,KAAKqoF,oBAAsB7H,yBAAyBsH,EAAmB3nF,EAAQ+jC,GAC/ElkC,KAAKsoF,oBAAsB5H,yBAAyBqH,EAAmB5nF,EAAQ+jC,GAI/ElkC,KAAKuoF,oBAAsB1H,yBAAyB2G,EAAmBrnF,GACvEH,KAAKwoF,+BAAiCtH,oCAAoCuG,EAA8BtnF,GACxGH,KAAKyoF,uBAAyB1H,4BAA4B6G,EAAsBznF,GAChFH,KAAK0oF,kCAAoCtH,uCACrCyG,EACA1nF,GAKJ,MAAM6U,EAAyC,CAC3C2pE,QAAS,IAAItuD,wBACTrwB,KAAKqG,YACL4gF,EACA,CAACjnF,KAAKgoF,mBAEVW,QAAS,IAAIt4D,wBACTrwB,KAAKqG,YACL6gF,EACA,CAAClnF,KAAKioF,iBAAkBjoF,KAAKkoF,wBAEjCU,OAAQ,IAAIv4D,wBACRrwB,KAAKqG,YACL8gF,EACA,CAACnnF,KAAKmoF,oBAAqBnoF,KAAKooF,2BAEpC9H,OAAQ,IAAIjwD,wBAAwBrwB,KAAKqG,YAAa+gF,EAAgB,CAClEpnF,KAAKqoF,oBACLroF,KAAKsoF,uBAOb,OAFAtoF,KAAK6oF,wBAAwB3B,EAAiBC,GAEvCnyE,CACX,CAGA,YAAAwU,CACIrpB,GAEA,MAAM4b,EAAW/b,KAAKG,OAChBwT,EAAQxT,GAAU,CAAC,EAInB2oF,EAAcxlF,OAAOC,KAAKw5E,wBAAwBG,SAClD6L,EAAqBzlF,OAAOqP,YAC9Bm2E,EAAYtlF,IAAKymE,GAAW,CACxBA,EACA,IAAK8S,wBAAwBG,QAAQjT,MAAYt2D,EAAMupE,UAAUjT,OAInE+e,EAA2C,CAC7C/L,aAAcF,wBAAwBE,aACtCD,YAAaD,wBAAwBC,eAClCrpE,EACHupE,QAAS6L,GAsBb,OAhBA/oF,KAAKG,OAAS6oF,EACdhpF,KAAKipF,iBAAiBD,GAGjB9sF,QAAQ8sF,EAAOrL,cAAe5hE,GAAU4hE,gBACxCzhF,QAAQ8sF,EAAO/4E,QAAQwwE,WAAY1kE,GAAU9L,QAAQwwE,aACrDvkF,QAAQ8sF,EAAO/4E,QAAQ0wE,WAAY5kE,GAAU9L,QAAQ0wE,aAEtD3gF,KAAKkpF,kBAAkBF,EAAQjtE,IAG/BitE,EAAOh/C,mBAAqBjuB,GAAUiuB,oBACtChqC,KAAKmpF,4BAGTnpF,KAAKisB,gBAAgB+8D,GACdA,CACX,CAIA,yBAAAG,GACI,IAAK,MAAMnuE,IAAQ,CACfhb,KAAKgoF,iBACLhoF,KAAKioF,iBACLjoF,KAAKkoF,sBACLloF,KAAKmoF,oBACLnoF,KAAKooF,0BAELxsE,gBAAgB5b,KAAKqG,YAAa2U,EAAKvX,GAAIuX,EAAKM,SAExD,CAGA,gBAAAwO,GACI9pB,KAAK+mF,cAAgB,IACzB,CAGA,wBAAAl9D,GACI,MAAM80D,QAAEA,EAAAgK,QAASA,EAAAC,OAASA,EAAAtI,OAAQA,GAAWtgF,KAAK0mB,kBAC5CyK,EAA4B,CAC9BwtD,QAASA,EAAQluD,cACjBk4D,QAASA,EAAQl4D,cACjBm4D,OAAQA,EAAOn4D,cACf6vD,OAAQA,EAAO7vD,eAEnBzwB,KAAKsoB,sBAAsBtoB,KAAKG,QAAQ,GAEpCH,KAAK+mF,cAEL/mF,KAAK6oC,QAAQ1X,GACNnxB,KAAKG,QACZH,KAAKwpB,aAAaxpB,KAAKG,OAE/B,CAoBA,UAAM6C,CAAKiiF,SACDjlF,KAAKkpB,uBAEXlpB,KAAK+mF,cAAgB9B,EAErBjlF,KAAK6oC,QAAQ,CACT81C,QAASqG,qBAAqBC,GAC9B0D,QAASnC,mBAAmBvB,GAC5B2D,OAAQjC,sBAAsB1B,GAC9B3E,OAAQtgF,KAAKopF,6BAA6BnE,EAAUz9D,SAAUo/D,iBAAiB5mF,KAAKG,WAExF,IAAK,MAAMG,KAAWN,KAAKyI,sBACvBnI,EAAQ2kF,EAEhB,CAEA,OAAAp8C,CAAgB1X,GACZnxB,KAAK0mB,kBAAkBi4D,QAAQ37E,KAAKmuB,EAAMwtD,QAAS,CAAEhuD,qBAAqB,IAC1E3wB,KAAK0mB,kBAAkBiiE,QAAQ3lF,KAAKmuB,EAAMw3D,QAAS,CAAEh4D,qBAAqB,IAC1E3wB,KAAK0mB,kBAAkBkiE,OAAO5lF,KAAKmuB,EAAMy3D,OAAQ,CAAEj4D,qBAAqB,IACxE3wB,KAAK0mB,kBAAkB45D,OAAOt9E,KAAKmuB,EAAMmvD,OAAQ,CAAE3vD,qBAAqB,IACxE3wB,KAAKipF,iBAAiBjpF,KAAKG,QAC3BH,KAAKisB,gBAAgBjsB,KAAKG,OAC9B,CAKA,WAAMqG,SACIxG,KAAKkpB,uBACXlpB,KAAK+mF,cAAgB,KACrB/mF,KAAK0mB,kBAAkBi4D,QAAQn4E,QAC/BxG,KAAK0mB,kBAAkBiiE,QAAQniF,QAC/BxG,KAAK0mB,kBAAkBkiE,OAAOpiF,QAC9BxG,KAAK0mB,kBAAkB45D,OAAO95E,OAClC,CAkBA,UAAA6iF,CAAW9gD,EAA2C20C,GAClDl9E,KAAKspF,eAAe,UAAW/gD,EAAS20C,GACxCl9E,KAAKipF,iBAAiBjpF,KAAKG,QAC3BH,KAAKypB,kBACT,CAiBA,aAAA8/D,CAAc/K,EAAuDtB,GACjEl9E,KAAKspF,eAAe,aAAc9K,EAAYtB,GAC9Cl9E,KAAKipF,iBAAiBjpF,KAAKG,QAC3BH,KAAKypB,kBACT,CAkBA,SAAA+/D,CAAUnK,EAAyCnC,GAC/Cl9E,KAAKspF,eAAe,SAAUjK,EAAcnC,GAC5Cl9E,KAAKipF,iBAAiBjpF,KAAKG,QAC3BH,KAAKypB,kBACT,CAqBA,UAAAggE,CAAWlkF,EAAgD23E,GACvDl9E,KAAKspF,eAAe,UAAW/jF,EAAS23E,GACxCl9E,KAAK0pF,uBACL1pF,KAAKypB,kBACT,CAKA,QAAAqZ,GACI,MAAO,CACH67C,QAAS3+E,KAAK0mB,kBAAkBi4D,QAAQluD,cACxCk4D,QAAS3oF,KAAK0mB,kBAAkBiiE,QAAQl4D,cACxCm4D,OAAQ5oF,KAAK0mB,kBAAkBkiE,OAAOn4D,cAE9C,CAGA,iBAAA1E,GACI,OAAO86D,WACX,CAwBA,UAAIn8D,GACA,OAAO1qB,KAAK6qB,sBACRg8D,YACA7mF,KAAKyI,sBAEb,CAKA,cAAA6gF,CACIK,EACAjnF,EACA2O,EAA6ClU,yBAE7C,MAAM+/E,EAAU,IAAKl9E,KAAKG,QAAQ+8E,SAClC,IAAK,MAAMjT,KAAU54D,EAAS,CAC1B,MAAMu4E,EAAqC,IAAK1M,EAAQjT,SAC1C,IAAVvnE,SACOknF,EAAQD,GAEfC,EAAQD,GAASjnF,EAErBw6E,EAAQjT,GAAU2f,CACtB,CACA5pF,KAAKG,OAAS,IAAKH,KAAKG,OAAQ+8E,UACpC,CAEA,gBAAYD,GACR,OAAOoB,gBAAgBr+E,KAAKG,OAChC,CAEA,aAAY+jC,GACR,OAAOlkC,KAAKkG,UAAU8iC,mBAC1B,CAGA,eAAA/c,CAA0B9rB,GACtB,IAAwB,IAApBA,GAAQojB,QAER,YADAvjB,KAAK6pF,oBAIT,MAAMp7E,ENheD,CAAkBtO,GAC3BA,GAAQ68E,aAAeD,wBAAwBC,YM+d9B8M,CAAe3pF,GACtB4pF,EAAiB/pF,KAAK0mB,kBAAkBi4D,QAAQluD,cAAcjJ,SAASjjB,OAAS,EAChFylF,EAAahqF,KAAK0mB,kBAAkBiiE,QAAQl4D,cAAcjJ,SAASjjB,OAAS,EAC5E0lF,EAAgBjqF,KAAK0mB,kBAAkBkiE,OAAOn4D,cAAcjJ,SAASjjB,OAAS,EAC9E2lF,EAAgBlqF,KAAK0mB,kBAAkB45D,OAAO7vD,cAAcjJ,SAASjjB,OAAS,EAIpFvE,KAAKwvB,iBAAiB,CAACxvB,KAAKgoF,kBAA4B,YAATv5E,GAAsBs7E,GACrE/pF,KAAKwvB,iBACD,CAACxvB,KAAKkoF,sBAAuBloF,KAAKwoF,gCACzB,eAAT/5E,GAAyBu7E,GAE7BhqF,KAAKwvB,iBAAiB,CAACxvB,KAAKioF,iBAAkBjoF,KAAKuoF,qBAA+B,eAAT95E,GAAyBu7E,GAClGhqF,KAAKwvB,iBACD,CAACxvB,KAAKooF,yBAA0BpoF,KAAK0oF,mCAC5B,cAATj6E,GAAwBw7E,GAE5BjqF,KAAKwvB,iBACD,CAACxvB,KAAKmoF,oBAAqBnoF,KAAKyoF,wBACvB,cAATh6E,GAAwBw7E,GAE5BjqF,KAAKwvB,iBAAiB,CAACxvB,KAAKqoF,oBAAqBroF,KAAKsoF,qBAAsB4B,EAChF,CAEA,iBAAAL,GACI7pF,KAAKwvB,iBACD,CACIxvB,KAAKgoF,iBACLhoF,KAAKioF,iBACLjoF,KAAKkoF,sBACLloF,KAAKuoF,oBACLvoF,KAAKwoF,+BACLxoF,KAAKmoF,oBACLnoF,KAAKooF,yBACLpoF,KAAKyoF,uBACLzoF,KAAK0oF,kCACL1oF,KAAKqoF,oBACLroF,KAAKsoF,sBAET,EAER,CAGA,gBAAA94D,CAAyB85B,EAA0C/lC,GAC/DD,sBAAsBgmC,EAAO/lC,EAASvjB,KAAKqG,YAC/C,CAEA,uBAAAwiF,CAAgC3B,EAAyBC,GAGrD,MAAMgD,EAAA,CAAcnvE,EAAuCsB,KAAA,IACjDtB,EAAMsB,WAChB8F,UACI,CACI+nE,EAAWnqF,KAAKuoF,oBAAqBrB,GACrCiD,EAAWnqF,KAAKwoF,+BAAgCtB,GAChDiD,EAAWnqF,KAAKyoF,uBAAwBtB,GACxCgD,EAAWnqF,KAAK0oF,kCAAmCvB,IAEvDnnF,KAAKqG,YAEb,CAEA,gBAAA4iF,CAAyB9oF,GACrB,MAAM6+E,EAAgBh/E,KAAK+mF,eAAe52E,WAAWu9B,SAAS1tC,KAAKi9E,cAE7DmN,EAAiB1K,sBAAsB1/E,KAAKgoF,iBAAiBvkF,GAAItD,EAAQ6+E,GACzEqL,EAAiBxK,sBAAsB7/E,KAAKioF,iBAAiBxkF,GAAItD,EAAQ6+E,GACzEsL,EAAsBvK,2BAA2B//E,KAAKkoF,sBAAsBzkF,GAAItD,EAAQ6+E,GACxFuL,EAA+BrJ,oCACjClhF,KAAKwoF,+BAA+B/kF,GACpCtD,EACA6+E,GAEEwL,EAAoBvK,yBAAyBjgF,KAAKmoF,oBAAoB1kF,GAAItD,EAAQ6+E,GAClFyL,EAAyBtK,8BAC3BngF,KAAKooF,yBAAyB3kF,GAC9BtD,EACA6+E,GAEE0L,EAAkCtJ,uCACpCphF,KAAK0oF,kCAAkCjlF,GACvCtD,EACA6+E,GAEE2L,EAAoB9J,yBAAyB7gF,KAAKuoF,oBAAoB9kF,GAAItD,GAC1EyqF,EAAuB7J,4BAA4B/gF,KAAKyoF,uBAAuBhlF,GAAItD,GAEzFsb,iBAAiB2uE,EAAgBpqF,KAAKgoF,iBAAkBhoF,KAAKqG,aAC7DoV,iBAAiB4uE,EAAgBrqF,KAAKioF,iBAAkBjoF,KAAKqG,aAC7DoV,iBAAiB6uE,EAAqBtqF,KAAKkoF,sBAAuBloF,KAAKqG,aACvEoV,iBAAiB8uE,EAA8BvqF,KAAKwoF,+BAAgCxoF,KAAKqG,aACzFoV,iBAAiBkvE,EAAmB3qF,KAAKuoF,oBAAqBvoF,KAAKqG,aACnEoV,iBAAiB+uE,EAAmBxqF,KAAKmoF,oBAAqBnoF,KAAKqG,aACnEoV,iBAAiBgvE,EAAwBzqF,KAAKooF,yBAA0BpoF,KAAKqG,aAC7EoV,iBAAiBivE,EAAiC1qF,KAAK0oF,kCAAmC1oF,KAAKqG,aAC/FoV,iBAAiBmvE,EAAsB5qF,KAAKyoF,uBAAwBzoF,KAAKqG,aAEzErG,KAAKgoF,iBAAmBoC,EACxBpqF,KAAKioF,iBAAmBoC,EACxBrqF,KAAKkoF,sBAAwBoC,EAC7BtqF,KAAKwoF,+BAAiC+B,EACtCvqF,KAAKuoF,oBAAsBoC,EAC3B3qF,KAAKmoF,oBAAsBqC,EAC3BxqF,KAAKooF,yBAA2BqC,EAChCzqF,KAAK0oF,kCAAoCgC,EACzC1qF,KAAKyoF,uBAAyBmC,EAE9B5qF,KAAK0pF,sBACT,CAEA,iBAAAR,CACI/oF,EACA4b,GAEA,MAAMmoB,EAAYlkC,KAAKkkC,UACjB2mD,EAAoBrK,yBAAyBxgF,KAAKqoF,oBAAoB5kF,GAAItD,EAAQ+jC,GAClF4mD,EAAoBpK,yBAAyB1gF,KAAKsoF,oBAAoB7kF,GAAItD,EAAQ+jC,GAExFzoB,iBAAiBovE,EAAmB7qF,KAAKqoF,oBAAqBroF,KAAKqG,aACnEoV,iBAAiBqvE,EAAmB9qF,KAAKsoF,oBAAqBtoF,KAAKqG,aAEnErG,KAAKqoF,oBAAsBwC,EAC3B7qF,KAAKsoF,oBAAsBwC,EAG3B,MAAMC,EAAWnE,iBAAiBzmF,GAC9BH,KAAK+mF,eAAiBgE,IAAanE,iBAAiB7qE,IACpD/b,KAAK0mB,kBAAkB45D,OAAOt9E,KAC1BhD,KAAKopF,6BAA6BppF,KAAK+mF,cAAcv/D,SAAUujE,GAG3E,CAGA,oBAAArB,GACI,MAAMzf,EAASjqE,KAAKi9E,aACd/7E,EAASlB,KAAKG,QAAQ+8E,UAAUjT,IAAS1kE,QAEzCylF,EAA8C,QAChC,IAAhB9pF,GAAQkB,KAAmB4oF,EAAWriF,KAAK,CAAC,KAAM,CAAC,MAAOshE,GAAS/oE,EAAOkB,WAC1D,IAAhBlB,GAAQmB,KAAmB2oF,EAAWriF,KAAK,CAAC,KAAM,CAAC,MAAOshE,GAAS/oE,EAAOmB,MAE9E,IAAK,MAAM2Y,IAAQ,CACfhb,KAAKioF,iBACLjoF,KAAKkoF,sBACLloF,KAAKmoF,oBACLnoF,KAAKooF,0BACN,CACC,MAAM6C,EAAMjwE,EAAK9Z,OAAS,CAACkE,mBAAmB4V,EAAK9Z,SAAW,GAC9DlB,KAAKqG,YAAYgC,UAAU2S,EAAKvX,GAAI4B,gBAAgB,MAAO,IAAI4lF,KAAQD,IAC3E,CACJ,CAEA,4BAAA5B,CACI5hE,EACAujE,GAEA,MAAO,CACH1qF,KAAM,oBACNmnB,SAAUA,EAAShkB,IAAA,CAAKqT,EAASqL,KAC7B,MAAMze,EAAK,iCAAiCye,IACtCpL,EAAWi0E,EAAWxrF,KAAKsX,GAASC,SAAWD,EAAQC,SAC7D,MAAO,IAAKD,EAASpT,KAAIqT,WAAU3G,WAAY,CAAE1M,SAG7D,GChsBEynF,gBAAA,CACFC,EACAzlF,EACA0lF,EACAC,KAEI7lF,uBAAuB2lF,IACvBC,EAAYziF,KxJiMP,EACTjD,EACAxE,EACAmqF,IAEA5lF,wBACIC,EACAxE,EAAO8B,KACPqoF,EAAgBnqF,EAAOiC,OAAOK,IAAI6nF,GAAiBnqF,EAAOiC,QwJzMzCmoF,CAAkB5lF,EAAcylF,EAAcE,KAIjEE,2BAAA,CAA8BC,EAAgCJ,KAChEF,gBAAgBM,EAAUlI,eAAgB,gBAAiB8H,GAC3DF,gBAAgBM,EAAUC,kBAAmB,mBAAoBL,IAG/DM,6BAAgCF,IAClC,MAAMJ,EAA+C,GAErDG,2BAA2BC,EAAWJ,GACtCF,gBAAgBM,EAAUG,mBAAoB,kBAAmBP,EAAahtF,+BAC9E8sF,gBAAgBM,EAAUI,WAAY,qBAAsBR,EAAcviB,GACtE9qE,kBAAkB8K,QAAQggE,IAE9B,MAAMgjB,EAAcL,EAAUM,QAnC5B,GAA2BC,gBAAeC,sBAC5C,GAAIA,EAAiB,CACjB,MAAMC,EAAiD,CAAC,KAAM,CAAC,MAAO,SAA4B,GAAlBD,GAEhF,OAAOD,EACDE,EACA,CAAC,MAAO,CAAC,IAAK,CAAC,MAAO,UAAW,CAAC,KAAM,CAAC,MAAO,SAAU,GAAIA,EACxE,CACA,OAAOF,EAAgB,CAAC,IAAK,CAAC,MAAO,SAAU,GAAK,MA2BZG,CAAsBV,EAAUM,QAGxE,OAFID,GAAaT,EAAYziF,KAAKkjF,GAE3BxmF,gBAAgB,MAAO+lF,IAM5Be,wBAAA,EACAC,SAAQnB,GACVoB,IAEAhnF,gBACI,MACA,CAAC4lF,KAAmBmB,GAAO,IAAK5oF,IAAI6oF,GAAUnrF,OAAQorF,IAAoBnwF,MAAMmwF,KASlFC,wBAA2Bf,IAC7B,MAAMJ,EAA+C,GAGrD,GADAG,2BAA2BC,EAAWJ,GAClCI,EAAUgB,aAAc,CACxB,MAAMznF,EAAsC,SAA3BymF,EAAUgB,aAA0B,KAAO,KAC5DpB,EAAYziF,KAAK,CAAC5D,EAAU,CAAC,MAAO,iBAAiB,GACzD,CAEA,OAAOM,gBAAgB,MAAO+lF,IA6CrBqB,oBAAA,CACTvmF,GACE6U,WAAUlU,cAAa6lF,SACzBnnF,EACAonF,KAEA,IAAKzmF,EAAUuX,SAAU,OAEzB,MAAMvc,EAASqE,GAAWmnF,EAAMnnF,IAC5BrE,GAAUyrF,IArCL,EACTzrF,EACA+O,EACAoY,EACAxhB,KAEA,IAAK,MAAMsV,KAASlM,EAChBoY,EAAe7gB,UAAU2U,EAAM1Y,GAAIoD,EAAa3F,IA+BhD0rF,CACI1rF,QAAU,EACVuuB,sBAAsBvpB,EAAUG,YAAa,CAAC0U,IAC9C9T,oBAAoB0c,IAAIzd,GACxBW,IC/HNgmF,aAAyD,CAC3D9xE,SrHmDkC,kBqHlDlClU,YAAa,eACb6lF,MDuEqCI,GACrCX,wBAAwBW,EAAaP,0BCO5BQ,kBAAb,MAAaA,UAA0BrgE,4BAqDnC,UAAO5wB,CAAI0H,EAAgBrD,GACvB,OAAOg9B,oBAAoB35B,EAAKupF,EAAA,IAAyB,IAAIA,EAAkBvpF,EAAKrD,GAASA,EAAQ,CACjG4a,SrHrF0B,kBqHsF1BpQ,KAAM,eAEd,CAEA,WAAA9K,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,EACf,CAKA,sBAAA2oB,GACI,MAAO,CACH1f,YAAas0B,sBAAsB19B,KAAKqG,YrHnGd,kBqHmGmD0mF,EAAkBtrF,MAEvG,CAKA,YAAA+nB,CAAuBrpB,GAInB,OAHAH,KAAK+yD,YAAY5yD,GAAQojB,UAAW,GAAO,GAC3CkpE,oBAAoBzsF,KAAKkG,UAAW2mF,aAAc1sF,GAAQoF,QAASvF,KAAKG,QAAQoF,SAChF8wE,gBAAgBruE,MAAMhI,KAAKkG,UAAW/F,GAC/BA,CACX,CAKA,UAAA6pB,CAAWvmB,GACP,OAAOyD,MAAM8iB,WAAWvmB,IAAO4yE,gBAAgBn5C,QAAQl9B,KAAKkG,UAAWzC,EAC3E,CAaA,UAAAuoB,CAAWzI,GACPvjB,KAAK+yD,YAAYxvC,EACrB,CAEA,WAAAwvC,CAAoBxvC,EAAkBmG,GAAe,GAC7C1pB,KAAKkG,UAAUuX,UACfzd,KAAK0mB,kBAAkBtd,YAAYomB,iBAAiBjM,GAEpDmG,IACA1pB,KAAKG,OAAS,IAAKH,KAAKG,OAAQojB,WAChCvjB,KAAKypB,mBAEb,CAOA,SAAAyC,GACI,OAAOlsB,KAAKG,QAAQojB,UAAW,CACnC,CA+BA,QAAAuf,GACI,MAAO,CACH15B,YAAapJ,KAAK0mB,kBAAkBtd,YAAYkmB,wBAAwB9rB,IAAIoT,oBAEpF,CAEA,UAAI8T,GACA,OAAO1qB,KAAK4qB,aAAuC,CAAC,eAAgB,CAAE/D,QAASjQ,oBACnF,GC3OEo2E,UAAiD,CACnDrL,QAAS,CAAEx8C,QAAS,sCAAuCzkC,MAAO,uCAClE4iD,MAAO,CAAEne,QAAS,uCAAwCzkC,MAAO,gCACjE6iD,SAAU,CAAEpe,QAAS,0CAA2CzkC,MAAO,mCACvE8iD,MAAO,CAAEre,QAAS,uCAAwCzkC,MAAO,gCACjE+iD,WAAY,CAAEte,QAAS,yCAA0CzkC,MAAO,2CAO/DusF,kCACA,CACLtL,QAAS,qBACTr+B,MAAO,oBACPC,SAAU,mBACVC,MAAO,oBACPC,WAAY,qBANPwpC,gCAQF,CACHtL,QAAS,qBACTr+B,MAAO,qBACPC,SAAU,mBACVC,MAAO,oBACPC,WAAY,sBAKPypC,oBAAuB1pF,IAChC,MAAMyM,EAASzM,EAAIya,YAAYhO,QAAU,GACnCk9E,iBAAO,IAAI/mF,IACjB,IAAK,MAAM+V,KAASlM,EAChBk9E,EAAKnnF,IAAImW,EAAM1Y,GAAG+J,OAAQ2O,GAG9B,MAAMosB,EAA2B,CAC7BpD,QAAS,IAAK8nD,mCACdvsF,MAAO,IAAKusF,kCAEVG,EAA+B,GAErC,IAAK,MAAMvkB,KAAa9qE,kBAAmB,CACvC,MAAM0tB,EAAMuhE,UAAUnkB,GAChB1jC,EAAUkoD,cAAcF,EAAKrxF,IAAI2vB,EAAI0Z,SAAU1Z,EAAI0Z,QAASioD,GAC5D1sF,EAAQ2sF,cAAcF,EAAKrxF,IAAI2vB,EAAI/qB,OAAQ+qB,EAAI/qB,MAAO0sF,GACxDjoD,IAASoD,EAAQpD,QAAQ0jC,GAAa1jC,GACtCzkC,IAAO6nC,EAAQ7nC,MAAMmoE,GAAanoE,EAC1C,CAUA,OARI0sF,EAAmB7oF,OAAS,GAC5B8G,QAAQC,KACJ,oIAEI8hF,EAAmBxrF,KAAK,OAI7B2mC,GAGL8kD,cAAA,CAAiBlxE,EAAgB/U,EAAiBkmF,KACpD,IAAKnxE,GAA0B,iBAAVA,EAAoB,OAEzC,MAAMkF,EAASlF,EAA8BkF,MAC7C,IAAKA,GAA0B,iBAAVA,EAAoB,OAEzC,MAAM3T,EAAS2T,EAAqC,cACpD,GAAqB,iBAAV3T,EAAoB,OAAOA,OAExB,IAAVA,GAAqB4/E,EAAe3kF,KAAKvB,ICkCpCmmF,6BAAb,MAAaA,UAAqC1hE,2BAqB9C,mBAAa9lB,CACTG,EACA/F,GAGA,aADMod,oBAAoBrX,GACnB,IAAIqnF,EAA6BrnF,EAAW/F,GAAU,CAAC,EAClE,CAEA,WAAAN,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,GA1BoBH,KAAAwtF,eAAA,GACcxtF,KAAAytF,aAAA,KACsCztF,KAAAyI,sBAAA,GAyBnFi7C,oBAAoBlgD,EAAKF,OAAOH,OAAOkgD,sBAAoB,IAASrjD,KAAK+oC,aAAa/oC,KAAKG,QAC/F,CAGA,eAAA2nD,CAAwB/sC,EAAkB5a,GACtC,MAAMmb,EAAWuoB,gBAAgB1jC,GAAQ6pC,mBArID,eAsIlCxD,ER1CD,EACTA,EACAtC,KAEkB,IAAdsC,EAA4B,KAEzB,CACHppB,aAAcopB,GAAWppB,cAAgBykE,sCAAsC39C,GAC/E2yB,YAAa/0D,YACT0kC,GAAWqwB,YACX0qB,sCArBkC,MQqDpBmM,CAAsBvtF,GAAQqmC,UAAWxmC,KAAKkG,UAAU8iC,qBAC1E,MAAO,IACAo5C,iCACCrnE,EACAO,EACA4xE,oBAAoBltF,KAAKqG,aACzBmgC,EACArmC,GAAQ8P,WAETizE,mCAAmCnoE,EAAUO,EAAUnb,GAAQ8P,QAE1E,CAKA,YAAA84B,CAAqB5oC,GACjB,MAAMsqE,UAAEA,GAAczqE,KAAK0mB,kBACrBzE,EAAajiB,KAAK8nD,gBAAgB2iB,EAAUzhD,kBAAkBuG,SAAUpvB,GAC9EohB,sBAAsBU,EAAYwoD,EAAUxpE,YAAawpE,EAAWzqE,KAAKqG,aACzE,MAAMsnF,EAAgB1rE,EAAWze,IAAA,EAAOC,QAASA,GACjDgnE,EAAUxpE,YAAY0jE,KAAA,CAAMyV,EAAGrsE,IAAM4/E,EAAc9kF,QAAQuxE,EAAE32E,IAAMkqF,EAAc9kF,QAAQkF,EAAEtK,KAC3FgnE,EAAUpoD,0BACd,CAGA,sBAAAyG,CAAiC3oB,EAA6C6mF,GAC1E,MAAMjsE,EAAW,4BAA4B/a,KAAKkoB,gBAG5C0lE,ETxKD,MACT,GAAK/zE,sBAGL,OADA1M,cAAgBkN,SAASsC,SAAS2kE,YAC3Bn0E,aSoKgB0gF,GAUnB,OATID,GACAvpE,iBACI,mBTlLmC,wCSoLnCupE,EACA5tF,KAAKqG,aAIN,CACHokE,UAAW,IAAIp6C,wBACXrwB,KAAKqG,YACL0U,EACA/a,KAAK8nD,gBAAgB/sC,EAAU5a,IAG3C,CAGA,YAAAqpB,CACIrpB,GAKA,OAHAH,KAAK+oC,aAAa5oC,GAClBH,KAAKisB,gBAAgB9rB,GACrBH,KAAK8tF,iBAAiB3tF,GAAQ6pC,mBACvB7pC,CACX,CAGA,eAAA8rB,CAA0B9rB,GACtBmjB,sBACItjB,KAAK0mB,kBAAkB+jD,UAAUxpE,aACb,IAApBd,GAAQojB,QACRvjB,KAAKqG,YAEb,CAEA,gBAAAynF,CAAyB9jD,GACrB,MAAM+jD,EAAWlqD,gBAAgBmG,GA3MO,eA4MxC,IAAK,MAAMgkD,KAAOhuF,KAAK0mB,kBAAkB+jD,UAAUzhD,kBAAkBsE,SACjE1R,gBAAgB5b,KAAKqG,YAAa2nF,EAAKD,EAE/C,CAGA,gBAAAjkE,GACI9pB,KAAKwtF,eAAiB,GACtBxtF,KAAKytF,aAAe,IACxB,CAGA,wBAAA5jE,GACI,MAAMsH,EAAQnxB,KAAK0mB,kBAAkB+jD,UAAUh6C,cAC/CzwB,KAAKsoB,sBAAsBtoB,KAAKG,QAAQ,GACxCH,KAAK0mB,kBAAkB+jD,UAAUznE,KAAKmuB,EAAO,CAAER,qBAAqB,IACpE3wB,KAAKwpB,aAAaxpB,KAAKG,QACG,OAAtBH,KAAKytF,cAAyBztF,KAAKytF,aAAalpF,OAAS,GACzDvE,KAAKiuF,SAASjuF,KAAKytF,aAE3B,CAKA,UAAMzqF,CAAKgS,SACDhV,KAAKkpB,uBACXlpB,KAAKkuF,oBACLluF,KAAKytF,aAAe,KACpBztF,KAAKwtF,eAAiBx4E,EAAOwS,SACxBhkB,IAAK2qF,GAAMA,EAAEh+E,WAAW1M,IACxBvC,OAAQuC,GAAmC,iBAAPA,GACzCzD,KAAK0mB,kBAAkB+jD,UAAUznE,KAAKgS,EAAQ,CAAE2b,qBAAqB,IACrE3wB,KAAK0mB,kBAAkB+jD,UAAUj7C,iBAAiBxvB,KAAKksB,aACvD,IAAK,MAAM5rB,KAAWN,KAAKyI,sBACvBnI,EAAQ0U,EAEhB,CAKA,WAAMxO,SACIxG,KAAKkpB,uBACXlpB,KAAKkuF,oBACLluF,KAAKwtF,eAAiB,GACtBxtF,KAAKytF,aAAe,KACpBztF,KAAK0mB,kBAAkB+jD,UAAUjkE,OACrC,CAWA,QAAAs8B,GACI,MAAO,CAAE2nC,UAAWzqE,KAAK0mB,kBAAkB+jD,UAAUh6C,cACzD,CAGA,iBAAA1E,GACI,MAAO,CAAC,YACZ,CAEA,UAAIrB,GACA,OAAO1qB,KAAK6qB,sBACR,CAAC,aACD7qB,KAAKyI,sBAEb,CAWA,QAAAwlF,CAASxiE,GACL,GAAY,OAARA,GAA+B,IAAfA,EAAIlnB,OAGpB,OAFAvE,KAAKytF,aAAe,UACpBztF,KAAKkuF,oBAGT,GAAmC,IAA/BluF,KAAKwtF,eAAejpF,OAAc,OAEtCvE,KAAKytF,aAAehiE,EACpB,MAAM2iE,EAAW,IAAI95E,IAAYmX,GAC3B1Q,EAAW/a,KAAK0mB,kBAAkB+jD,UAAUzhD,kBAAkBuG,SACpE,IAAK,MAAMmC,KAAa1xB,KAAKwtF,eACzBxtF,KAAKqG,YAAYgoF,gBAAgB,CAAE/xE,OAAQvB,EAAUtX,GAAIiuB,GAAa,CAAE48D,QAASF,EAASpyF,IAAI01B,IAEtG,CAEA,iBAAAw8D,GACI,GAAmC,IAA/BluF,KAAKwtF,eAAejpF,OAAc,OAEtC,MAAMwW,EAAW/a,KAAK0mB,kBAAkB+jD,UAAUzhD,kBAAkBuG,SACpE,IAAK,MAAMmC,KAAa1xB,KAAKwtF,eACzBxtF,KAAKqG,YAAYkoF,mBAAmB,CAAEjyE,OAAQvB,EAAUtX,GAAIiuB,GAEpE,GCtUE88D,kBAAmE,CACrEzzE,SxHuCuC,uBwHtCvClU,YAAa,oBACb6lF,MJoDyC+B,GACzCtC,wBAAwBsC,EAAiB/C,+BI4ChCgD,uBAAb,MAAaA,UAA+BhiE,4BAoDxC,UAAO5wB,CAAI0H,EAAgBrD,GACvB,OAAOg9B,oBAAoB35B,EAAKkrF,EAAA,IAA8B,IAAIA,EAAuBlrF,EAAKrD,GAASA,EAAQ,CAC3G4a,SxHlH+B,uBwHmH/BpQ,KAAM,oBAEd,CAEA,WAAA9K,CAAoB2D,EAAgBrD,GAChC+G,MAAM1D,EAAKrD,EACf,CAKA,sBAAA2oB,GACI,MAAO,CACH3f,iBAAkBu0B,sBACd19B,KAAKqG,YxHjIsB,uBwHmI3BqoF,EAAuBjtF,MAGnC,CAKA,YAAA+nB,CAAuBrpB,GAQnB,OANAH,KAAK+yD,YAAY5yD,GAAQojB,UAAW,EAAO,CAAEmG,cAAc,IACtDvtB,MAAMgE,GAAQu5C,OAAOn2B,UACtBvjB,KAAK2uF,iBAAiBxuF,EAAOu5C,MAAMn2B,SAAS,GAEhDkpE,oBAAoBzsF,KAAKkG,UAAWsoF,kBAAmBruF,GAAQoF,QAASvF,KAAKG,QAAQoF,SACrF+wE,qBAAqBtuE,MAAMhI,KAAKkG,UAAW/F,GACpCA,CACX,CAKA,UAAA6pB,CAAWvmB,GACP,OAAOyD,MAAM8iB,WAAWvmB,IAAO6yE,qBAAqBp5C,QAAQl9B,KAAKkG,UAAWzC,EAChF,CAqBA,eAAAmrF,CAAgBrrE,GACZvjB,KAAK2uF,iBAAiBprE,EAC1B,CAEA,gBAAAorE,CAAyBprE,EAAkBmG,GAAe,GAClD1pB,KAAKkG,UAAUuX,UACfzd,KAAK0mB,kBAAkBvd,iBAAiBqmB,iBACpCjM,EACCZ,GAAiC,WAAnBA,EAAUtiB,MAG7BqpB,IACA1pB,KAAKG,OAAS,IAAKH,KAAKG,OAAQu5C,MAAO,IAAK15C,KAAKG,QAAQu5C,MAAOn2B,YAChEvjB,KAAKypB,mBAEb,CAiBA,UAAAuC,CAAWzI,GACPvjB,KAAK+yD,YAAYxvC,EACrB,CAEA,WAAAwvC,CAAoBxvC,EAAkB3gB,GAElC,GADqBA,GAAS8mB,cAAgB,EAC5B,CACd,MAAMgwB,MAAEA,KAAUv5C,GAAWH,KAAKG,QAAU,CAAC,EACvC0uF,EAAoBryF,KAAKk9C,EAAO,WACtC15C,KAAKG,OAAS,IAAKA,MAAalE,QAAQ4yF,IAAsB,CAAEn1C,MAAOm1C,GAAsBtrE,WAC7FvjB,KAAKypB,kBACT,CACIzpB,KAAKkG,UAAUuX,UACfzd,KAAK0mB,kBAAkBvd,iBAAiBqmB,iBAAiBjM,EAEjE,CAeA,SAAA2I,GACI,OAAOlsB,KAAKG,QAAQojB,UAAW,CACnC,CAeA,cAAAurE,GACI,OAAO9uF,KAAKG,QAAQu5C,OAAOn2B,SAAWvjB,KAAKksB,WAC/C,CAsCA,QAAA4W,GACI,MAAO,CACH35B,iBAAkBnJ,KAAK0mB,kBAAkBvd,iBACpCmmB,wBACA9rB,IAAI0U,wBAEjB,CAEA,UAAIwS,GACA,OAAO1qB,KAAK4qB,aAA4C,CAAC,oBAAqB,CAC1E/D,QAAS3O,wBAEjB,GCxVE62E,yBAAA,CAA4BC,EAAsBC,KAAA,CACpDnzC,KAAMkzC,EAAYlzC,KAAOmzC,EAAcnzC,KACvCD,IAAKmzC,EAAYnzC,IAAMozC,EAAcpzC,IACrCE,MAAOizC,EAAYjzC,MAAQkzC,EAAcnzC,KACzCozC,OAAQF,EAAYE,OAASD,EAAcpzC,MAGzCszC,0BAAA,CAA6BrrF,EAAkBsrF,EAAwBC,IACzEvrF,EAAOi4C,OAAS,GAAKj4C,EAAOg4C,MAAQszC,GAAkBtrF,EAAOorF,QAAU,GAAKprF,EAAO+3C,KAAOwzC,EAGxFC,uBAAA,CACFxrF,EACAyrF,EACAF,EACAv6D,KAEwBhxB,EAAO+3C,KACJwzC,EAAkBvrF,EAAOorF,OAEhDK,EAAe1zC,IAAMjwC,KAAKvJ,IAAIktF,EAAe1zC,IAAK/3C,EAAOorF,OAASp6D,GAElEy6D,EAAeL,OAAStjF,KAAKxJ,IAAImtF,EAAeL,OAAQprF,EAAO+3C,IAAM/mB,IAKvE06D,qBAAA,CACF1rF,EACAyrF,EACAH,EACAt6D,KAEyBhxB,EAAOg4C,MACNszC,EAAiBtrF,EAAOi4C,MAE9CwzC,EAAezzC,KAAOlwC,KAAKvJ,IAAIktF,EAAezzC,KAAMh4C,EAAOi4C,MAAQjnB,GAEnEy6D,EAAexzC,MAAQnwC,KAAKxJ,IAAImtF,EAAexzC,MAAOj4C,EAAOg4C,KAAOhnB,IAyCtE26D,8BAAA,CACF3rF,EACAyrF,EACAG,EACAC,EACA76D,KAGA,MAAM86D,EAfJ,EAA0B9rF,EAAkBsrF,EAAwBC,KAAA,CACtEvzC,KAAMlwC,KAAKvJ,IAAI,EAAGyB,EAAOg4C,MACzBD,IAAKjwC,KAAKvJ,IAAI,EAAGyB,EAAO+3C,KACxBE,MAAOnwC,KAAKxJ,IAAIgtF,EAAgBtrF,EAAOi4C,OACvCmzC,OAAQtjF,KAAKxJ,IAAIitF,EAAiBvrF,EAAOorF,UAWnBW,CAAuB/rF,EAAQ4rF,EAAYC,GAI3DG,EAAiBhsF,EAAOg4C,MAAQ,GAAKh4C,EAAOi4C,OAAS2zC,EACrDK,EAAkBjsF,EAAO+3C,KAAO,GAAK/3C,EAAOorF,QAAUS,EAG5D,GAAIG,IAAmBC,EAEnB,YADAT,uBAAuBM,EAAeL,EAAgBI,EAAa76D,GAKvE,GAAIi7D,IAAoBD,EAEpB,YADAN,qBAAqBI,EAAeL,EAAgBG,EAAY56D,GAKpE,MAAMk7D,EAAclsF,EAAOg4C,MAAQ,EAC7Bm0C,EAAensF,EAAOi4C,OAAS2zC,EAC/BQ,EAAapsF,EAAO+3C,KAAO,EAC3Bs0C,EAAgBrsF,EAAOorF,QAAUS,GAIlCK,IAAeC,GAAkBC,GAAeC,GAKhDD,IAAcC,GAAmBH,GAAgBC,EA5EpD,EACFnsF,EACAyrF,EACAH,EACAC,EACAv6D,KAEA,MAAMs7D,EAAmBtsF,EAAOg4C,KAC1Bu0C,EAAoBjB,EAAiBtrF,EAAOi4C,MAC5Cu0C,EAAkBxsF,EAAO+3C,IACzB00C,EAAqBlB,EAAkBvrF,EAAOorF,OAC9CsB,EAAc5kF,KAAKxJ,IAAIguF,EAAkBC,EAAmBC,EAAiBC,GAE/EC,IAAgBJ,EAChBb,EAAezzC,KAAOlwC,KAAKvJ,IAAIktF,EAAezzC,KAAMh4C,EAAOi4C,MAAQjnB,GAC5D07D,IAAgBH,EACvBd,EAAexzC,MAAQnwC,KAAKxJ,IAAImtF,EAAexzC,MAAOj4C,EAAOg4C,KAAOhnB,GAC7D07D,IAAgBF,EACvBf,EAAe1zC,IAAMjwC,KAAKvJ,IAAIktF,EAAe1zC,IAAK/3C,EAAOorF,OAASp6D,GAElEy6D,EAAeL,OAAStjF,KAAKxJ,IAAImtF,EAAeL,OAAQprF,EAAO+3C,IAAM/mB,IAgEzE27D,CAAyBb,EAAeL,EAAgBG,EAAYC,EAAa76D,GAN7E06D,qBAAqBI,EAAeL,EAAgBG,EAAY56D,GALhEw6D,uBAAuBM,EAAeL,EAAgBI,EAAa76D,IAoBrE47D,UAAaC,GACU,iBAAdA,EACA72E,SAASoD,cAAcyzE,GAE3BA,EASLC,eAAkBptF,GAChB,gBAAiBA,EACVA,EAAI6C,YAER7C,EAaLqtF,2BAAA,CACFrtF,EACAstF,EACAC,KAEA,MAAM9B,EAAgB2B,eAAeptF,GAAKovE,eAAeoe,yBACjDjsE,MAAO2qE,EAAY1qE,OAAQ2qE,GAAgBV,EAG7CgC,EAA8B,CAChCn1C,KAAMi1C,EACNl1C,IAAKk1C,EACLh1C,MAAO2zC,EAAaqB,EACpB7B,OAAQS,EAAcoB,GAG1B,IAAK,MAAMG,KAAoBJ,EAAqB,CAChD,MAAM9zE,EAAU0zE,UAAUQ,GAC1B,IAAKl0E,EACD,SAEJ,MAAMgyE,EAAchyE,EAAQg0E,wBACtBG,EAAgBpC,yBAAyBC,EAAaC,GAExDE,0BAA0BgC,EAAezB,EAAYC,IAIzDF,8BAA8B0B,EAAeF,EAAmBvB,EAAYC,EAAaoB,EAC7F,CAEA,OAAIE,EAAkBn1C,MAAQm1C,EAAkBl1C,OAASk1C,EAAkBp1C,KAAOo1C,EAAkB/B,OACzF,KAGJ,CAAE+B,oBAAmBvB,aAAYC,gBAe/ByB,oBAAuBxuF,IAChC,MAAMY,IAAEA,EAAAstF,oBAAKA,EAAAC,UAAqBA,EAAY,GAAMnuF,EAE9CoS,EAAS67E,2BAA2BrtF,EAAKstF,EAAqBC,GACpE,IAAK/7E,EACD,OAAO,KAGX,MAAMi8E,kBAAEA,GAAsBj8E,EACxB3O,EAAcuqF,eAAeptF,GAC7B6tF,EAAYhrF,EAAYirF,UAAU,CAACL,EAAkBn1C,KAAMm1C,EAAkB/B,SAC7EqC,EAAYlrF,EAAYirF,UAAU,CAACL,EAAkBl1C,MAAOk1C,EAAkBp1C,MAEpF,MAAO,CAACw1C,EAAUG,IAAKH,EAAUjM,IAAKmM,EAAUC,IAAKD,EAAUnM,MAgBtDqM,sBAAyB7uF,IAClC,MAAMY,IAAEA,EAAAstF,oBAAKA,GAAwBluF,EAC/BqoC,EAAOmmD,oBAAoB,CAAE5tF,MAAKstF,wBACxC,OAAK7lD,EAGE7tC,WAAW6tC,GAFP,MAoBFymD,qBAAwB9uF,IACjC,MAAMY,IAAEA,EAAAmuF,kBAAKA,EAAAb,oBAAmBA,EAAAC,UAAqBA,EAAY,GAAMnuF,EAEjEoS,EAAS67E,2BAA2BrtF,EAAKstF,EAAqBC,GACpE,IAAK/7E,EACD,OAAO,KAGX,MAAMi8E,kBAAEA,EAAAvB,WAAmBA,EAAAC,YAAYA,GAAgB36E,EAIjD48E,EAAYX,EAAkBn1C,KAAO4zC,EACrCmC,EAAaZ,EAAkBl1C,MAAQ2zC,EACvCoC,EAAWb,EAAkBp1C,IAAM8zC,EAInCoC,EAAaF,EAAaD,EAC1BI,EAJcf,EAAkB/B,OAASS,EAIbmC,GAG3BG,EAAMC,EAAOC,EAAMC,GAAST,EAM7BU,GALcF,EAAOF,GAKKF,EAC1BO,GALeF,EAAQF,GAKKF,EAI5BO,EAAeN,EAAOL,EAAYS,EAKlCG,EAAgBJ,EAAQN,EAAWQ,EAGzC,MAAO,CAACC,EAFcC,EAAgBF,EALjBC,EAAeF,EAOeG"}