@tomtom-org/maps-sdk 0.30.2 → 0.31.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2289,6 +2289,14 @@ class GeoJSONSourceWithLayers extends AddedSourceWithLayers {
2289
2289
  clear() {
2290
2290
  this.show(emptyFeatureCollection);
2291
2291
  }
2292
+ findFeature(options) {
2293
+ if ("index" in options) {
2294
+ return this.shownFeatures.features[options.index];
2295
+ } else if ("id" in options) {
2296
+ return this.shownFeatures.features.find((f) => f.id === options.id);
2297
+ }
2298
+ return void 0;
2299
+ }
2292
2300
  putEventState(options) {
2293
2301
  const mode = options.mode ?? "put";
2294
2302
  if (mode === "put") {
@@ -2298,7 +2306,7 @@ class GeoJSONSourceWithLayers extends AddedSourceWithLayers {
2298
2306
  }
2299
2307
  }
2300
2308
  }
2301
- const feature = this.shownFeatures.features[options.index];
2309
+ const feature = this.findFeature(options);
2302
2310
  if (feature) {
2303
2311
  feature.properties = { ...feature.properties, eventState: options.state };
2304
2312
  }
@@ -2307,7 +2315,7 @@ class GeoJSONSourceWithLayers extends AddedSourceWithLayers {
2307
2315
  }
2308
2316
  }
2309
2317
  cleanEventState(options) {
2310
- const feature = this.shownFeatures.features[options.index];
2318
+ const feature = this.findFeature(options);
2311
2319
  if (feature?.properties?.eventState) {
2312
2320
  delete feature?.properties?.eventState;
2313
2321
  if (options?.show !== false) {
@@ -2789,15 +2797,16 @@ const toMapDisplayPOICategory = (category) => (
2789
2797
  // if it's one of the different categories between search and poi layer, use poi layer category
2790
2798
  category in mapDisplayPoiCategoryMappings ? mapDisplayPoiCategoryMappings[category] : category.toLowerCase()
2791
2799
  );
2800
+ const MAP_BOLD_FONT = "Noto-Bold";
2801
+ const MAP_MEDIUM_FONT = "Noto-Medium";
2802
+ const DEFAULT_TEXT_SIZE = ["interpolate", ["linear"], ["zoom"], 10, 14, 18, 16];
2803
+ const PIN_ICON_SIZE = ["interpolate", ["linear"], ["zoom"], 8, 0.6, 22, 0.8];
2804
+ const SELECTED_PIN_ICON_SIZE = ["interpolate", ["linear"], ["zoom"], 8, 0.8, 22, 1];
2792
2805
  const isClickEventState = [
2793
2806
  "in",
2794
2807
  ["get", "eventState"],
2795
2808
  ["literal", ["click", "contextmenu"]]
2796
2809
  ];
2797
- const MAP_BOLD_FONT = "Noto-Bold";
2798
- const MAP_MEDIUM_FONT = "Noto-Medium";
2799
- const DEFAULT_TEXT_SIZE = ["interpolate", ["linear"], ["zoom"], 10, 14, 18, 16];
2800
- const PIN_ICON_SIZE = ["interpolate", ["linear"], ["zoom"], 8, 0.6, 22, 0.8];
2801
2810
  const TITLE = "title";
2802
2811
  const ICON_ID = "iconID";
2803
2812
  const pinIconBaseLayout = {
@@ -2817,7 +2826,8 @@ const pinTextBaseLayout = {
2817
2826
  "text-field": ["get", TITLE],
2818
2827
  "text-justify": "auto",
2819
2828
  "text-variable-anchor": ["top", "left", "right"],
2820
- "text-variable-anchor-offset": ["top", [0, 0.5], "left", [1.3, -1.4], "right", [-1.3, -1.4]],
2829
+ // 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
2830
+ "text-variable-anchor-offset": ["top", [0, 0.7], "left", [1.4, -1.4], "right", [-1.4, -1.4]],
2821
2831
  "text-size": DEFAULT_TEXT_SIZE,
2822
2832
  "text-padding": 5
2823
2833
  };
@@ -2849,8 +2859,7 @@ const selectedPlaceLayerSpec = {
2849
2859
  filter: hasEventState,
2850
2860
  layout: {
2851
2861
  ...pinLayerBaseSpec.layout,
2852
- // Increased sizes from PIN_ICON_SIZE (from shared default pin)
2853
- "icon-size": ["interpolate", ["linear"], ["zoom"], 8, 0.8, 22, 1],
2862
+ "icon-size": SELECTED_PIN_ICON_SIZE,
2854
2863
  "text-allow-overlap": true
2855
2864
  },
2856
2865
  paint: {
@@ -2858,30 +2867,27 @@ const selectedPlaceLayerSpec = {
2858
2867
  "text-color": SELECTED_COLOR
2859
2868
  }
2860
2869
  };
2861
- const clickedPlaceLayerSpec = {
2862
- ...selectedPlaceLayerSpec,
2863
- filter: isClickEventState
2864
- };
2865
- const withConfig = (layerSpec, config, id) => {
2866
- const textConfig = config?.textConfig;
2870
+ const withConfig = (layerSpec, config, layerName) => {
2871
+ const textConfig = config?.text;
2872
+ const customLayer = config?.layers?.[layerName];
2867
2873
  return {
2868
2874
  ...layerSpec,
2869
- id,
2870
2875
  layout: {
2871
2876
  ...layerSpec.layout,
2872
- ...textConfig?.textSize && { "text-size": textConfig.textSize },
2873
- ...textConfig?.textFont && { "text-font": textConfig.textFont },
2874
- ...textConfig?.textOffset && { "text-offset": textConfig.textOffset },
2875
- ...textConfig?.textField && typeof textConfig?.textField !== "function" && {
2876
- "text-field": textConfig?.textField
2877
+ ...textConfig?.size && { "text-size": textConfig.size },
2878
+ ...textConfig?.font && { "text-font": textConfig.font },
2879
+ ...textConfig?.offset && { "text-offset": textConfig.offset },
2880
+ ...textConfig?.title && typeof textConfig?.title !== "function" && {
2881
+ "text-field": textConfig?.title
2877
2882
  }
2878
2883
  },
2879
2884
  paint: {
2880
2885
  ...layerSpec.paint,
2881
- ...textConfig?.textColor && { "text-color": textConfig.textColor },
2882
- ...textConfig?.textHaloColor && { "text-halo-color": textConfig.textHaloColor },
2883
- ...textConfig?.textHaloWidth && { "text-halo-width": textConfig.textHaloWidth }
2884
- }
2886
+ ...textConfig?.color && { "text-color": textConfig.color },
2887
+ ...textConfig?.haloColor && { "text-halo-color": textConfig.haloColor },
2888
+ ...textConfig?.haloWidth && { "text-halo-width": textConfig.haloWidth }
2889
+ },
2890
+ ...customLayer
2885
2891
  };
2886
2892
  };
2887
2893
  const getTextSizeSpec = (textSize) => {
@@ -2902,14 +2908,36 @@ const buildPoiLikeLayerSpec = (map) => {
2902
2908
  }
2903
2909
  };
2904
2910
  };
2905
- const buildPlacesLayerSpecs = (config, idPrefix, map) => {
2906
- const layerSpecs = config?.iconConfig?.iconStyle === "poi-like" ? [buildPoiLikeLayerSpec(map), clickedPlaceLayerSpec] : (
2907
- // TODO: 'circle' config could take the icon properties from the poi layer as well (size, offsets, etc) and just try to make text bolder
2908
- [placesLayerSpec, selectedPlaceLayerSpec]
2909
- );
2910
- return layerSpecs.map(
2911
- (spec, index) => withConfig(spec, config, `${idPrefix}-${index === 0 ? "main" : "selected"}`)
2912
- );
2911
+ const buildPlacesLayerSpecs = (config, map) => {
2912
+ let layerSpecTemplates;
2913
+ if (config?.theme === "base-map") {
2914
+ const poiLikeLayerSpec = buildPoiLikeLayerSpec(map);
2915
+ layerSpecTemplates = {
2916
+ main: poiLikeLayerSpec,
2917
+ selected: {
2918
+ ...poiLikeLayerSpec,
2919
+ filter: hasEventState,
2920
+ layout: {
2921
+ ...poiLikeLayerSpec.layout,
2922
+ "text-allow-overlap": true
2923
+ },
2924
+ paint: {
2925
+ ...poiLikeLayerSpec.paint,
2926
+ "text-color": SELECTED_COLOR
2927
+ }
2928
+ }
2929
+ };
2930
+ } else {
2931
+ layerSpecTemplates = {
2932
+ main: placesLayerSpec,
2933
+ selected: selectedPlaceLayerSpec
2934
+ };
2935
+ }
2936
+ return {
2937
+ main: withConfig(layerSpecTemplates.main, config, "main"),
2938
+ selected: withConfig(layerSpecTemplates.selected, config, "selected"),
2939
+ ...config?.layers?.additional
2940
+ };
2913
2941
  };
2914
2942
  const supportedPinSubcategories = /* @__PURE__ */ new Set([
2915
2943
  7339002,
@@ -2966,9 +2994,9 @@ const toMapDisplayPin = (place) => {
2966
2994
  return categoryID.toString().substring(0, 4);
2967
2995
  };
2968
2996
  const buildPlaceTitle = (place) => place.properties.poi?.name ?? place.properties.address.freeformAddress;
2969
- const getIconIDForPlace = (place, config = {}, map) => {
2970
- const iconStyle = config.iconConfig?.iconStyle ?? "pin";
2971
- const customIcons = config.iconConfig?.customIcons;
2997
+ const getIconIDForPlace = (place, config = {}) => {
2998
+ const iconStyle = config.theme ?? "pin";
2999
+ const customIcons = config.icon?.customIcons;
2972
3000
  const classificationCode = place.properties.poi?.classifications?.[0]?.code;
2973
3001
  const matchingCustomIcon = customIcons?.find((customIcon) => customIcon.id === classificationCode);
2974
3002
  if (matchingCustomIcon) {
@@ -2997,7 +3025,7 @@ const preparePlacesForDisplay = (placesInput, map, config = {}) => {
2997
3025
  return {
2998
3026
  ...places,
2999
3027
  features: places.features.map((place) => {
3000
- const title = typeof config?.textConfig?.textField === "function" ? config?.textConfig?.textField(place) : buildPlaceTitle(place);
3028
+ const title = typeof config?.text?.title === "function" ? config?.text?.title(place) : buildPlaceTitle(place);
3001
3029
  const extraFeatureProps = config.extraFeatureProps ? Object.fromEntries(
3002
3030
  Object.entries(config.extraFeatureProps).map(([prop, value]) => [
3003
3031
  prop,
@@ -3015,9 +3043,7 @@ const preparePlacesForDisplay = (placesInput, map, config = {}) => {
3015
3043
  id: place.id,
3016
3044
  title,
3017
3045
  iconID: getIconIDForPlace(place, config),
3018
- ...config?.iconConfig?.iconStyle === "poi-like" && {
3019
- category: getPOILayerCategoryForPlace(place)
3020
- },
3046
+ ...config?.theme === "base-map" && { category: getPOILayerCategoryForPlace(place) },
3021
3047
  ...extraFeatureProps
3022
3048
  }
3023
3049
  };
@@ -3049,15 +3075,28 @@ const _PlacesModule = class _PlacesModule extends AbstractMapModule {
3049
3075
  this.sourceID = `${PLACES_SOURCE_PREFIX_ID}-${this.instanceIndex}`;
3050
3076
  this.layerIDPrefix = `placesSymbols-${this.instanceIndex}`;
3051
3077
  }
3052
- const layerSpecs = buildPlacesLayerSpecs(config, this.layerIDPrefix, this.mapLibreMap);
3053
- this.layerSpecs = layerSpecs;
3054
- return { places: new GeoJSONSourceWithLayers(this.mapLibreMap, this.sourceID, layerSpecs) };
3078
+ this.layerSpecs = this.buildLayerSpecs(config);
3079
+ return {
3080
+ places: new GeoJSONSourceWithLayers(this.mapLibreMap, this.sourceID, [
3081
+ this.layerSpecs.main,
3082
+ this.layerSpecs.selected
3083
+ ])
3084
+ };
3085
+ }
3086
+ buildLayerSpecs(config) {
3087
+ const layerSpecTemplates = buildPlacesLayerSpecs(config, this.mapLibreMap);
3088
+ return Object.fromEntries(
3089
+ Object.entries(layerSpecTemplates).map(([key, spec]) => [
3090
+ key,
3091
+ { ...spec, id: `${this.layerIDPrefix}-${key}` }
3092
+ ])
3093
+ );
3055
3094
  }
3056
3095
  /**
3057
3096
  * @ignore
3058
3097
  */
3059
3098
  _applyConfig(config) {
3060
- if (config?.iconConfig || config?.textConfig) {
3099
+ if (config?.theme || config?.icon || config?.text) {
3061
3100
  this.updateLayersAndData(config);
3062
3101
  } else if (config?.extraFeatureProps) {
3063
3102
  this.updateData(config);
@@ -3073,6 +3112,35 @@ const _PlacesModule = class _PlacesModule extends AbstractMapModule {
3073
3112
  this.config && this._applyConfig(this.config);
3074
3113
  this.show(previousShownFeatures);
3075
3114
  }
3115
+ /**
3116
+ * Updates the visual theme for displayed places.
3117
+ *
3118
+ * @param theme - The theme style to apply to place markers.
3119
+ *
3120
+ * @remarks
3121
+ * **Available Themes:**
3122
+ * - `pin`: Traditional teardrop-shaped map pins
3123
+ * - `circle`: Simple circular markers
3124
+ * - `base-map`: Mimics the map's built-in POI layer style with category icons
3125
+ *
3126
+ * Changes apply immediately to all currently shown places. Other configuration
3127
+ * properties (icon config, text config) remain unchanged.
3128
+ *
3129
+ * @example
3130
+ * ```typescript
3131
+ * // Switch to pin markers
3132
+ * places.applyTheme('pin');
3133
+ *
3134
+ * // Use simple circles
3135
+ * places.applyTheme('circle');
3136
+ *
3137
+ * // Match map's POI style (ideal to blend in)
3138
+ * places.applyTheme('base-map');
3139
+ * ```
3140
+ */
3141
+ applyTheme(theme) {
3142
+ this.applyConfigPart({ theme });
3143
+ }
3076
3144
  /**
3077
3145
  * Updates the icon configuration for displayed places.
3078
3146
  *
@@ -3086,21 +3154,14 @@ const _PlacesModule = class _PlacesModule extends AbstractMapModule {
3086
3154
  * @example
3087
3155
  * ```typescript
3088
3156
  * places.applyIconConfig({
3089
- * iconStyle: 'circle'
3090
- * });
3091
- *
3092
- * // Add custom icons
3093
- * places.applyIconConfig({
3094
3157
  * customIcons: [
3095
- * { category: 'RESTAURANT', image: '/icons/food.png' }
3158
+ * { category: 'RESTAURANT', id: 'restaurant-icon', image: '/icons/food.png' }
3096
3159
  * ]
3097
3160
  * });
3098
3161
  * ```
3099
3162
  */
3100
3163
  applyIconConfig(iconConfig) {
3101
- const config = { ...this.config, iconConfig };
3102
- this.updateLayersAndData(config);
3103
- this.config = config;
3164
+ this.applyConfigPart({ icon: iconConfig });
3104
3165
  }
3105
3166
  /**
3106
3167
  * Updates the text/label configuration for displayed places.
@@ -3114,19 +3175,22 @@ const _PlacesModule = class _PlacesModule extends AbstractMapModule {
3114
3175
  * ```typescript
3115
3176
  * // Use function
3116
3177
  * places.applyTextConfig({
3117
- * textField: (place) => place.properties.poi?.name || 'Unknown'
3178
+ * field: (place) => place.properties.poi?.name || 'Unknown'
3118
3179
  * });
3119
3180
  *
3120
3181
  * // Use MapLibre expression
3121
3182
  * places.applyTextConfig({
3122
- * textField: ['get', 'title'],
3123
- * textSize: 14,
3124
- * textColor: '#333'
3183
+ * field: ['get', 'title'],
3184
+ * size: 14,
3185
+ * color: '#333'
3125
3186
  * });
3126
3187
  * ```
3127
3188
  */
3128
3189
  applyTextConfig(textConfig) {
3129
- const config = { ...this.config, textConfig };
3190
+ this.applyConfigPart({ text: textConfig });
3191
+ }
3192
+ applyConfigPart(partialConfig) {
3193
+ const config = { ...this.config, ...partialConfig };
3130
3194
  this.updateLayersAndData(config);
3131
3195
  this.config = config;
3132
3196
  }
@@ -3153,13 +3217,15 @@ const _PlacesModule = class _PlacesModule extends AbstractMapModule {
3153
3217
  this.config = config;
3154
3218
  }
3155
3219
  updateLayersAndData(config) {
3156
- for (const customIcon of config?.iconConfig?.customIcons ?? []) {
3220
+ for (const customIcon of config?.icon?.customIcons ?? []) {
3157
3221
  addImageIfNotExisting(this.mapLibreMap, customIcon.id, customIcon.image, {
3158
3222
  pixelRatio: customIcon.pixelRatio ?? 2
3159
3223
  });
3160
3224
  }
3161
- const newLayerSpecs = buildPlacesLayerSpecs(config, this.layerIDPrefix, this.mapLibreMap);
3162
- changeLayersProps(newLayerSpecs, this.layerSpecs, this.mapLibreMap);
3225
+ const newLayerSpecs = this.buildLayerSpecs(config);
3226
+ const newLayerSpecsArray = [newLayerSpecs.main, newLayerSpecs.selected];
3227
+ const oldLayerSpecsArray = [this.layerSpecs.main, this.layerSpecs.selected];
3228
+ changeLayersProps(newLayerSpecsArray, oldLayerSpecsArray, this.mapLibreMap);
3163
3229
  this.layerSpecs = newLayerSpecs;
3164
3230
  this.updateData(config);
3165
3231
  }
@@ -4213,7 +4279,7 @@ const buildGeometryLayerSpecs = (fillLayerId, outlineLayerId, config) => {
4213
4279
  };
4214
4280
  return [fillLayerSpec, outlineLayerSpec];
4215
4281
  };
4216
- const buildTitle = (feature, config) => {
4282
+ const buildTitle$1 = (feature, config) => {
4217
4283
  if (config.textConfig?.textField) {
4218
4284
  return config.textConfig.textField;
4219
4285
  }
@@ -4251,7 +4317,7 @@ const buildGeometryTitleLayerSpec = (layerId, config) => {
4251
4317
  const prepareGeometryForDisplay = (geometry, config = {}) => ({
4252
4318
  ...geometry,
4253
4319
  features: geometry.features.map((feature, index) => {
4254
- const title = feature.properties?.title ? feature.properties.title : buildTitle(feature, config);
4320
+ const title = feature.properties?.title ? feature.properties.title : buildTitle$1(feature, config);
4255
4321
  const color = feature.properties?.color ? feature.properties.color : buildColor(config, index);
4256
4322
  return { ...feature, properties: { ...feature.properties, title, color } };
4257
4323
  })
@@ -4969,9 +5035,7 @@ const EXTRA_FOREGROUND_LINE_WIDTH = [
4969
5035
  ];
4970
5036
  const routeIncidentsBGLine = {
4971
5037
  type: "line",
4972
- layout: {
4973
- "line-join": "round"
4974
- },
5038
+ layout: { "line-cap": "round" },
4975
5039
  paint: {
4976
5040
  "line-width": EXTRA_FOREGROUND_LINE_WIDTH,
4977
5041
  "line-color": [
@@ -4991,9 +5055,7 @@ const routeIncidentsBGLine = {
4991
5055
  const routeIncidentsDashedLine = {
4992
5056
  type: "line",
4993
5057
  filter: ["in", ["get", "magnitudeOfDelay"], ["literal", ["unknown", "indefinite"]]],
4994
- layout: {
4995
- "line-join": "round"
4996
- },
5058
+ layout: { "line-join": "round" },
4997
5059
  paint: {
4998
5060
  "line-width": EXTRA_FOREGROUND_LINE_WIDTH,
4999
5061
  "line-color": [
@@ -5007,28 +5069,6 @@ const routeIncidentsDashedLine = {
5007
5069
  "line-dasharray": [1.5, 1]
5008
5070
  }
5009
5071
  };
5010
- const routeIncidentsPatternLine = {
5011
- type: "line",
5012
- filter: ["in", ["get", "magnitudeOfDelay"], ["literal", ["minor", "moderate", "major"]]],
5013
- layout: {
5014
- "line-join": "round"
5015
- },
5016
- paint: {
5017
- "line-width": EXTRA_FOREGROUND_LINE_WIDTH,
5018
- "line-pattern": [
5019
- "match",
5020
- ["get", "magnitudeOfDelay"],
5021
- "minor",
5022
- "traffic-incidents-minor-pattern",
5023
- "moderate",
5024
- "traffic-incidents-moderate-pattern",
5025
- "major",
5026
- "traffic-incidents-major-pattern",
5027
- // other
5028
- "traffic-incidents-no_delay-pattern"
5029
- ]
5030
- }
5031
- };
5032
5072
  const magnitudeOfDelayTextColor = [
5033
5073
  "match",
5034
5074
  ["get", "magnitudeOfDelay"],
@@ -5043,29 +5083,48 @@ const magnitudeOfDelayTextColor = [
5043
5083
  // other
5044
5084
  UNKNOWN_DELAY_COLOR
5045
5085
  ];
5046
- const routeIncidentsSymbol = {
5086
+ const routeIncidentsSymbolBase = {
5047
5087
  filter: SELECTED_ROUTE_FILTER,
5048
5088
  type: "symbol",
5049
5089
  minzoom: 6,
5050
5090
  layout: {
5051
5091
  "symbol-placement": "point",
5052
5092
  "symbol-avoid-edges": true,
5053
- "icon-anchor": "bottom",
5054
- "icon-ignore-placement": true,
5055
- "icon-size": ["interpolate", ["linear"], ["zoom"], 6, 0.8, 12, 1],
5056
- "icon-image": ["get", "iconID"],
5093
+ "icon-ignore-placement": true
5094
+ }
5095
+ };
5096
+ const routeIncidentsJamSymbol = {
5097
+ ...routeIncidentsSymbolBase,
5098
+ filter: ["all", ["has", "jamIconID"], routeIncidentsSymbolBase.filter],
5099
+ layout: {
5100
+ ...routeIncidentsSymbolBase.layout,
5101
+ "icon-image": ["get", "jamIconID"],
5102
+ "icon-anchor": "bottom-left",
5103
+ "text-anchor": "bottom-left",
5104
+ // Jam symbols have delay labels in them:
5057
5105
  "text-field": ["get", "title"],
5058
5106
  "text-font": [MAP_BOLD_FONT],
5059
- "text-optional": true,
5060
- "text-anchor": "top",
5061
- "text-size": ["interpolate", ["linear"], ["zoom"], 6, 11, 10, 13]
5107
+ "text-offset": [3.9, -1.4],
5108
+ "text-size": 13
5062
5109
  },
5063
5110
  paint: {
5111
+ ...routeIncidentsSymbolBase.paint,
5064
5112
  "text-color": magnitudeOfDelayTextColor,
5065
5113
  "text-halo-color": "#FFFFFF",
5066
5114
  "text-halo-width": 1
5067
5115
  }
5068
5116
  };
5117
+ const routeIncidentsCauseSymbol = {
5118
+ ...routeIncidentsSymbolBase,
5119
+ filter: ["all", ["has", "causeIconID"], routeIncidentsSymbolBase.filter],
5120
+ layout: {
5121
+ ...routeIncidentsSymbolBase.layout,
5122
+ "icon-image": ["get", "causeIconID"],
5123
+ "icon-anchor": "bottom-right"
5124
+ // Cause symbols have no label in them.
5125
+ },
5126
+ paint: { ...routeIncidentsSymbolBase.paint }
5127
+ };
5069
5128
  const routeTunnelsLine = {
5070
5129
  filter: SELECTED_ROUTE_FILTER,
5071
5130
  type: "line",
@@ -5290,27 +5349,26 @@ const buildRoutingLayers = (config = {}) => {
5290
5349
  },
5291
5350
  sections: {
5292
5351
  incident: {
5293
- routeIncidentSymbol: {
5294
- ...routeIncidentsSymbol,
5352
+ routeIncidentJamSymbol: {
5353
+ ...routeIncidentsJamSymbol,
5295
5354
  beforeID: "routeChargingStopSymbol",
5296
- ...configSectionLayers?.incident?.routeIncidentSymbol
5355
+ ...configSectionLayers?.incident?.routeIncidentJamSymbol
5356
+ },
5357
+ routeIncidentCauseSymbol: {
5358
+ ...routeIncidentsCauseSymbol,
5359
+ beforeID: "routeChargingStopSymbol",
5360
+ ...configSectionLayers?.incident?.routeIncidentCauseSymbol
5297
5361
  },
5298
5362
  routeIncidentBackgroundLine: {
5299
5363
  ...routeIncidentsBGLine,
5300
- beforeID: mapStyleLayerIDs.lowestLabel,
5364
+ beforeID: "routeIncidentDashedLine",
5301
5365
  ...configSectionLayers?.incident?.routeIncidentBackgroundLine
5302
5366
  },
5303
5367
  routeIncidentDashedLine: {
5304
5368
  ...routeIncidentsDashedLine,
5305
5369
  beforeID: mapStyleLayerIDs.lowestLabel,
5306
5370
  ...configSectionLayers?.incident?.routeIncidentDashedLine
5307
- },
5308
- routeIncidentPatternLine: {
5309
- ...routeIncidentsPatternLine,
5310
- beforeID: mapStyleLayerIDs.lowestLabel,
5311
- ...configSectionLayers?.incident?.routeIncidentPatternLine
5312
- },
5313
- ...configLayers?.sections?.incident?.additional
5371
+ }
5314
5372
  },
5315
5373
  ferry: {
5316
5374
  routeFerryLine: {
@@ -5320,7 +5378,7 @@ const buildRoutingLayers = (config = {}) => {
5320
5378
  },
5321
5379
  routeFerrySymbol: {
5322
5380
  ...routeFerriesSymbol,
5323
- beforeID: "routeIncidentSymbol",
5381
+ beforeID: "routeIncidentJamSymbol",
5324
5382
  ...configSectionLayers?.ferry?.routeFerrySymbol
5325
5383
  },
5326
5384
  ...configSectionLayers?.ferry?.additional
@@ -5500,14 +5558,14 @@ const e = { commonBaseURL: "https://api.tomtom.com", apiKey: "", apiVersion: 1 }
5500
5558
  };
5501
5559
  t.instance = new t();
5502
5560
  let r = t;
5503
- const G = (e2) => e2?.minutes ?? "min", F = (e2, t2) => {
5561
+ const F = (e2) => e2?.minutes ?? "min", L = (e2, t2) => {
5504
5562
  if (e2) {
5505
5563
  const a = Math.abs(e2) / 3600;
5506
5564
  let o = Math.floor(a), i = Math.round(a % 1 * 60);
5507
5565
  60 === i && (i = 0, o++);
5508
5566
  const s = { ...r.instance.get().displayUnits?.time, ...t2 };
5509
- if (o) return `${o} ${n = s, n?.hours ?? "hr"} ${i.toString().padStart(2, "0")} ${G(s)}`;
5510
- if (i) return `${i.toString()} ${G(s)}`;
5567
+ if (o) return `${o} ${n = s, n?.hours ?? "hr"} ${i.toString().padStart(2, "0")} ${F(s)}`;
5568
+ if (i) return `${i.toString()} ${F(s)}`;
5511
5569
  }
5512
5570
  var n;
5513
5571
  };
@@ -5547,7 +5605,7 @@ const toDisplayChargingStops = (routes, config) => {
5547
5605
  iconID: getIconID(chargingStop, config),
5548
5606
  title: formatTitle(chargingStop),
5549
5607
  chargingPower: `${properties.chargingConnectionInfo?.chargingPowerInkW} kW`,
5550
- chargingDuration: F(
5608
+ chargingDuration: L(
5551
5609
  properties.chargingTimeInSeconds,
5552
5610
  config?.displayUnits?.time
5553
5611
  ),
@@ -5560,42 +5618,71 @@ const toDisplayChargingStops = (routes, config) => {
5560
5618
  }
5561
5619
  return { type: "FeatureCollection", features: displayChargingStops };
5562
5620
  };
5563
- const delayMagnitudeToIconPrefix = {
5564
- unknown: "traffic-incidents-no_delay",
5565
- minor: "traffic-incidents-minor",
5566
- moderate: "traffic-incidents-moderate",
5567
- major: "traffic-incidents-major",
5568
- indefinite: "traffic-incidents-no_delay"
5569
- };
5570
- const tecCauseToIconSuffix = {
5571
- 1: "jam",
5572
- 2: "accident",
5573
- 3: "roadworks",
5574
- 5: "road_closed",
5575
- 9: "danger",
5576
- 16: "lane_closed",
5577
- 17: "weather_wind",
5578
- 18: "weather_fog",
5579
- 19: "weather_rain"
5580
- };
5581
- const trafficSectionToIconID = (sectionProps) => {
5582
- const tecCauseCode = sectionProps.tec.causes?.[0].mainCauseCode;
5583
- const tecIconSuffix = tecCauseCode && tecCauseToIconSuffix[tecCauseCode];
5584
- if (!tecIconSuffix) {
5621
+ const hasJam = (sectionProps) => sectionProps.categories.includes("jam");
5622
+ const buildTitle = (sectionProps) => {
5623
+ if (hasJam(sectionProps)) {
5624
+ return core.formatDuration(sectionProps.delayInSeconds);
5625
+ }
5626
+ return void 0;
5627
+ };
5628
+ const toTrafficJamIconSuffix = (title) => {
5629
+ if (!title?.length) {
5630
+ return "collapsed";
5631
+ }
5632
+ if (title.length < 6) {
5633
+ return "small";
5634
+ }
5635
+ if (title.length < 8) {
5636
+ return "medium";
5637
+ }
5638
+ return "large";
5639
+ };
5640
+ const toJamIconID = (sectionProps, title) => {
5641
+ if (!hasJam(sectionProps)) {
5585
5642
  return null;
5586
5643
  }
5587
- const magnitudePrefix = (
5588
- // ("traffic-incidents-road_closed" is an exception)
5589
- tecIconSuffix === "road_closed" ? "traffic-incidents" : delayMagnitudeToIconPrefix[sectionProps.magnitudeOfDelay ?? "unknown"]
5590
- );
5591
- return `${magnitudePrefix}-${tecIconSuffix}`;
5644
+ const magnitude = sectionProps.magnitudeOfDelay ?? "unknown";
5645
+ return `traffic-jam-${magnitude}-${toTrafficJamIconSuffix(title)}`;
5646
+ };
5647
+ const toCauseIconID = (sectionProps) => {
5648
+ const firstNonJamCategory = sectionProps.categories.find((category) => category !== "jam");
5649
+ switch (firstNonJamCategory) {
5650
+ case "accident":
5651
+ return "traffic-incidents-accident";
5652
+ case "roadworks":
5653
+ return "traffic-incidents-roadworks";
5654
+ case "road-closed":
5655
+ return "traffic-incidents-road_closed";
5656
+ case "danger":
5657
+ case "animals-on-road":
5658
+ return "traffic-incidents-danger";
5659
+ case "broken-down-vehicle":
5660
+ return "traffic-incidents-broken_down_vehicle";
5661
+ case "lane-closed":
5662
+ case "narrow-lanes":
5663
+ return "traffic-incidents-lane_closed";
5664
+ case "wind":
5665
+ return "traffic-incidents-wind";
5666
+ case "fog":
5667
+ return "traffic-incidents-fog";
5668
+ case "rain":
5669
+ return "traffic-incidents-rain";
5670
+ case "frost":
5671
+ return "traffic-incidents-frost";
5672
+ case "flooding":
5673
+ return "traffic-incidents-flooding";
5674
+ default:
5675
+ return null;
5676
+ }
5592
5677
  };
5593
5678
  const toDisplayTrafficSectionProps = (sectionProps) => {
5594
- const title = core.formatDuration(sectionProps.delayInSeconds);
5595
- const iconId = trafficSectionToIconID(sectionProps);
5679
+ const title = buildTitle(sectionProps);
5680
+ const jamIconID = toJamIconID(sectionProps, title);
5681
+ const causeIconID = toCauseIconID(sectionProps);
5596
5682
  return {
5597
5683
  ...sectionProps,
5598
- ...iconId && { iconID: iconId },
5684
+ ...jamIconID && { jamIconID },
5685
+ ...causeIconID && { causeIconID },
5599
5686
  ...title && { title }
5600
5687
  };
5601
5688
  };
@@ -5974,11 +6061,12 @@ class RoutingModule extends AbstractMapModule {
5974
6061
  const mergedConfig = routeModuleConfigWithDefaults(config);
5975
6062
  if (this.config) {
5976
6063
  const newLayersSpecs = createLayersSpecs(mergedConfig.layers);
5977
- Object.keys(newLayersSpecs).forEach((layersSpecs) => {
6064
+ Object.keys(newLayersSpecs).forEach((layersSpecID) => {
6065
+ const id = layersSpecID;
5978
6066
  updateLayersAndSource(
5979
- newLayersSpecs[layersSpecs],
5980
- this.layersSpecs[layersSpecs],
5981
- this.sourcesWithLayers[layersSpecs],
6067
+ newLayersSpecs[id],
6068
+ this.layersSpecs[id],
6069
+ this.sourcesWithLayers[id],
5982
6070
  this.mapLibreMap
5983
6071
  );
5984
6072
  });
@@ -6211,6 +6299,7 @@ class RoutingModule extends AbstractMapModule {
6211
6299
  return mapStyleLayerIDs.lowestLabel;
6212
6300
  }
6213
6301
  }
6302
+ const version = "5.12.0";
6214
6303
  const DEFAULT_PUBLISHED_STYLE = "standardLight";
6215
6304
  const URL_PREFIX = "${baseURL}/maps/orbis/assets/styles/${version}/style.json?&apiVersion=1&key=${apiKey}";
6216
6305
  const publishedStyleModulesValues = {
@@ -6260,7 +6349,7 @@ const baseMapStyleUrlTemplates = {
6260
6349
  monoDark: baseMapStyleUrlTemplate("basic_mono-dark"),
6261
6350
  satellite: baseMapStyleUrlTemplate("basic_street-satellite")
6262
6351
  };
6263
- const buildBaseMapStyleUrl = (publishedStyle, baseUrl, apiKey) => baseMapStyleUrlTemplates[publishedStyle?.id ?? DEFAULT_PUBLISHED_STYLE].replace("${baseURL}", baseUrl).replace("${version}", publishedStyle.version ?? "0.*").replace("${apiKey}", apiKey);
6352
+ const buildBaseMapStyleUrl = (publishedStyle, baseUrl, apiKey) => baseMapStyleUrlTemplates[publishedStyle?.id ?? DEFAULT_PUBLISHED_STYLE].replace("${baseURL}", baseUrl).replace("${version}", publishedStyle.version ?? "0.6.0-0").replace("${apiKey}", apiKey);
6264
6353
  const withApiKey = (givenUrl, apiKey) => {
6265
6354
  const url = new URL(givenUrl);
6266
6355
  if (!url.searchParams.has("key")) {
@@ -6436,9 +6525,13 @@ class TomTomMap {
6436
6525
  return this._params.style;
6437
6526
  };
6438
6527
  this._params = core.mergeFromGlobal(mapParams);
6528
+ this.ensureMapLibreCSSLoaded();
6439
6529
  this.mapLibreMap = new maplibreGl.Map(buildMapOptions(mapLibreOptions, this._params));
6440
6530
  this.mapLibreMap.once("styledata", () => this.handleStyleData(false));
6441
6531
  this._eventsProxy = new EventsProxy(this.mapLibreMap, this._params?.eventsConfig);
6532
+ this.loadRTLTextPlugin();
6533
+ }
6534
+ loadRTLTextPlugin() {
6442
6535
  setTimeout(() => {
6443
6536
  if (!["deferred", "loaded"].includes(maplibreGl.getRTLTextPluginStatus())) {
6444
6537
  maplibreGl.setRTLTextPlugin(
@@ -6448,6 +6541,24 @@ class TomTomMap {
6448
6541
  }
6449
6542
  });
6450
6543
  }
6544
+ /**
6545
+ * Dynamically loads the MapLibre CSS stylesheet from CDN.
6546
+ */
6547
+ ensureMapLibreCSSLoaded() {
6548
+ if (typeof document === "undefined") {
6549
+ return;
6550
+ }
6551
+ const existingLink = Array.from(document.querySelectorAll('link[rel="stylesheet"], style')).some(
6552
+ (element) => element.textContent?.includes("maplibre")
6553
+ );
6554
+ if (existingLink) {
6555
+ return;
6556
+ }
6557
+ const link = document.createElement("link");
6558
+ link.rel = "stylesheet";
6559
+ link.href = `https://unpkg.com/maplibre-gl@${version}/dist/maplibre-gl.css`;
6560
+ document.head.appendChild(link);
6561
+ }
6451
6562
  _setLanguage(language) {
6452
6563
  this._params = { ...this._params, language };
6453
6564
  const mapLanguage = language?.includes("-") ? language.split("-")[0] : language;