@accelint/map-toolkit 1.5.0 → 3.0.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.
Files changed (224) hide show
  1. package/CHANGELOG.md +83 -0
  2. package/README.md +8 -1
  3. package/catalog-info.yaml +9 -6
  4. package/dist/camera/events.js +1 -1
  5. package/dist/camera/index.d.ts +1 -1
  6. package/dist/camera/index.js +1 -1
  7. package/dist/camera/store.d.ts +1 -1
  8. package/dist/camera/store.js +3 -5
  9. package/dist/camera/store.js.map +1 -1
  10. package/dist/camera/types.d.ts +1 -1
  11. package/dist/camera/types.js +1 -1
  12. package/dist/cursor-coordinates/constants.js +1 -1
  13. package/dist/cursor-coordinates/index.d.ts +1 -1
  14. package/dist/cursor-coordinates/index.js +1 -1
  15. package/dist/cursor-coordinates/store.d.ts +1 -1
  16. package/dist/cursor-coordinates/store.js +1 -1
  17. package/dist/cursor-coordinates/types.d.ts +1 -1
  18. package/dist/cursor-coordinates/types.js +1 -1
  19. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
  20. package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
  21. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  22. package/dist/deckgl/base-map/constants.js +1 -1
  23. package/dist/deckgl/base-map/controls.d.ts +1 -1
  24. package/dist/deckgl/base-map/controls.js +1 -1
  25. package/dist/deckgl/base-map/events.js +1 -1
  26. package/dist/deckgl/base-map/index.d.ts +3 -3
  27. package/dist/deckgl/base-map/index.js +1 -1
  28. package/dist/deckgl/base-map/provider.d.ts +1 -1
  29. package/dist/deckgl/base-map/provider.js +1 -1
  30. package/dist/deckgl/base-map/types.d.ts +1 -1
  31. package/dist/deckgl/base-map/types.js +1 -1
  32. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.d.ts +144 -0
  33. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js +535 -0
  34. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js.map +1 -0
  35. package/dist/deckgl/extensions/coffin-corner/index.d.ts +17 -0
  36. package/dist/deckgl/extensions/coffin-corner/index.js +19 -0
  37. package/dist/deckgl/extensions/coffin-corner/store.d.ts +96 -0
  38. package/dist/deckgl/extensions/coffin-corner/store.js +173 -0
  39. package/dist/deckgl/extensions/coffin-corner/store.js.map +1 -0
  40. package/dist/deckgl/extensions/coffin-corner/types.d.ts +76 -0
  41. package/dist/deckgl/extensions/coffin-corner/types.js +27 -0
  42. package/dist/deckgl/extensions/coffin-corner/types.js.map +1 -0
  43. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts +81 -0
  44. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js +75 -0
  45. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js.map +1 -0
  46. package/dist/deckgl/extensions/index.d.ts +15 -0
  47. package/dist/deckgl/extensions/index.js +16 -0
  48. package/dist/deckgl/index.d.ts +9 -4
  49. package/dist/deckgl/index.js +6 -2
  50. package/dist/deckgl/saved-viewports/index.d.ts +1 -1
  51. package/dist/deckgl/saved-viewports/index.js +1 -1
  52. package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
  53. package/dist/deckgl/saved-viewports/storage.js +5 -10
  54. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  55. package/dist/deckgl/shapes/display-shape-layer/constants.js +70 -26
  56. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  57. package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
  58. package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
  59. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +93 -38
  60. package/dist/deckgl/shapes/display-shape-layer/index.js +433 -187
  61. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  62. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
  63. package/dist/deckgl/shapes/display-shape-layer/store.js +1 -1
  64. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +116 -19
  65. package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
  66. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
  67. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
  68. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
  69. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  70. package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
  71. package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
  72. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +106 -0
  73. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
  74. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
  75. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
  76. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  77. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js +53 -0
  78. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js.map +1 -0
  79. package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
  80. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
  81. package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
  82. package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
  83. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +8 -4
  84. package/dist/deckgl/shapes/draw-shape-layer/index.js +11 -17
  85. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
  86. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +6 -5
  87. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  88. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +4 -3
  89. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  90. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +6 -20
  91. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  92. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +6 -33
  93. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  94. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +16 -12
  95. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  96. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
  97. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  98. package/dist/deckgl/shapes/draw-shape-layer/store.js +1 -1
  99. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +3 -3
  100. package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
  101. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
  102. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +1 -1
  103. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +3 -8
  104. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  105. package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
  106. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
  107. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
  108. package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
  109. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
  110. package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
  111. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +12 -6
  112. package/dist/deckgl/shapes/edit-shape-layer/index.js +72 -27
  113. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  114. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
  115. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  116. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +4 -3
  117. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  118. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +5 -3
  119. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  120. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +1 -1
  121. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
  122. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
  123. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +1 -1
  124. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  125. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
  126. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  127. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
  128. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  129. package/dist/deckgl/shapes/edit-shape-layer/store.js +83 -14
  130. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  131. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +18 -4
  132. package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
  133. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +2 -2
  134. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +8 -4
  135. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
  136. package/dist/deckgl/shapes/index.d.ts +5 -4
  137. package/dist/deckgl/shapes/index.js +3 -2
  138. package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
  139. package/dist/deckgl/shapes/shared/constants.js +51 -11
  140. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  141. package/dist/deckgl/shapes/shared/events.d.ts +5 -1
  142. package/dist/deckgl/shapes/shared/events.js +1 -1
  143. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  144. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
  145. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  146. package/dist/deckgl/shapes/shared/types.d.ts +182 -54
  147. package/dist/deckgl/shapes/shared/types.js +155 -2
  148. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  149. package/dist/deckgl/shapes/shared/utils/duplicate-shape.d.ts +56 -0
  150. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js +131 -0
  151. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js.map +1 -0
  152. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
  153. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  154. package/dist/deckgl/shapes/shared/utils/layer-config.js +15 -9
  155. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
  156. package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
  157. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
  158. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
  159. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  160. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
  161. package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
  162. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
  163. package/dist/deckgl/symbol-layer/fiber.d.ts +4 -2
  164. package/dist/deckgl/symbol-layer/fiber.js +1 -1
  165. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  166. package/dist/deckgl/symbol-layer/index.d.ts +1 -1
  167. package/dist/deckgl/symbol-layer/index.js +1 -1
  168. package/dist/deckgl/text-layer/character-sets.js +1 -1
  169. package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
  170. package/dist/deckgl/text-layer/default-settings.js +1 -1
  171. package/dist/deckgl/text-layer/fiber.d.ts +1 -1
  172. package/dist/deckgl/text-layer/fiber.js +1 -1
  173. package/dist/deckgl/text-layer/index.d.ts +1 -1
  174. package/dist/deckgl/text-layer/index.js +1 -1
  175. package/dist/deckgl/text-settings.d.ts +3 -3
  176. package/dist/deckgl/text-settings.js +1 -1
  177. package/dist/map-cursor/events.js +1 -1
  178. package/dist/map-cursor/index.d.ts +1 -1
  179. package/dist/map-cursor/index.js +1 -1
  180. package/dist/map-cursor/store.d.ts +1 -1
  181. package/dist/map-cursor/store.js +1 -1
  182. package/dist/map-cursor/types.d.ts +1 -1
  183. package/dist/map-cursor/types.js +1 -1
  184. package/dist/map-cursor/use-map-cursor.d.ts +1 -1
  185. package/dist/map-cursor/use-map-cursor.js +1 -1
  186. package/dist/map-mode/events.js +1 -1
  187. package/dist/map-mode/index.d.ts +1 -1
  188. package/dist/map-mode/index.js +1 -1
  189. package/dist/map-mode/store.d.ts +1 -1
  190. package/dist/map-mode/store.js +3 -8
  191. package/dist/map-mode/store.js.map +1 -1
  192. package/dist/map-mode/types.d.ts +1 -1
  193. package/dist/map-mode/types.js +1 -1
  194. package/dist/map-mode/use-map-mode.d.ts +1 -1
  195. package/dist/map-mode/use-map-mode.js +1 -1
  196. package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
  197. package/dist/maplibre/hooks/use-maplibre.js +1 -1
  198. package/dist/maplibre/index.d.ts +1 -1
  199. package/dist/maplibre/index.js +1 -1
  200. package/dist/shared/cleanup.d.ts +1 -1
  201. package/dist/shared/cleanup.js +1 -1
  202. package/dist/shared/constants.js +1 -1
  203. package/dist/shared/create-map-store.d.ts +1 -1
  204. package/dist/shared/create-map-store.js +1 -1
  205. package/dist/shared/logger.js +31 -0
  206. package/dist/shared/logger.js.map +1 -0
  207. package/dist/shared/units.d.ts +15 -56
  208. package/dist/shared/units.js +2 -53
  209. package/dist/shared/units.js.map +1 -1
  210. package/dist/viewport/index.d.ts +3 -4
  211. package/dist/viewport/index.js +2 -3
  212. package/dist/viewport/store.d.ts +1 -1
  213. package/dist/viewport/store.js +1 -1
  214. package/dist/viewport/types.d.ts +9 -5
  215. package/dist/viewport/types.js +1 -1
  216. package/dist/viewport/utils.d.ts +4 -4
  217. package/dist/viewport/utils.js +17 -9
  218. package/dist/viewport/utils.js.map +1 -1
  219. package/dist/viewport/viewport-size.d.ts +7 -6
  220. package/dist/viewport/viewport-size.js +3 -3
  221. package/dist/viewport/viewport-size.js.map +1 -1
  222. package/package.json +29 -20
  223. package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
  224. package/dist/hotkey-manager/dist/react/use-hotkey.js.map +0 -1
@@ -1,5 +1,5 @@
1
1
  /*
2
- * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
3
  * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
4
  * you may not use this file except in compliance with the License. You may obtain a copy
5
5
  * of the License at https://www.apache.org/licenses/LICENSE-2.0
@@ -14,24 +14,22 @@
14
14
  'use client';
15
15
 
16
16
  import { ShapeEvents } from "../shared/events.js";
17
- import { DASH_ARRAYS, SHAPE_LAYER_IDS } from "../shared/constants.js";
17
+ import { isCircleShape, isLineGeometry, isPointType, isPolygonGeometry } from "../shared/types.js";
18
+ import { DEFAULT_TEXT_SIZE, DEFAULT_TEXT_STYLE } from "../../text-settings.js";
19
+ import { SHAPE_LAYER_IDS } from "../shared/constants.js";
18
20
  import { getDashArray, getFillColor, getLineColor } from "../shared/utils/style-utils.js";
19
- import { COFFIN_CORNERS, DEFAULT_DISPLAY_PROPS, MAP_INTERACTION } from "./constants.js";
21
+ import { BRIGHTNESS_FACTOR, DEFAULT_DISPLAY_PROPS, DISPLAY_EXTENSIONS, HIGHLIGHT_COLOR_TUPLE, MAP_INTERACTION, MATERIAL_SETTINGS } from "./constants.js";
22
+ import { getLabelPosition2d } from "./utils/labels.js";
20
23
  import { createShapeLabelLayer } from "./shape-label-layer.js";
21
- import { getHighlightColor, getHighlightLineWidth, getHoverLineWidth } from "./utils/display-style.js";
24
+ import { applyOverlayOpacity, brightenColor, getHighlightLineWidth, getHoverLineWidth, getOverlayFillColor } from "./utils/display-style.js";
25
+ import { buildIndicatorLineData, classifyElevatedFeatures, createCurtainPolygonFeatures, flattenFeatureTo2D, getFeatureElevation, partitionCurtains } from "./utils/elevation.js";
26
+ import { getIconConfig, getIconLayerProps, getIconUpdateTriggers } from "./utils/icon-config.js";
27
+ import { getRadiusLabelText } from "./utils/radius-label.js";
22
28
  import { Broadcast } from "@accelint/bus";
23
- import { getLogger } from "@accelint/logger";
24
29
  import { CompositeLayer } from "@deck.gl/core";
25
- import { PathStyleExtension } from "@deck.gl/extensions";
26
- import { GeoJsonLayer, IconLayer } from "@deck.gl/layers";
30
+ import { GeoJsonLayer, LineLayer, TextLayer } from "@deck.gl/layers";
27
31
 
28
32
  //#region src/deckgl/shapes/display-shape-layer/index.ts
29
- const logger = getLogger({
30
- enabled: process.env.NODE_ENV !== "production",
31
- level: "warn",
32
- prefix: "[DisplayShapeLayer]",
33
- pretty: true
34
- });
35
33
  /**
36
34
  * Typed event bus instance for shape events.
37
35
  * Provides type-safe event emission for shape interactions.
@@ -46,24 +44,28 @@ const shapeBus = Broadcast.getInstance();
46
44
  * ## Features
47
45
  * - **Multiple geometry types**: Point, LineString, Polygon, and Circle
48
46
  * - **Icon support**: Custom icons for Point geometries via icon atlases
49
- * - **Interactive selection**: Click handling with dotted border and optional highlight
50
- * - **Hover effects**: Border/outline width increases on hover for better UX
47
+ * - **Interactive selection**: Click handling with brightness overlay on polygon select, optional highlight outline for non-Point shapes (if showHighlight=true). Point geometries use coffin corner brackets instead.
48
+ * - **Hover effects**: Polygon fills brighten via material lighting; outline width increases by 2px on hover
51
49
  * - **Customizable labels**: Flexible label positioning with per-shape or global options
52
50
  * - **Style properties**: Full control over colors, border/outline patterns, and opacity
53
51
  * - **Event bus integration**: Automatically emits shape events via @accelint/bus
54
52
  * - **Multi-map support**: Events include map instance ID for isolation
55
53
  *
56
- * ## Selection Visual Feedback
57
- * When a shape is selected via `selectedShapeId`:
58
- * - The shape's border/outline pattern changes to dotted
59
- * - An optional highlight renders underneath (controlled by `showHighlight` prop)
54
+ * ## Interaction Philosophy
55
+ * Interactions never modify a shape's innate styling. Hover and selection are always
56
+ * additive overlays rendered apart from the main layer using opacity-scaled fill colors
57
+ * and material-based brightness the base shape is never altered.
60
58
  *
61
59
  * ## Layer Structure
62
- * Renders up to four sublayers (in order, bottom to top):
63
- * 1. **Highlight layer**: Selection highlight effect for non-icon-Point shapes (if showHighlight=true)
64
- * 2. **Coffin corners layer**: Selection/hover feedback for Point shapes with icons
65
- * 3. **Main GeoJsonLayer**: Shape geometries with styling and interaction
66
- * 4. **Label layer**: Text labels (if showLabels enabled)
60
+ * Renders sublayers in this order (bottom to top):
61
+ * 1. **Highlight layer**: Selection outline for non-polygon, non-Point shapes (when showHighlight enabled)
62
+ * 2. **Select layer**: Selection brightness overlay for polygon shapes
63
+ * 3. **Hover layer**: Hover brightness overlay for polygon shapes
64
+ * 4. **Elevation visualization**: Curtains (LineStrings) or wireframes (polygons) elevation only
65
+ * 5. **Elevation indicators**: Vertical strut lines for elevated non-polygon shapes — elevation only
66
+ * 6. **Main GeoJsonLayer**: Shape geometries with styling and interaction; includes CoffinCornerExtension
67
+ * which propagates to the icon/scatterplot sublayer for Point hover/select bracket feedback
68
+ * 7. **Label layer**: Text labels (if showLabels enabled)
67
69
  *
68
70
  * ## Icon Atlas Constraint
69
71
  * When using icons for Point geometries, all shapes in a single layer must share the
@@ -124,6 +126,12 @@ const shapeBus = Broadcast.getInstance();
124
126
  var DisplayShapeLayer = class extends CompositeLayer {
125
127
  /** Cache for transformed features to avoid recreating objects on every render */
126
128
  featuresCache = null;
129
+ /** Cache for elevation classification and curtain features */
130
+ elevationCache = null;
131
+ /** Cache for elevation indicator line segments */
132
+ indicatorCache = null;
133
+ /** Cached coffin corner color tuple — avoids new array reference per render when highlight hasn't changed. */
134
+ coffinCornerColorCache = null;
127
135
  static layerName = "DisplayShapeLayer";
128
136
  static defaultProps = { ...DEFAULT_DISPLAY_PROPS };
129
137
  /**
@@ -135,19 +143,82 @@ var DisplayShapeLayer = class extends CompositeLayer {
135
143
  lastHoveredId: void 0
136
144
  });
137
145
  this.featuresCache = null;
146
+ this.elevationCache = null;
147
+ this.indicatorCache = null;
148
+ this.coffinCornerColorCache = null;
149
+ }
150
+ /**
151
+ * Resolved highlight color — uses prop if provided, falls back to default.
152
+ */
153
+ get resolvedHighlight() {
154
+ return this.props.highlightColor ?? HIGHLIGHT_COLOR_TUPLE;
155
+ }
156
+ /**
157
+ * Coffin corner bracket color derived from resolvedHighlight with forced full opacity.
158
+ * Cached to avoid a new array reference per render triggering deck.gl prop-change detection.
159
+ */
160
+ get coffinCornerColor() {
161
+ const highlight = this.resolvedHighlight;
162
+ if (this.coffinCornerColorCache?.source !== highlight) this.coffinCornerColorCache = {
163
+ source: highlight,
164
+ color: [
165
+ highlight[0],
166
+ highlight[1],
167
+ highlight[2],
168
+ 255
169
+ ]
170
+ };
171
+ return this.coffinCornerColorCache.color;
172
+ }
173
+ /**
174
+ * Handle picking events from the main shapes layer
175
+ */
176
+ handleMainLayerPick(info, mode) {
177
+ if (mode === "query") this.handleShapeClick(info);
178
+ if (mode === "hover" || !mode) {
179
+ if (info.index !== void 0 && info.index !== this.state?.hoverIndex) this.setState({ hoverIndex: info.index });
180
+ this.handleShapeHover(info);
181
+ }
182
+ }
183
+ /**
184
+ * Handle picking events from curtain layers and map back to original LineString
185
+ */
186
+ handleCurtainPick(info, mode) {
187
+ if (!info.object) return;
188
+ const curtainShapeId = info.object.properties?.shapeId;
189
+ if (!curtainShapeId) return;
190
+ const features = this.getFeaturesWithId();
191
+ const featureIndex = this.featuresCache?.shapeIdToIndex.get(curtainShapeId);
192
+ if (featureIndex === void 0) return;
193
+ const modifiedInfo = {
194
+ ...info,
195
+ index: featureIndex,
196
+ object: features[featureIndex]
197
+ };
198
+ if (mode === "query") this.handleShapeClick(modifiedInfo);
199
+ if (mode === "hover" || !mode) {
200
+ if (featureIndex !== this.state?.hoverIndex) this.setState({ hoverIndex: featureIndex });
201
+ this.handleShapeHover(modifiedInfo);
202
+ }
203
+ return modifiedInfo;
138
204
  }
139
205
  /**
140
206
  * Override getPickingInfo to handle events from sublayers
141
207
  * This is the correct pattern for CompositeLayer event handling
142
208
  */
143
209
  getPickingInfo({ info, mode, sourceLayer }) {
210
+ if ((mode === "hover" || !mode) && (info.index === void 0 || info.index < 0)) {
211
+ if (this.state?.hoverIndex !== void 0) this.setState({ hoverIndex: void 0 });
212
+ this.handleShapeHover(info);
213
+ return info;
214
+ }
144
215
  if (sourceLayer?.id === `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`) {
145
- if (mode === "query") this.handleShapeClick(info);
146
- if (mode === "hover" || !mode) {
147
- if (info.index !== void 0 && info.index !== this.state?.hoverIndex) this.setState({ hoverIndex: info.index });
148
- else if (info.index === void 0 && this.state?.hoverIndex !== void 0) this.setState({ hoverIndex: void 0 });
149
- this.handleShapeHover(info);
150
- }
216
+ this.handleMainLayerPick(info, mode);
217
+ return info;
218
+ }
219
+ if (sourceLayer?.id?.includes("-elevation-curtain")) {
220
+ const curtainInfo = this.handleCurtainPick(info, mode);
221
+ if (curtainInfo) return curtainInfo;
151
222
  }
152
223
  return info;
153
224
  }
@@ -160,20 +231,25 @@ var DisplayShapeLayer = class extends CompositeLayer {
160
231
  if (this.featuresCache?.data === data) return this.featuresCache.features;
161
232
  const features = [];
162
233
  const shapeIdToIndex = /* @__PURE__ */ new Map();
234
+ const normalizedLineColors = [];
163
235
  for (const [i, shape] of data.entries()) {
164
- features.push({
236
+ let feature = {
165
237
  ...shape.feature,
166
238
  properties: {
167
239
  ...shape.feature.properties,
168
240
  shapeId: shape.id
169
241
  }
170
- });
242
+ };
243
+ if (isPolygonGeometry(feature.geometry) && getFeatureElevation(feature) > 0) feature = flattenFeatureTo2D(feature);
244
+ features.push(feature);
171
245
  shapeIdToIndex.set(shape.id, i);
246
+ normalizedLineColors.push(getLineColor(shape.feature));
172
247
  }
173
248
  this.featuresCache = {
174
249
  data,
175
250
  features,
176
- shapeIdToIndex
251
+ shapeIdToIndex,
252
+ normalizedLineColors
177
253
  };
178
254
  return features;
179
255
  }
@@ -182,10 +258,12 @@ var DisplayShapeLayer = class extends CompositeLayer {
182
258
  * Used by event handlers to get full shape without storing in feature properties.
183
259
  */
184
260
  getShapeById(shapeId) {
185
- return this.props.data.find((shape) => shape.id === shapeId);
261
+ const index = this.featuresCache?.shapeIdToIndex.get(shapeId);
262
+ return index !== void 0 ? this.props.data[index] : void 0;
186
263
  }
187
264
  /**
188
- * Handle shape click
265
+ * Handle shape click — emits `shapes:selected` via bus and calls `onShapeClick` callback.
266
+ * @param info - deck.gl picking info from the clicked sublayer.
189
267
  */
190
268
  handleShapeClick = (info) => {
191
269
  const { onShapeClick, mapId } = this.props;
@@ -201,208 +279,192 @@ var DisplayShapeLayer = class extends CompositeLayer {
201
279
  if (onShapeClick) onShapeClick(shape);
202
280
  };
203
281
  /**
204
- * Handle shape hover
282
+ * Handle shape hover — emits `shapes:hovered` via bus (deduplicated by shapeId) and calls `onShapeHover` callback.
283
+ * @param info - deck.gl picking info from the hovered sublayer.
205
284
  */
206
285
  handleShapeHover = (info) => {
207
286
  const { onShapeHover, mapId } = this.props;
208
- const shapeId = info.object?.properties?.shapeId ?? null;
209
- const shape = shapeId ? this.getShapeById(shapeId) ?? null : null;
287
+ const shapeId = info.object?.properties?.shapeId;
288
+ const shape = shapeId ? this.getShapeById(shapeId) : void 0;
210
289
  if (shapeId !== this.state?.lastHoveredId) {
211
290
  this.setState({ lastHoveredId: shapeId });
212
291
  shapeBus.emit(ShapeEvents.hovered, {
213
- shapeId,
292
+ shapeId: shapeId ?? null,
214
293
  mapId
215
294
  });
216
295
  }
217
296
  if (onShapeHover) onShapeHover(shape);
218
297
  };
219
298
  /**
220
- * Render highlight sublayer (underneath main layer)
221
- * Note: Points with icons use coffin corners instead of highlight layer
299
+ * Get or compute elevation-derived data (feature classification + curtain features).
300
+ * Cached on features identity and applyBaseOpacity to avoid per-frame recomputation.
301
+ */
302
+ getElevationData(features, applyBaseOpacity) {
303
+ if (this.elevationCache !== null && this.elevationCache.features === features && this.elevationCache.applyBaseOpacity === applyBaseOpacity) return this.elevationCache;
304
+ const classification = classifyElevatedFeatures(features, getFeatureElevation);
305
+ const curtainFeatures = createCurtainPolygonFeatures(classification.lines, applyBaseOpacity);
306
+ this.elevationCache = {
307
+ features,
308
+ applyBaseOpacity,
309
+ classification,
310
+ curtainFeatures
311
+ };
312
+ return {
313
+ classification,
314
+ curtainFeatures
315
+ };
316
+ }
317
+ /**
318
+ * Render highlight sublayer (underneath main layer).
319
+ * Point geometries skip this layer — they use coffin corner brackets
320
+ * via CoffinCornerExtension on the main layer's icon/scatterplot sublayer instead.
222
321
  */
223
322
  renderHighlightLayer(features) {
224
- const { selectedShapeId, showHighlight, highlightColor } = this.props;
225
- if (!selectedShapeId || showHighlight === false) return null;
226
- const selectedFeature = features.find((f) => f.properties?.shapeId === selectedShapeId);
227
- if (!selectedFeature) return null;
228
- if (selectedFeature.geometry.type === "Point") {
229
- if (!!selectedFeature.properties?.styleProperties?.icon) return null;
230
- }
231
- return new GeoJsonLayer({
323
+ const { selectedShapeId, showHighlight } = this.props;
324
+ if (!selectedShapeId || showHighlight === false) return [];
325
+ const featureIndex = this.featuresCache?.shapeIdToIndex.get(selectedShapeId);
326
+ const selectedFeature = featureIndex !== void 0 ? features[featureIndex] : void 0;
327
+ if (!selectedFeature) return [];
328
+ if (isPointType(selectedFeature.geometry)) return [];
329
+ const highlightFeature = flattenFeatureTo2D(selectedFeature);
330
+ const lineColor = this.resolvedHighlight;
331
+ return [new GeoJsonLayer({
232
332
  id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,
233
- data: [selectedFeature],
234
- filled: true,
333
+ data: [highlightFeature],
334
+ filled: false,
235
335
  stroked: true,
236
336
  lineWidthUnits: "pixels",
237
337
  lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,
238
- getFillColor: () => [
239
- 0,
240
- 0,
241
- 0,
242
- 0
243
- ],
244
- getLineColor: () => highlightColor || getHighlightColor(),
338
+ getLineColor: lineColor,
245
339
  getLineWidth: getHighlightLineWidth,
246
340
  pickable: false,
247
341
  updateTriggers: {
248
- getLineColor: [highlightColor],
342
+ getLineColor: [this.props.highlightColor],
249
343
  getLineWidth: [selectedShapeId, features]
250
344
  }
251
- });
345
+ })];
252
346
  }
253
347
  /**
254
- * Render coffin corners layer for Point geometries that have icons on hover/select
255
- * Coffin corners provide visual feedback for points instead of highlight layer
348
+ * Render selection overlay layer for polygon shapes.
349
+ * Mirrors renderHoverLayer but triggers on selectedShapeId instead of hover.
350
+ * When a shape is both selected and hovered, both layers stack for a brighter combined effect.
256
351
  */
257
- renderCoffinCornersLayer(features) {
258
- const { selectedShapeId } = this.props;
259
- const hoverIndex = this.state?.hoverIndex;
260
- const shapeIdToIndex = this.featuresCache?.shapeIdToIndex;
261
- if (!shapeIdToIndex) return null;
262
- const pointFeatures = features.filter((f) => {
263
- if (f.geometry.type !== "Point") return false;
264
- if (!!!f.properties?.styleProperties?.icon) return false;
265
- const shapeId = f.properties?.shapeId;
266
- const isSelected = shapeId === selectedShapeId;
267
- const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : void 0;
268
- return isSelected || hoverIndex !== void 0 && featureIndex === hoverIndex;
269
- });
270
- if (pointFeatures.length === 0) return null;
271
- const firstPointIcon = pointFeatures[0]?.properties?.styleProperties?.icon;
272
- const iconAtlas = firstPointIcon?.atlas;
273
- const iconMapping = firstPointIcon?.mapping;
274
- if (!(iconAtlas && iconMapping)) {
275
- logger.warn("Point shape has icon style but missing iconAtlas or iconMapping - coffin corners will not render");
276
- return null;
277
- }
278
- const extendedMapping = {
279
- ...iconMapping,
280
- [COFFIN_CORNERS.HOVER_ICON]: {
281
- x: 0,
282
- y: 0,
283
- width: 76,
284
- height: 76,
285
- mask: false
286
- },
287
- [COFFIN_CORNERS.SELECTED_ICON]: {
288
- x: 76,
289
- y: 0,
290
- width: 76,
291
- height: 76,
292
- mask: false
293
- },
294
- [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {
295
- x: 152,
296
- y: 0,
297
- width: 76,
298
- height: 76,
299
- mask: false
300
- }
301
- };
302
- return new IconLayer({
303
- id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-coffin-corners`,
304
- data: pointFeatures,
305
- iconAtlas,
306
- iconMapping: extendedMapping,
307
- getIcon: (d) => {
308
- const shapeId = d.properties?.shapeId;
309
- const isSelected = shapeId === selectedShapeId;
310
- const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : void 0;
311
- if (isSelected && hoverIndex !== void 0 && featureIndex === hoverIndex) return COFFIN_CORNERS.SELECTED_HOVER_ICON;
312
- if (isSelected) return COFFIN_CORNERS.SELECTED_ICON;
313
- return COFFIN_CORNERS.HOVER_ICON;
314
- },
315
- getSize: COFFIN_CORNERS.SIZE,
316
- getPosition: (d) => {
317
- return d.geometry.type === "Point" ? d.geometry.coordinates : [0, 0];
318
- },
319
- getPixelOffset: (d) => {
320
- return [-1, -(d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE) / 2];
321
- },
322
- billboard: false,
352
+ renderSelectLayer(features) {
353
+ const { selectedShapeId, enableElevation } = this.props;
354
+ if (!selectedShapeId) return [];
355
+ const featureIndex = this.featuresCache?.shapeIdToIndex.get(selectedShapeId);
356
+ const selectedFeature = featureIndex !== void 0 ? features[featureIndex] : void 0;
357
+ if (!selectedFeature) return [];
358
+ if (!isPolygonGeometry(selectedFeature.geometry)) return [];
359
+ return [new GeoJsonLayer({
360
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_SELECTION}`,
361
+ data: [selectedFeature],
362
+ filled: true,
363
+ stroked: false,
364
+ getFillColor: getOverlayFillColor,
365
+ extruded: enableElevation,
366
+ getElevation: getFeatureElevation,
367
+ material: MATERIAL_SETTINGS.HOVER_OR_SELECT,
323
368
  pickable: false,
324
369
  updateTriggers: {
325
- getIcon: [selectedShapeId, this.state?.hoverIndex],
326
- data: [
327
- features,
328
- selectedShapeId,
329
- this.state?.hoverIndex
330
- ]
370
+ data: [features, selectedShapeId],
371
+ getFillColor: [features],
372
+ getElevation: [features]
331
373
  }
332
- });
374
+ })];
333
375
  }
334
376
  /**
335
- * Extract icon configuration from features in a single pass.
336
- * Returns the first icon's atlas and mapping (all shapes share the same atlas).
337
- * Uses early return for O(1) best case when first feature has icons.
377
+ * Render hover layer for all polygon shapes (2D and 3D).
378
+ * Overlays the shape's base fill with brighter material lighting.
379
+ * Stacks with other interaction layers (e.g. selection highlight underneath).
338
380
  */
339
- getIconConfig(features) {
340
- for (const f of features) {
341
- const icon = f.properties?.styleProperties?.icon;
342
- if (icon) return {
343
- hasIcons: true,
344
- atlas: icon.atlas,
345
- mapping: icon.mapping
346
- };
347
- }
348
- return { hasIcons: false };
381
+ renderHoverLayer(features) {
382
+ const { enableElevation, selectedShapeId } = this.props;
383
+ const hoverIndex = this.state?.hoverIndex;
384
+ if (hoverIndex === void 0) return [];
385
+ const hoveredFeature = features[hoverIndex];
386
+ if (!hoveredFeature) return [];
387
+ if (!isPolygonGeometry(hoveredFeature.geometry)) return [];
388
+ const material = hoveredFeature.properties?.shapeId === selectedShapeId ? MATERIAL_SETTINGS.HOVER_AND_SELECT : MATERIAL_SETTINGS.HOVER_OR_SELECT;
389
+ return [new GeoJsonLayer({
390
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-hover`,
391
+ data: [hoveredFeature],
392
+ filled: true,
393
+ stroked: false,
394
+ getFillColor: getOverlayFillColor,
395
+ extruded: enableElevation,
396
+ getElevation: getFeatureElevation,
397
+ material,
398
+ pickable: false,
399
+ updateTriggers: {
400
+ data: [features, hoverIndex],
401
+ getFillColor: [features],
402
+ getElevation: [features],
403
+ material: [selectedShapeId, hoverIndex]
404
+ }
405
+ })];
349
406
  }
350
407
  /**
351
408
  * Render main shapes layer
352
409
  */
353
410
  renderMainLayer(features) {
354
411
  const { pickable, applyBaseOpacity, selectedShapeId } = this.props;
355
- const { hasIcons, atlas: iconAtlas, mapping: iconMapping } = this.getIconConfig(features);
412
+ const { hasIcons, atlas: iconAtlas, mapping: iconMapping } = getIconConfig(features);
413
+ const hoveredFeature = this.state?.hoverIndex !== void 0 ? features[this.state.hoverIndex] : void 0;
414
+ const hoveredEntityId = hoveredFeature?.geometry.type === "Point" ? hoveredFeature.properties?.shapeId : void 0;
356
415
  return new GeoJsonLayer({
357
416
  id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,
358
417
  data: features,
359
418
  filled: true,
360
419
  stroked: true,
361
420
  getFillColor: (d) => getFillColor(d, applyBaseOpacity),
362
- getLineColor,
421
+ getLineColor: (d, info) => {
422
+ const baseColor = this.featuresCache?.normalizedLineColors[info?.index ?? -1] ?? getLineColor(d);
423
+ const isHovered = info?.index === this.state?.hoverIndex;
424
+ const isSelected = d.properties?.shapeId === selectedShapeId;
425
+ if (isHovered && isSelected) return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_AND_SELECT);
426
+ if (isHovered || isSelected) return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_OR_SELECT);
427
+ return baseColor;
428
+ },
363
429
  getLineWidth: (d, info) => {
430
+ if (this.props.enableElevation && isLineGeometry(d.geometry) && getFeatureElevation(d) > 0) return d.properties?.styleProperties?.lineWidth ?? 2;
364
431
  return getHoverLineWidth(d, info?.index === this.state?.hoverIndex);
365
432
  },
366
433
  lineWidthUnits: "pixels",
367
434
  lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,
368
435
  lineWidthMaxPixels: 20,
436
+ extruded: this.props.enableElevation ?? false,
437
+ getElevation: getFeatureElevation,
438
+ ...this.props.enableElevation ? { material: MATERIAL_SETTINGS.NORMAL } : {},
369
439
  pointType: hasIcons ? "icon" : "circle",
370
440
  getPointRadius: (d) => {
371
441
  return d.properties?.styleProperties?.icon?.size ?? 2;
372
442
  },
373
443
  pointRadiusUnits: "pixels",
374
- ...hasIcons && iconAtlas ? { iconAtlas } : {},
375
- ...hasIcons && iconMapping ? { iconMapping } : {},
376
- ...hasIcons ? {
377
- getIcon: (d) => d.properties?.styleProperties?.icon?.name ?? "marker",
378
- getIconSize: (d) => {
379
- return d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE;
380
- },
381
- getIconColor: getLineColor,
382
- getIconPixelOffset: (d) => {
383
- return [-1, -(d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE) / 2];
384
- },
385
- iconBillboard: false
386
- } : {},
387
- extensions: [new PathStyleExtension({ dash: true })],
388
- getDashArray: (d) => {
389
- if (d.properties?.shapeId === selectedShapeId) return DASH_ARRAYS.dotted;
390
- return getDashArray(d);
391
- },
444
+ ...getIconLayerProps(hasIcons, iconAtlas, iconMapping),
445
+ selectedEntityId: selectedShapeId,
446
+ hoveredEntityId,
447
+ getEntityId: (d) => d.properties?.shapeId,
448
+ selectedCoffinCornerColor: this.coffinCornerColor,
449
+ extensions: DISPLAY_EXTENSIONS,
450
+ getDashArray,
392
451
  pickable,
393
452
  autoHighlight: false,
453
+ ...this.props.enableElevation ? { parameters: {
454
+ depthTest: true,
455
+ depthCompare: "less-equal"
456
+ } } : {},
394
457
  updateTriggers: {
395
458
  getFillColor: [features, applyBaseOpacity],
396
- getLineColor: [features],
459
+ getLineColor: [
460
+ features,
461
+ this.state?.hoverIndex,
462
+ selectedShapeId
463
+ ],
397
464
  getLineWidth: [features, this.state?.hoverIndex],
398
- getDashArray: [features, selectedShapeId],
465
+ getDashArray: [features],
399
466
  getPointRadius: [features],
400
- ...hasIcons ? {
401
- getIcon: [features],
402
- getIconSize: [features],
403
- getIconColor: [features],
404
- getIconPixelOffset: [features]
405
- } : {}
467
+ ...getIconUpdateTriggers(hasIcons, features)
406
468
  }
407
469
  });
408
470
  }
@@ -415,32 +477,216 @@ var DisplayShapeLayer = class extends CompositeLayer {
415
477
  */
416
478
  renderLabelsLayer() {
417
479
  const { showLabels, data, labelOptions } = this.props;
418
- if (showLabels === "never") return null;
480
+ if (showLabels === "never") return [];
419
481
  let labelData = data;
420
482
  if (showLabels === "hover") {
421
483
  const hoverIndex = this.state?.hoverIndex;
422
- if (hoverIndex === void 0) return null;
484
+ if (hoverIndex === void 0) return [];
423
485
  const hoveredShape = data[hoverIndex];
424
486
  labelData = hoveredShape ? [hoveredShape] : [];
425
487
  }
426
- if (labelData.length === 0) return null;
427
- return createShapeLabelLayer({
488
+ if (labelData.length === 0) return [];
489
+ return [createShapeLabelLayer({
428
490
  id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,
429
491
  data: labelData,
430
492
  labelOptions
493
+ })];
494
+ }
495
+ /**
496
+ * Render radius label layer for hovered circle shapes.
497
+ * Shows the circle's radius value converted to the configured display unit.
498
+ *
499
+ * Positioning relative to the shape's label:
500
+ * - When showLabels is 'always' or 'hover': appears below the label
501
+ * - When showLabels is 'never': appears in the label's position
502
+ */
503
+ renderRadiusLabelLayer() {
504
+ const { data, unit, showLabels, labelOptions } = this.props;
505
+ const hoverIndex = this.state?.hoverIndex;
506
+ const hoveredShape = hoverIndex !== void 0 ? data[hoverIndex] : void 0;
507
+ const radiusText = hoveredShape && isCircleShape(hoveredShape) ? getRadiusLabelText(hoveredShape, unit) : void 0;
508
+ const labelPosition = radiusText && hoveredShape ? getLabelPosition2d(hoveredShape, labelOptions) : void 0;
509
+ if (!labelPosition) return [];
510
+ const pixelOffset = showLabels !== "never" ? [labelPosition.pixelOffset[0], labelPosition.pixelOffset[1] + DEFAULT_TEXT_SIZE + 2] : labelPosition.pixelOffset;
511
+ return [new TextLayer({
512
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-radius-label`,
513
+ data: [{
514
+ text: radiusText,
515
+ position: labelPosition.coordinates
516
+ }],
517
+ getText: (d) => d.text,
518
+ getPosition: (d) => d.position,
519
+ getPixelOffset: pixelOffset,
520
+ getTextAnchor: labelPosition.textAnchor,
521
+ getAlignmentBaseline: labelPosition.alignmentBaseline,
522
+ ...DEFAULT_TEXT_STYLE,
523
+ getAngle: 0,
524
+ background: false,
525
+ fontFamily: "Roboto MonoVariable, monospace",
526
+ pickable: false,
527
+ updateTriggers: {
528
+ getText: [hoverIndex, unit],
529
+ getPosition: [hoverIndex, labelOptions],
530
+ getPixelOffset: [
531
+ hoverIndex,
532
+ labelOptions,
533
+ showLabels
534
+ ],
535
+ getTextAnchor: [hoverIndex, labelOptions],
536
+ getAlignmentBaseline: [hoverIndex, labelOptions]
537
+ }
538
+ })];
539
+ }
540
+ /**
541
+ * Render vertical elevation indicator lines for non-polygon shapes.
542
+ * Creates vertical "strut" lines from ground level to elevated features.
543
+ * For LineStrings, creates a "curtain" effect with vertical lines at each coordinate.
544
+ * Polygons use wireframe extrusion instead.
545
+ */
546
+ renderElevationIndicatorLayer(features, elevatedNonPolygons) {
547
+ if (elevatedNonPolygons.length === 0) return [];
548
+ const { selectedShapeId } = this.props;
549
+ const hoverIndex = this.state?.hoverIndex;
550
+ const cache = this.indicatorCache;
551
+ let lineData;
552
+ if (cache !== null && cache.features === features && cache.selectedShapeId === selectedShapeId && cache.hoverIndex === hoverIndex) lineData = cache.lineData;
553
+ else {
554
+ lineData = buildIndicatorLineData(elevatedNonPolygons, features, selectedShapeId, hoverIndex);
555
+ this.indicatorCache = {
556
+ features,
557
+ selectedShapeId,
558
+ hoverIndex,
559
+ lineData
560
+ };
561
+ }
562
+ if (lineData.length === 0) return [];
563
+ return [new LineLayer({
564
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-indicators`,
565
+ data: lineData,
566
+ getSourcePosition: (d) => d.source,
567
+ getTargetPosition: (d) => d.target,
568
+ getColor: (d) => d.color,
569
+ getWidth: 2,
570
+ widthUnits: "pixels",
571
+ pickable: false,
572
+ updateTriggers: {
573
+ data: [
574
+ features,
575
+ selectedShapeId,
576
+ hoverIndex
577
+ ],
578
+ getColor: [
579
+ features,
580
+ selectedShapeId,
581
+ hoverIndex
582
+ ]
583
+ }
584
+ })];
585
+ }
586
+ /**
587
+ * Create a single curtain GeoJsonLayer with shared configuration.
588
+ */
589
+ createCurtainGeoJsonLayer(idSuffix, data, getFillColor$1, dataTriggers, fillColorTriggers) {
590
+ return new GeoJsonLayer({
591
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-${idSuffix}`,
592
+ data,
593
+ filled: true,
594
+ stroked: false,
595
+ _full3d: true,
596
+ getFillColor: getFillColor$1,
597
+ pickable: this.props.pickable ?? true,
598
+ parameters: {
599
+ depthTest: true,
600
+ depthCompare: "less-equal"
601
+ },
602
+ updateTriggers: {
603
+ data: dataTriggers,
604
+ getFillColor: fillColorTriggers
605
+ }
431
606
  });
432
607
  }
433
608
  /**
434
- * Render all sublayers
609
+ * Render curtain layers for elevated LineStrings.
610
+ * Creates three separate layers for main, hovered, and selected states.
611
+ */
612
+ renderCurtainLayers(features, allCurtainFeatures) {
613
+ const layers = [];
614
+ const { selectedShapeId } = this.props;
615
+ const hoverIndex = this.state?.hoverIndex;
616
+ const hoveredShapeId = hoverIndex !== void 0 && features[hoverIndex] ? features[hoverIndex].properties?.shapeId : void 0;
617
+ const { main, hovered, selected } = partitionCurtains(allCurtainFeatures, hoveredShapeId, selectedShapeId);
618
+ const isSelectedHovered = selectedShapeId === hoveredShapeId;
619
+ const dataTriggers = [
620
+ features,
621
+ hoveredShapeId,
622
+ selectedShapeId
623
+ ];
624
+ if (main.length > 0) layers.push(this.createCurtainGeoJsonLayer("elevation-curtain", main, (d) => d.properties.fillColor, dataTriggers, [features, this.props.applyBaseOpacity]));
625
+ if (hovered.length > 0) {
626
+ const hoveredColor = applyOverlayOpacity(brightenColor(hovered[0].properties.lineColor, BRIGHTNESS_FACTOR.HOVER_OR_SELECT));
627
+ layers.push(this.createCurtainGeoJsonLayer("elevation-curtain-hover", hovered, () => hoveredColor, dataTriggers, [features]));
628
+ }
629
+ if (selected.length > 0) {
630
+ const factor = isSelectedHovered ? BRIGHTNESS_FACTOR.HOVER_AND_SELECT : BRIGHTNESS_FACTOR.HOVER_OR_SELECT;
631
+ const selectedColor = applyOverlayOpacity(brightenColor(selected[0].properties.lineColor, factor));
632
+ layers.push(this.createCurtainGeoJsonLayer("elevation-curtain-selected", selected, () => selectedColor, dataTriggers, [features, isSelectedHovered]));
633
+ }
634
+ return layers;
635
+ }
636
+ /**
637
+ * Render elevation visualization layers (curtains for lines, wireframes for polygons).
638
+ */
639
+ renderElevationVisualizationLayer(features, allCurtainFeatures, elevatedPolygons) {
640
+ const layers = [];
641
+ if (allCurtainFeatures.length > 0) layers.push(...this.renderCurtainLayers(features, allCurtainFeatures));
642
+ if (elevatedPolygons.length > 0) {
643
+ const { applyBaseOpacity } = this.props;
644
+ layers.push(new GeoJsonLayer({
645
+ id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-wireframe`,
646
+ data: elevatedPolygons,
647
+ filled: false,
648
+ stroked: false,
649
+ extruded: true,
650
+ wireframe: true,
651
+ getElevation: getFeatureElevation,
652
+ getFillColor: (d) => getFillColor(d, applyBaseOpacity),
653
+ getLineColor,
654
+ pickable: false,
655
+ parameters: {
656
+ depthTest: true,
657
+ depthCompare: "less-equal"
658
+ },
659
+ updateTriggers: {
660
+ getElevation: [features],
661
+ getFillColor: [features, applyBaseOpacity],
662
+ getLineColor: [features]
663
+ }
664
+ }));
665
+ }
666
+ return layers;
667
+ }
668
+ /**
669
+ * Render all sublayers.
670
+ * @returns Ordered array of sublayers (highlight, select, hover, elevation, main, labels) from bottom to top.
435
671
  */
436
672
  renderLayers() {
437
673
  const features = this.getFeaturesWithId();
438
- return [
439
- this.renderHighlightLayer(features),
440
- this.renderCoffinCornersLayer(features),
441
- this.renderMainLayer(features),
442
- this.renderLabelsLayer()
443
- ].filter(Boolean);
674
+ const enableElevation = this.props.enableElevation ?? false;
675
+ const layers = [
676
+ ...this.renderHighlightLayer(features),
677
+ ...this.renderSelectLayer(features),
678
+ ...this.renderHoverLayer(features)
679
+ ];
680
+ if (enableElevation) {
681
+ const { classification, curtainFeatures } = this.getElevationData(features, this.props.applyBaseOpacity);
682
+ const { polygons, nonPolygons } = classification;
683
+ layers.push(...this.renderElevationVisualizationLayer(features, curtainFeatures, polygons));
684
+ layers.push(...this.renderElevationIndicatorLayer(features, nonPolygons));
685
+ }
686
+ layers.push(this.renderMainLayer(features));
687
+ layers.push(...this.renderLabelsLayer());
688
+ layers.push(...this.renderRadiusLabelLayer());
689
+ return layers;
444
690
  }
445
691
  };
446
692