@accelint/map-toolkit 1.4.0 → 2.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 (204) hide show
  1. package/CHANGELOG.md +45 -0
  2. package/catalog-info.yaml +4 -4
  3. package/dist/camera/events.js +1 -1
  4. package/dist/camera/index.d.ts +1 -1
  5. package/dist/camera/index.js +1 -1
  6. package/dist/camera/store.d.ts +1 -1
  7. package/dist/camera/store.js +4 -6
  8. package/dist/camera/store.js.map +1 -1
  9. package/dist/camera/types.d.ts +1 -1
  10. package/dist/camera/types.js +1 -1
  11. package/dist/cursor-coordinates/constants.js +1 -1
  12. package/dist/cursor-coordinates/index.d.ts +1 -1
  13. package/dist/cursor-coordinates/index.js +1 -1
  14. package/dist/cursor-coordinates/store.d.ts +1 -1
  15. package/dist/cursor-coordinates/store.js +1 -1
  16. package/dist/cursor-coordinates/types.d.ts +1 -1
  17. package/dist/cursor-coordinates/types.js +1 -1
  18. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
  19. package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
  20. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  21. package/dist/deckgl/base-map/constants.js +1 -1
  22. package/dist/deckgl/base-map/controls.d.ts +1 -1
  23. package/dist/deckgl/base-map/controls.js +1 -1
  24. package/dist/deckgl/base-map/events.js +1 -1
  25. package/dist/deckgl/base-map/index.d.ts +3 -3
  26. package/dist/deckgl/base-map/index.js +15 -7
  27. package/dist/deckgl/base-map/index.js.map +1 -1
  28. package/dist/deckgl/base-map/provider.d.ts +3 -3
  29. package/dist/deckgl/base-map/provider.js +3 -5
  30. package/dist/deckgl/base-map/provider.js.map +1 -1
  31. package/dist/deckgl/base-map/types.d.ts +1 -1
  32. package/dist/deckgl/base-map/types.js +1 -1
  33. package/dist/deckgl/index.d.ts +4 -4
  34. package/dist/deckgl/index.js +4 -4
  35. package/dist/deckgl/saved-viewports/index.d.ts +1 -1
  36. package/dist/deckgl/saved-viewports/index.js +1 -1
  37. package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
  38. package/dist/deckgl/saved-viewports/storage.js +5 -10
  39. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  40. package/dist/deckgl/shapes/display-shape-layer/constants.js +66 -13
  41. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  42. package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
  43. package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
  44. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +74 -35
  45. package/dist/deckgl/shapes/display-shape-layer/index.js +381 -154
  46. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  47. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
  48. package/dist/deckgl/shapes/display-shape-layer/store.js +8 -2
  49. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
  50. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +108 -19
  51. package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
  52. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
  53. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
  54. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
  55. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  56. package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
  57. package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
  58. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +151 -0
  59. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
  60. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +50 -0
  61. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +1 -0
  62. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
  63. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
  64. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  65. package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
  66. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
  67. package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
  68. package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
  69. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +3 -3
  70. package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -17
  71. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
  72. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +8 -9
  73. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  74. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -3
  75. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +4 -21
  76. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  77. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +4 -34
  78. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  79. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +11 -12
  80. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  81. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
  82. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  83. package/dist/deckgl/shapes/draw-shape-layer/store.js +38 -4
  84. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
  85. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +1 -1
  86. package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
  87. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
  88. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +2 -2
  89. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +4 -9
  90. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  91. package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
  92. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
  93. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
  94. package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
  95. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
  96. package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
  97. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
  98. package/dist/deckgl/shapes/edit-shape-layer/index.js +52 -21
  99. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  100. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
  101. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  102. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +2 -2
  103. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  104. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +7 -7
  105. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  106. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +2 -2
  107. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
  108. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
  109. package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
  110. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +2 -2
  111. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  112. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
  113. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  114. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
  115. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  116. package/dist/deckgl/shapes/edit-shape-layer/store.js +78 -14
  117. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  118. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +14 -2
  119. package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
  120. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
  121. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +2 -2
  122. package/dist/deckgl/shapes/index.d.ts +4 -4
  123. package/dist/deckgl/shapes/index.js +5 -5
  124. package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
  125. package/dist/deckgl/shapes/shared/constants.js +55 -15
  126. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  127. package/dist/deckgl/shapes/shared/events.d.ts +5 -1
  128. package/dist/deckgl/shapes/shared/events.js +1 -1
  129. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  130. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
  131. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  132. package/dist/deckgl/shapes/shared/types.d.ts +174 -53
  133. package/dist/deckgl/shapes/shared/types.js +155 -2
  134. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  135. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
  136. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  137. package/dist/deckgl/shapes/shared/utils/layer-config.js +9 -6
  138. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
  139. package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
  140. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
  141. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
  142. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  143. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
  144. package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
  145. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
  146. package/dist/deckgl/symbol-layer/fiber.d.ts +1 -1
  147. package/dist/deckgl/symbol-layer/fiber.js +1 -1
  148. package/dist/deckgl/symbol-layer/index.d.ts +1 -1
  149. package/dist/deckgl/symbol-layer/index.js +1 -1
  150. package/dist/deckgl/text-layer/character-sets.js +1 -1
  151. package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
  152. package/dist/deckgl/text-layer/default-settings.js +1 -1
  153. package/dist/deckgl/text-layer/fiber.d.ts +1 -1
  154. package/dist/deckgl/text-layer/fiber.js +1 -1
  155. package/dist/deckgl/text-layer/index.d.ts +1 -1
  156. package/dist/deckgl/text-layer/index.js +1 -1
  157. package/dist/deckgl/text-settings.d.ts +3 -3
  158. package/dist/deckgl/text-settings.js +1 -1
  159. package/dist/map-cursor/events.js +1 -1
  160. package/dist/map-cursor/index.d.ts +1 -1
  161. package/dist/map-cursor/index.js +1 -1
  162. package/dist/map-cursor/store.d.ts +1 -1
  163. package/dist/map-cursor/store.js +1 -1
  164. package/dist/map-cursor/types.d.ts +1 -1
  165. package/dist/map-cursor/types.js +1 -1
  166. package/dist/map-cursor/use-map-cursor.d.ts +1 -1
  167. package/dist/map-cursor/use-map-cursor.js +1 -1
  168. package/dist/map-mode/events.js +1 -1
  169. package/dist/map-mode/index.d.ts +1 -1
  170. package/dist/map-mode/index.js +1 -1
  171. package/dist/map-mode/store.d.ts +1 -1
  172. package/dist/map-mode/store.js +3 -8
  173. package/dist/map-mode/store.js.map +1 -1
  174. package/dist/map-mode/types.d.ts +1 -1
  175. package/dist/map-mode/types.js +1 -1
  176. package/dist/map-mode/use-map-mode.d.ts +1 -1
  177. package/dist/map-mode/use-map-mode.js +1 -1
  178. package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
  179. package/dist/maplibre/hooks/use-maplibre.js +1 -1
  180. package/dist/maplibre/index.d.ts +1 -1
  181. package/dist/maplibre/index.js +1 -1
  182. package/dist/shared/cleanup.d.ts +58 -0
  183. package/dist/shared/cleanup.js +93 -0
  184. package/dist/shared/cleanup.js.map +1 -0
  185. package/dist/shared/constants.js +1 -1
  186. package/dist/shared/create-map-store.d.ts +13 -1
  187. package/dist/shared/create-map-store.js +9 -4
  188. package/dist/shared/create-map-store.js.map +1 -1
  189. package/dist/shared/logger.js +31 -0
  190. package/dist/shared/logger.js.map +1 -0
  191. package/dist/shared/units.js +1 -1
  192. package/dist/viewport/index.d.ts +1 -1
  193. package/dist/viewport/index.js +1 -1
  194. package/dist/viewport/store.d.ts +1 -1
  195. package/dist/viewport/store.js +1 -1
  196. package/dist/viewport/types.d.ts +1 -1
  197. package/dist/viewport/types.js +1 -1
  198. package/dist/viewport/utils.d.ts +1 -1
  199. package/dist/viewport/utils.js +1 -1
  200. package/dist/viewport/viewport-size.d.ts +3 -3
  201. package/dist/viewport/viewport-size.js +1 -1
  202. package/package.json +22 -19
  203. package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
  204. package/dist/hotkey-manager/dist/react/use-hotkey.js.map +0 -1
@@ -0,0 +1,407 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ 'use client';
15
+
16
+ import { isGeometryCollectionType, isLineGeometry, isLineStringType, isMultiLineStringType, isMultiPointType, isMultiPolygonType, isPointType, isPolygonGeometry, isPolygonType } from "../../shared/types.js";
17
+ import { getLineColor } from "../../shared/utils/style-utils.js";
18
+ import { BRIGHTNESS_FACTOR } from "../constants.js";
19
+ import { brightenColor } from "./display-style.js";
20
+
21
+ //#region src/deckgl/shapes/display-shape-layer/utils/elevation.ts
22
+ /**
23
+ * Extract elevation from 3D coordinates.
24
+ * Drills into nested coordinate arrays to find the z-value.
25
+ *
26
+ * @param coordinates - GeoJSON coordinates (single, array, or nested)
27
+ * @returns The z-coordinate if present, otherwise 0
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * getElevationFromCoordinates([10, 20, 5000]); // 5000
32
+ * getElevationFromCoordinates([[10, 20, 5000], [11, 21, 6000]]); // 5000
33
+ * getElevationFromCoordinates([10, 20]); // 0
34
+ * ```
35
+ */
36
+ const MAX_COORDINATE_DEPTH = 5;
37
+ function getElevationFromCoordinates(coordinates) {
38
+ let current = coordinates;
39
+ let depth = 0;
40
+ while (Array.isArray(current) && depth < MAX_COORDINATE_DEPTH) {
41
+ if (!Array.isArray(current[0])) return current[2] ?? 0;
42
+ current = current[0];
43
+ depth++;
44
+ }
45
+ return 0;
46
+ }
47
+ /**
48
+ * Get elevation from a feature's geometry.
49
+ * Extracts z-coordinate, returning 0 for missing geometry or GeometryCollection.
50
+ *
51
+ * @param feature - The GeoJSON feature
52
+ * @returns The elevation value, or 0
53
+ *
54
+ * @example
55
+ * ```typescript
56
+ * const elevation = getFeatureElevation(feature);
57
+ * if (elevation > 0) {
58
+ * // Feature has 3D elevation
59
+ * }
60
+ * ```
61
+ */
62
+ function getFeatureElevation(feature) {
63
+ const maxElevation = feature.properties?.maxElevation;
64
+ if (maxElevation !== void 0) return maxElevation;
65
+ const { geometry } = feature;
66
+ if (!geometry) return 0;
67
+ if (isGeometryCollectionType(geometry)) return 0;
68
+ return getElevationFromCoordinates(geometry.coordinates);
69
+ }
70
+ /**
71
+ * Extract [lon, lat] from a coordinate array if valid.
72
+ */
73
+ function extractLonLat(coords) {
74
+ if (!coords || coords.length < 2) return;
75
+ const [lon, lat] = coords;
76
+ return lon !== void 0 && lat !== void 0 ? [lon, lat] : void 0;
77
+ }
78
+ /**
79
+ * Extract a representative coordinate from a feature for elevation indicators.
80
+ *
81
+ * @param geometry - The feature geometry
82
+ * @returns [lon, lat] or undefined if coordinates cannot be extracted
83
+ */
84
+ function getRepresentativeCoordinate(geometry) {
85
+ if (isPointType(geometry)) return extractLonLat(geometry.coordinates);
86
+ if (isLineStringType(geometry) || isMultiPointType(geometry)) return extractLonLat(geometry.coordinates[0]);
87
+ if (isMultiLineStringType(geometry)) return extractLonLat(geometry.coordinates[0]?.[0]);
88
+ }
89
+ /** Create a vertical line segment from ground to an elevated position. */
90
+ function createVerticalSegment(lon, lat, elevation, color) {
91
+ return {
92
+ source: [
93
+ lon,
94
+ lat,
95
+ 0
96
+ ],
97
+ target: [
98
+ lon,
99
+ lat,
100
+ elevation
101
+ ],
102
+ color
103
+ };
104
+ }
105
+ /** Process a coordinates array and create vertical segments for each elevated point. */
106
+ function processCoordinates(coordinates, color) {
107
+ const segments = [];
108
+ for (const coord of coordinates) {
109
+ const [lon, lat, elevation] = coord;
110
+ if (lon !== void 0 && lat !== void 0 && elevation !== void 0 && elevation > 0) segments.push(createVerticalSegment(lon, lat, elevation, color));
111
+ }
112
+ return segments;
113
+ }
114
+ /**
115
+ * Create elevation indicator line segments for a geometry.
116
+ *
117
+ * @param geometry - The feature geometry
118
+ * @param color - RGBA color for the line segments
119
+ * @returns Array of line segments from ground to elevated positions
120
+ *
121
+ * @example
122
+ * ```typescript
123
+ * const segments = createElevationLineSegments(
124
+ * feature.geometry,
125
+ * [255, 255, 255, 255],
126
+ * );
127
+ * // Each segment has { source: [lon, lat, 0], target: [lon, lat, elevation], color }
128
+ * ```
129
+ */
130
+ function createElevationLineSegments(geometry, color) {
131
+ if (isGeometryCollectionType(geometry)) return [];
132
+ if (isLineStringType(geometry)) return processCoordinates(geometry.coordinates, color);
133
+ if (isMultiLineStringType(geometry)) {
134
+ const allSegments = [];
135
+ const lines = geometry.coordinates;
136
+ for (const line of lines) for (const segment of processCoordinates(line, color)) allSegments.push(segment);
137
+ return allSegments;
138
+ }
139
+ const coords = getRepresentativeCoordinate(geometry);
140
+ if (coords) {
141
+ const [lon, lat] = coords;
142
+ const elevation = getElevationFromCoordinates(geometry.coordinates);
143
+ if (elevation > 0) return [createVerticalSegment(lon, lat, elevation, color)];
144
+ }
145
+ return [];
146
+ }
147
+ /**
148
+ * Single-pass classification of features by geometry type and elevation.
149
+ * Avoids multiple .filter() passes per render frame.
150
+ *
151
+ * @param features - The features to classify
152
+ * @param getElevation - Elevation accessor function
153
+ * @returns Classified features: lines, polygons, nonPolygons
154
+ *
155
+ * @example
156
+ * ```typescript
157
+ * const { lines, polygons, nonPolygons } = classifyElevatedFeatures(
158
+ * features,
159
+ * getFeatureElevation,
160
+ * );
161
+ * ```
162
+ */
163
+ function classifyElevatedFeatures(features, getElevation) {
164
+ const lines = [];
165
+ const polygons = [];
166
+ const nonPolygons = [];
167
+ for (const f of features) {
168
+ if (getElevation(f) <= 0) continue;
169
+ const { geometry } = f;
170
+ if (isPolygonGeometry(geometry)) polygons.push(f);
171
+ else {
172
+ nonPolygons.push(f);
173
+ if (isLineGeometry(geometry)) lines.push(f);
174
+ }
175
+ }
176
+ return {
177
+ lines,
178
+ polygons,
179
+ nonPolygons
180
+ };
181
+ }
182
+ /**
183
+ * Create curtain polygon features from a single LineString's coordinate array.
184
+ * Each pair of consecutive coordinates becomes a vertical rectangular polygon.
185
+ *
186
+ * @param coordinates - Array of [lon, lat, elevation] coordinates
187
+ * @param fillColor - RGBA fill color for the curtain
188
+ * @param lineColor - RGBA line color for the curtain
189
+ * @param shapeId - Optional shape ID for picking correlation
190
+ * @returns Array of curtain polygon features
191
+ *
192
+ * @example
193
+ * ```typescript
194
+ * const curtains = createCurtainPolygonsFromLine(
195
+ * [[10, 20, 5000], [11, 21, 6000]],
196
+ * [98, 166, 255, 51],
197
+ * [98, 166, 255, 255],
198
+ * 'shape-1',
199
+ * );
200
+ * ```
201
+ */
202
+ function createCurtainPolygonsFromLine(coordinates, fillColor, lineColor, shapeId) {
203
+ const polygons = [];
204
+ for (let i = 0; i < coordinates.length - 1; i++) {
205
+ const coord1 = coordinates[i];
206
+ const coord2 = coordinates[i + 1];
207
+ if (coord1 === void 0 || coord2 === void 0) continue;
208
+ const [lon1, lat1, elev1] = coord1;
209
+ const [lon2, lat2, elev2] = coord2;
210
+ if (lon1 === void 0 || lat1 === void 0 || elev1 === void 0 || lon2 === void 0 || lat2 === void 0 || elev2 === void 0) continue;
211
+ if (elev1 === 0 && elev2 === 0) continue;
212
+ const ring = [
213
+ [
214
+ lon1,
215
+ lat1,
216
+ 0
217
+ ],
218
+ [
219
+ lon2,
220
+ lat2,
221
+ 0
222
+ ],
223
+ [
224
+ lon2,
225
+ lat2,
226
+ elev2
227
+ ],
228
+ [
229
+ lon1,
230
+ lat1,
231
+ elev1
232
+ ],
233
+ [
234
+ lon1,
235
+ lat1,
236
+ 0
237
+ ]
238
+ ];
239
+ polygons.push({
240
+ type: "Feature",
241
+ geometry: {
242
+ type: "Polygon",
243
+ coordinates: [ring]
244
+ },
245
+ properties: {
246
+ fillColor,
247
+ lineColor,
248
+ shapeId
249
+ }
250
+ });
251
+ }
252
+ return polygons;
253
+ }
254
+ /**
255
+ * Create vertical curtain polygon features from elevated LineString features.
256
+ * Converts elevated LineStrings into vertical rectangular polygons from ground to elevation.
257
+ *
258
+ * @param elevatedLines - Features with LineString/MultiLineString geometry and elevation
259
+ * @param applyBaseOpacity - Whether to apply reduced opacity to fills
260
+ * @returns Array of curtain polygon features
261
+ *
262
+ * @example
263
+ * ```typescript
264
+ * const curtainFeatures = createCurtainPolygonFeatures(elevatedLines, true);
265
+ * // Returns vertical polygon features for each consecutive coordinate pair
266
+ * ```
267
+ */
268
+ function createCurtainPolygonFeatures(elevatedLines, applyBaseOpacity) {
269
+ const curtainFeatures = [];
270
+ for (const feature of elevatedLines) {
271
+ const { geometry } = feature;
272
+ const lineColorRGBA = getLineColor(feature);
273
+ const shapeId = feature.properties?.shapeId;
274
+ const fillColor = applyBaseOpacity ? [
275
+ lineColorRGBA[0],
276
+ lineColorRGBA[1],
277
+ lineColorRGBA[2],
278
+ Math.round(lineColorRGBA[3] * .2)
279
+ ] : lineColorRGBA;
280
+ let coordArrays;
281
+ if (isLineStringType(geometry)) coordArrays = [geometry.coordinates];
282
+ else coordArrays = geometry.coordinates;
283
+ for (const coords of coordArrays) for (const polygon of createCurtainPolygonsFromLine(coords, fillColor, lineColorRGBA, shapeId)) curtainFeatures.push(polygon);
284
+ }
285
+ return curtainFeatures;
286
+ }
287
+ /**
288
+ * Returns a copy of a feature with Z coordinates stripped from its geometry.
289
+ *
290
+ * Use this to project a feature back to the ground plane. Common use cases:
291
+ * - Highlight outlines that should render at ground level
292
+ * - Extruded polygons where elevation must come solely from `getElevation`,
293
+ * not from coordinate Z (deck.gl's SolidPolygonLayer adds both together)
294
+ *
295
+ * @param feature - The feature to flatten
296
+ * @returns A new feature with 2D coordinates, or the original if geometry
297
+ * type is not handled (Point, MultiPoint, GeometryCollection)
298
+ *
299
+ * @example
300
+ * ```typescript
301
+ * // Elevated LineString [lon, lat, 20000] → [lon, lat]
302
+ * const flat = flattenFeatureTo2D(elevatedLineStringFeature);
303
+ * // flat.geometry.coordinates: [[lon, lat], [lon, lat], ...]
304
+ *
305
+ * // Elevated Polygon [lon, lat, 43500] → [lon, lat]
306
+ * const flat = flattenFeatureTo2D(elevatedPolygonFeature);
307
+ * // flat.geometry.coordinates: [[[lon, lat], [lon, lat], ...]]
308
+ * ```
309
+ */
310
+ const stripZ = ([lon, lat]) => [lon, lat];
311
+ function flattenFeatureTo2D(feature) {
312
+ const { geometry } = feature;
313
+ if (isLineStringType(geometry)) return {
314
+ ...feature,
315
+ geometry: {
316
+ ...geometry,
317
+ coordinates: geometry.coordinates.map(stripZ)
318
+ }
319
+ };
320
+ if (isMultiLineStringType(geometry) || isPolygonType(geometry)) return {
321
+ ...feature,
322
+ geometry: {
323
+ ...geometry,
324
+ coordinates: geometry.coordinates.map((ring) => ring.map(stripZ))
325
+ }
326
+ };
327
+ if (isMultiPolygonType(geometry)) return {
328
+ ...feature,
329
+ geometry: {
330
+ ...geometry,
331
+ coordinates: geometry.coordinates.map((polygon) => polygon.map((ring) => ring.map(stripZ)))
332
+ }
333
+ };
334
+ return feature;
335
+ }
336
+ /**
337
+ * Partition curtain features by interaction state (main, hovered, selected).
338
+ *
339
+ * @param allCurtainFeatures - All curtain features to partition
340
+ * @param hoveredShapeId - Currently hovered shape ID
341
+ * @param selectedShapeId - Currently selected shape ID
342
+ * @returns Partitioned curtain features by state
343
+ *
344
+ * @example
345
+ * ```typescript
346
+ * const { main, hovered, selected } = partitionCurtains(
347
+ * curtainFeatures,
348
+ * hoveredShapeId,
349
+ * selectedShapeId,
350
+ * );
351
+ * ```
352
+ */
353
+ function partitionCurtains(allCurtainFeatures, hoveredShapeId, selectedShapeId) {
354
+ const main = [];
355
+ const hovered = [];
356
+ const selected = [];
357
+ for (const f of allCurtainFeatures) {
358
+ const id = f.properties.shapeId;
359
+ if (id === selectedShapeId) selected.push(f);
360
+ else if (id === hoveredShapeId) hovered.push(f);
361
+ else main.push(f);
362
+ }
363
+ return {
364
+ main,
365
+ hovered,
366
+ selected
367
+ };
368
+ }
369
+ /**
370
+ * Build elevation indicator line segments for a set of elevated non-polygon features,
371
+ * applying interaction-state colors (brightened on hover/select).
372
+ *
373
+ * @param elevatedNonPolygons - Features to generate segments for
374
+ * @param features - Full feature array for hover-index lookup
375
+ * @param selectedShapeId - Currently selected shape ID
376
+ * @param hoverIndex - Currently hovered feature index
377
+ * @returns Array of colored line segments from ground to elevation
378
+ * @example
379
+ * ```typescript
380
+ * const lineData = buildIndicatorLineData(
381
+ * elevatedNonPolygons,
382
+ * allFeatures,
383
+ * selectedShapeId,
384
+ * hoverIndex,
385
+ * );
386
+ * // Each segment: { source: [lon, lat, 0], target: [lon, lat, elevation], color: [r, g, b, a] }
387
+ * ```
388
+ */
389
+ function buildIndicatorLineData(elevatedNonPolygons, features, selectedShapeId, hoverIndex) {
390
+ const lineData = [];
391
+ const hoveredFeatureShapeId = hoverIndex !== void 0 ? features[hoverIndex]?.properties?.shapeId : void 0;
392
+ for (const feature of elevatedNonPolygons) {
393
+ const { geometry } = feature;
394
+ const shapeId = feature.properties?.shapeId;
395
+ const isSelected = shapeId != null && shapeId === selectedShapeId;
396
+ const isHovered = shapeId != null && shapeId === hoveredFeatureShapeId;
397
+ const base = getLineColor(feature);
398
+ const factor = isSelected && isHovered ? BRIGHTNESS_FACTOR.HOVER_AND_SELECT : BRIGHTNESS_FACTOR.HOVER_OR_SELECT;
399
+ const color = isSelected || isHovered ? brightenColor(base, factor) : base;
400
+ for (const segment of createElevationLineSegments(geometry, color)) lineData.push(segment);
401
+ }
402
+ return lineData;
403
+ }
404
+
405
+ //#endregion
406
+ export { buildIndicatorLineData, classifyElevatedFeatures, createCurtainPolygonFeatures, flattenFeatureTo2D, getFeatureElevation, partitionCurtains };
407
+ //# sourceMappingURL=elevation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"elevation.js","names":["segments: LineSegment[]","allSegments: LineSegment[]","lines: Shape['feature'][]","polygons: Shape['feature'][]","nonPolygons: Shape['feature'][]","polygons: CurtainFeature[]","ring: number[][]","curtainFeatures: CurtainFeature[]","fillColor: Rgba255Tuple","coordArrays: number[][][]","main: CurtainFeature[]","hovered: CurtainFeature[]","selected: CurtainFeature[]","lineData: LineSegment[]"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/elevation.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport {\n isGeometryCollectionType,\n isLineGeometry,\n isLineStringType,\n isMultiLineStringType,\n isMultiPointType,\n isMultiPolygonType,\n isPointType,\n isPolygonGeometry,\n isPolygonType,\n} from '../../shared/types';\nimport { getLineColor } from '../../shared/utils/style-utils';\nimport { BRIGHTNESS_FACTOR } from '../constants';\nimport { brightenColor } from './display-style';\nimport type { Rgba255Tuple } from '@accelint/predicates';\nimport type { Shape, ShapeId } from '../../shared/types';\nimport type {\n CurtainFeature,\n ElevatedFeatureClassification,\n LineSegment,\n} from '../types';\n\n// =============================================================================\n// Coordinate Helpers\n// =============================================================================\n\n/**\n * Extract elevation from 3D coordinates.\n * Drills into nested coordinate arrays to find the z-value.\n *\n * @param coordinates - GeoJSON coordinates (single, array, or nested)\n * @returns The z-coordinate if present, otherwise 0\n *\n * @example\n * ```typescript\n * getElevationFromCoordinates([10, 20, 5000]); // 5000\n * getElevationFromCoordinates([[10, 20, 5000], [11, 21, 6000]]); // 5000\n * getElevationFromCoordinates([10, 20]); // 0\n * ```\n */\n// GeoJSON nesting is at most 4 levels deep (MultiPolygon → Polygon → Ring → Position)\nconst MAX_COORDINATE_DEPTH = 5;\n\nexport function getElevationFromCoordinates(coordinates: unknown): number {\n let current = coordinates;\n let depth = 0;\n\n while (Array.isArray(current) && depth < MAX_COORDINATE_DEPTH) {\n // Leaf coordinate: [lon, lat] or [lon, lat, elevation]\n if (!Array.isArray(current[0])) {\n return (current[2] as number) ?? 0;\n }\n // Drill into nested coordinate arrays (polygons, multi-polygons)\n current = current[0];\n depth++;\n }\n\n return 0;\n}\n\n/**\n * Get elevation from a feature's geometry.\n * Extracts z-coordinate, returning 0 for missing geometry or GeometryCollection.\n *\n * @param feature - The GeoJSON feature\n * @returns The elevation value, or 0\n *\n * @example\n * ```typescript\n * const elevation = getFeatureElevation(feature);\n * if (elevation > 0) {\n * // Feature has 3D elevation\n * }\n * ```\n */\nexport function getFeatureElevation(feature: Shape['feature']): number {\n // Prefer explicit maxElevation from feature properties (set at data creation time).\n // This avoids needing to drill into coordinates and is the source of truth for\n // Z-stripped polygons where coordinate Z has been removed.\n const maxElevation = feature.properties?.maxElevation;\n if (maxElevation !== undefined) {\n return maxElevation;\n }\n\n const { geometry } = feature;\n\n if (!geometry) {\n return 0;\n }\n\n // GeometryCollection doesn't have coordinates property\n if (isGeometryCollectionType(geometry)) {\n return 0;\n }\n\n return getElevationFromCoordinates(geometry.coordinates);\n}\n\n/**\n * Extract [lon, lat] from a coordinate array if valid.\n */\nfunction extractLonLat(\n coords: number[] | undefined,\n): [number, number] | undefined {\n if (!coords || coords.length < 2) {\n return undefined;\n }\n const [lon, lat] = coords;\n return lon !== undefined && lat !== undefined ? [lon, lat] : undefined;\n}\n\n/**\n * Extract a representative coordinate from a feature for elevation indicators.\n *\n * @param geometry - The feature geometry\n * @returns [lon, lat] or undefined if coordinates cannot be extracted\n */\nfunction getRepresentativeCoordinate(\n geometry: Shape['feature']['geometry'],\n): [number, number] | undefined {\n if (isPointType(geometry)) {\n return extractLonLat(geometry.coordinates as number[]);\n }\n\n if (isLineStringType(geometry) || isMultiPointType(geometry)) {\n return extractLonLat(geometry.coordinates[0] as number[]);\n }\n\n if (isMultiLineStringType(geometry)) {\n return extractLonLat(geometry.coordinates[0]?.[0] as number[] | undefined);\n }\n\n return undefined;\n}\n\n// =============================================================================\n// Line Segment Builders\n// =============================================================================\n\n/** Create a vertical line segment from ground to an elevated position. */\nfunction createVerticalSegment(\n lon: number,\n lat: number,\n elevation: number,\n color: Rgba255Tuple,\n): LineSegment {\n return {\n source: [lon, lat, 0] as [number, number, number],\n target: [lon, lat, elevation] as [number, number, number],\n color,\n };\n}\n\n/** Process a coordinates array and create vertical segments for each elevated point. */\nfunction processCoordinates(\n coordinates: number[][],\n color: Rgba255Tuple,\n): LineSegment[] {\n const segments: LineSegment[] = [];\n\n for (const coord of coordinates) {\n const [lon, lat, elevation] = coord;\n if (\n lon !== undefined &&\n lat !== undefined &&\n elevation !== undefined &&\n elevation > 0\n ) {\n segments.push(createVerticalSegment(lon, lat, elevation, color));\n }\n }\n\n return segments;\n}\n\n/**\n * Create elevation indicator line segments for a geometry.\n *\n * @param geometry - The feature geometry\n * @param color - RGBA color for the line segments\n * @returns Array of line segments from ground to elevated positions\n *\n * @example\n * ```typescript\n * const segments = createElevationLineSegments(\n * feature.geometry,\n * [255, 255, 255, 255],\n * );\n * // Each segment has { source: [lon, lat, 0], target: [lon, lat, elevation], color }\n * ```\n */\nexport function createElevationLineSegments(\n geometry: Shape['feature']['geometry'],\n color: Rgba255Tuple,\n): LineSegment[] {\n // Skip GeometryCollection\n if (isGeometryCollectionType(geometry)) {\n return [];\n }\n\n if (isLineStringType(geometry)) {\n // Create vertical lines at EACH coordinate to form a \"curtain\"\n return processCoordinates(geometry.coordinates as number[][], color);\n }\n\n if (isMultiLineStringType(geometry)) {\n // Create vertical lines for each coordinate in each line\n const allSegments: LineSegment[] = [];\n const lines = geometry.coordinates as number[][][];\n for (const line of lines) {\n for (const segment of processCoordinates(line, color)) {\n allSegments.push(segment);\n }\n }\n return allSegments;\n }\n\n // For Point and MultiPoint, use single representative coordinate\n const coords = getRepresentativeCoordinate(geometry);\n if (coords) {\n const [lon, lat] = coords;\n const elevation = getElevationFromCoordinates(geometry.coordinates);\n if (elevation > 0) {\n return [createVerticalSegment(lon, lat, elevation, color)];\n }\n }\n\n return [];\n}\n\n// =============================================================================\n// Feature Classification\n// =============================================================================\n\n/**\n * Single-pass classification of features by geometry type and elevation.\n * Avoids multiple .filter() passes per render frame.\n *\n * @param features - The features to classify\n * @param getElevation - Elevation accessor function\n * @returns Classified features: lines, polygons, nonPolygons\n *\n * @example\n * ```typescript\n * const { lines, polygons, nonPolygons } = classifyElevatedFeatures(\n * features,\n * getFeatureElevation,\n * );\n * ```\n */\nexport function classifyElevatedFeatures(\n features: Shape['feature'][],\n getElevation: (feature: Shape['feature']) => number,\n): ElevatedFeatureClassification {\n const lines: Shape['feature'][] = [];\n const polygons: Shape['feature'][] = [];\n const nonPolygons: Shape['feature'][] = [];\n\n for (const f of features) {\n const elevation = getElevation(f);\n if (elevation <= 0) {\n continue;\n }\n\n const { geometry } = f;\n if (isPolygonGeometry(geometry)) {\n polygons.push(f);\n } else {\n nonPolygons.push(f);\n if (isLineGeometry(geometry)) {\n lines.push(f);\n }\n }\n }\n\n return { lines, polygons, nonPolygons };\n}\n\n// =============================================================================\n// Curtain Polygon Builders\n// =============================================================================\n\n/**\n * Create curtain polygon features from a single LineString's coordinate array.\n * Each pair of consecutive coordinates becomes a vertical rectangular polygon.\n *\n * @param coordinates - Array of [lon, lat, elevation] coordinates\n * @param fillColor - RGBA fill color for the curtain\n * @param lineColor - RGBA line color for the curtain\n * @param shapeId - Optional shape ID for picking correlation\n * @returns Array of curtain polygon features\n *\n * @example\n * ```typescript\n * const curtains = createCurtainPolygonsFromLine(\n * [[10, 20, 5000], [11, 21, 6000]],\n * [98, 166, 255, 51],\n * [98, 166, 255, 255],\n * 'shape-1',\n * );\n * ```\n */\nexport function createCurtainPolygonsFromLine(\n coordinates: number[][],\n fillColor: Rgba255Tuple,\n lineColor: Rgba255Tuple,\n shapeId?: ShapeId,\n): CurtainFeature[] {\n const polygons: CurtainFeature[] = [];\n\n // Create vertical polygon for each pair of consecutive coordinates\n for (let i = 0; i < coordinates.length - 1; i++) {\n const coord1 = coordinates[i];\n const coord2 = coordinates[i + 1];\n if (coord1 === undefined || coord2 === undefined) {\n continue;\n }\n\n const [lon1, lat1, elev1] = coord1;\n const [lon2, lat2, elev2] = coord2;\n\n if (\n lon1 === undefined ||\n lat1 === undefined ||\n elev1 === undefined ||\n lon2 === undefined ||\n lat2 === undefined ||\n elev2 === undefined\n ) {\n continue;\n }\n\n // Skip if both points are at ground level\n if (elev1 === 0 && elev2 === 0) {\n continue;\n }\n\n // Create vertical rectangle as a polygon\n // Winding order: counter-clockwise when viewed from outside\n // Bottom-left -> Bottom-right -> Top-right -> Top-left -> Bottom-left (closing)\n const ring: number[][] = [\n [lon1, lat1, 0], // Bottom left (ground)\n [lon2, lat2, 0], // Bottom right (ground)\n [lon2, lat2, elev2], // Top right (elevated)\n [lon1, lat1, elev1], // Top left (elevated)\n [lon1, lat1, 0], // Close the ring\n ];\n\n polygons.push({\n type: 'Feature',\n geometry: {\n type: 'Polygon',\n coordinates: [ring],\n },\n properties: {\n fillColor,\n lineColor,\n shapeId,\n },\n });\n }\n\n return polygons;\n}\n\n/**\n * Create vertical curtain polygon features from elevated LineString features.\n * Converts elevated LineStrings into vertical rectangular polygons from ground to elevation.\n *\n * @param elevatedLines - Features with LineString/MultiLineString geometry and elevation\n * @param applyBaseOpacity - Whether to apply reduced opacity to fills\n * @returns Array of curtain polygon features\n *\n * @example\n * ```typescript\n * const curtainFeatures = createCurtainPolygonFeatures(elevatedLines, true);\n * // Returns vertical polygon features for each consecutive coordinate pair\n * ```\n */\nexport function createCurtainPolygonFeatures(\n elevatedLines: Shape['feature'][],\n applyBaseOpacity: boolean | undefined,\n): CurtainFeature[] {\n const curtainFeatures: CurtainFeature[] = [];\n\n for (const feature of elevatedLines) {\n const { geometry } = feature;\n const lineColorRGBA = getLineColor(feature);\n const shapeId = feature.properties?.shapeId;\n\n // Create fill color with base opacity (same as polygon fills)\n const fillColor: Rgba255Tuple = applyBaseOpacity\n ? [\n lineColorRGBA[0],\n lineColorRGBA[1],\n lineColorRGBA[2],\n Math.round(lineColorRGBA[3] * 0.2),\n ]\n : lineColorRGBA;\n\n // Normalize to array of coordinate arrays for both LineString and MultiLineString\n // Safe cast: elevatedLines only contains LineString/MultiLineString features\n let coordArrays: number[][][];\n if (isLineStringType(geometry)) {\n coordArrays = [geometry.coordinates as number[][]];\n } else {\n coordArrays = (geometry as { coordinates: number[][][] }).coordinates;\n }\n\n for (const coords of coordArrays) {\n for (const polygon of createCurtainPolygonsFromLine(\n coords,\n fillColor,\n lineColorRGBA,\n shapeId,\n )) {\n curtainFeatures.push(polygon);\n }\n }\n }\n\n return curtainFeatures;\n}\n\n// =============================================================================\n// Coordinate Projection\n// =============================================================================\n\n/**\n * Returns a copy of a feature with Z coordinates stripped from its geometry.\n *\n * Use this to project a feature back to the ground plane. Common use cases:\n * - Highlight outlines that should render at ground level\n * - Extruded polygons where elevation must come solely from `getElevation`,\n * not from coordinate Z (deck.gl's SolidPolygonLayer adds both together)\n *\n * @param feature - The feature to flatten\n * @returns A new feature with 2D coordinates, or the original if geometry\n * type is not handled (Point, MultiPoint, GeometryCollection)\n *\n * @example\n * ```typescript\n * // Elevated LineString [lon, lat, 20000] → [lon, lat]\n * const flat = flattenFeatureTo2D(elevatedLineStringFeature);\n * // flat.geometry.coordinates: [[lon, lat], [lon, lat], ...]\n *\n * // Elevated Polygon [lon, lat, 43500] → [lon, lat]\n * const flat = flattenFeatureTo2D(elevatedPolygonFeature);\n * // flat.geometry.coordinates: [[[lon, lat], [lon, lat], ...]]\n * ```\n */\n\nconst stripZ = ([lon, lat]: number[]) => [lon, lat] as [number, number];\n\nexport function flattenFeatureTo2D(\n feature: Shape['feature'],\n): Shape['feature'] {\n const { geometry } = feature;\n if (isLineStringType(geometry)) {\n return {\n ...feature,\n geometry: {\n ...geometry,\n coordinates: (geometry.coordinates as number[][]).map(stripZ),\n },\n };\n }\n if (isMultiLineStringType(geometry) || isPolygonType(geometry)) {\n return {\n ...feature,\n geometry: {\n ...geometry,\n coordinates: (geometry.coordinates as number[][][]).map((ring) =>\n ring.map(stripZ),\n ),\n },\n };\n }\n if (isMultiPolygonType(geometry)) {\n return {\n ...feature,\n geometry: {\n ...geometry,\n coordinates: (geometry.coordinates as number[][][][]).map((polygon) =>\n polygon.map((ring) => ring.map(stripZ)),\n ),\n },\n };\n }\n return feature;\n}\n\n/**\n * Partition curtain features by interaction state (main, hovered, selected).\n *\n * @param allCurtainFeatures - All curtain features to partition\n * @param hoveredShapeId - Currently hovered shape ID\n * @param selectedShapeId - Currently selected shape ID\n * @returns Partitioned curtain features by state\n *\n * @example\n * ```typescript\n * const { main, hovered, selected } = partitionCurtains(\n * curtainFeatures,\n * hoveredShapeId,\n * selectedShapeId,\n * );\n * ```\n */\nexport function partitionCurtains(\n allCurtainFeatures: CurtainFeature[],\n hoveredShapeId: ShapeId | undefined,\n selectedShapeId: ShapeId | undefined,\n): {\n main: CurtainFeature[];\n hovered: CurtainFeature[];\n selected: CurtainFeature[];\n} {\n const main: CurtainFeature[] = [];\n const hovered: CurtainFeature[] = [];\n const selected: CurtainFeature[] = [];\n\n for (const f of allCurtainFeatures) {\n const id = f.properties.shapeId;\n if (id === selectedShapeId) {\n selected.push(f);\n } else if (id === hoveredShapeId) {\n hovered.push(f);\n } else {\n main.push(f);\n }\n }\n\n return { main, hovered, selected };\n}\n\n/**\n * Build elevation indicator line segments for a set of elevated non-polygon features,\n * applying interaction-state colors (brightened on hover/select).\n *\n * @param elevatedNonPolygons - Features to generate segments for\n * @param features - Full feature array for hover-index lookup\n * @param selectedShapeId - Currently selected shape ID\n * @param hoverIndex - Currently hovered feature index\n * @returns Array of colored line segments from ground to elevation\n * @example\n * ```typescript\n * const lineData = buildIndicatorLineData(\n * elevatedNonPolygons,\n * allFeatures,\n * selectedShapeId,\n * hoverIndex,\n * );\n * // Each segment: { source: [lon, lat, 0], target: [lon, lat, elevation], color: [r, g, b, a] }\n * ```\n */\nexport function buildIndicatorLineData(\n elevatedNonPolygons: Shape['feature'][],\n features: Shape['feature'][],\n selectedShapeId: ShapeId | undefined,\n hoverIndex: number | undefined,\n): LineSegment[] {\n const lineData: LineSegment[] = [];\n const hoveredFeatureShapeId =\n hoverIndex !== undefined\n ? features[hoverIndex]?.properties?.shapeId\n : undefined;\n\n for (const feature of elevatedNonPolygons) {\n const { geometry } = feature;\n const shapeId = feature.properties?.shapeId;\n const isSelected = shapeId != null && shapeId === selectedShapeId;\n const isHovered = shapeId != null && shapeId === hoveredFeatureShapeId;\n\n const base = getLineColor(feature);\n const factor =\n isSelected && isHovered\n ? BRIGHTNESS_FACTOR.HOVER_AND_SELECT\n : BRIGHTNESS_FACTOR.HOVER_OR_SELECT;\n const color = isSelected || isHovered ? brightenColor(base, factor) : base;\n\n for (const segment of createElevationLineSegments(geometry, color)) {\n lineData.push(segment);\n }\n }\n\n return lineData;\n}\n"],"mappings":"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"}
@@ -0,0 +1,151 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ 'use client';
15
+
16
+ import { getLineColor } from "../../shared/utils/style-utils.js";
17
+ import { COFFIN_CORNERS, MAP_INTERACTION } from "../constants.js";
18
+
19
+ //#region src/deckgl/shapes/display-shape-layer/utils/icon-config.ts
20
+ /**
21
+ * Extract icon configuration from features in a single pass.
22
+ * Returns the first icon's atlas and mapping (all shapes share the same atlas).
23
+ * Uses early return for O(1) best case when the first feature has icons.
24
+ *
25
+ * @param features - Array of styled features to scan
26
+ * @returns Icon configuration with atlas and mapping if any feature has icons
27
+ *
28
+ * @example
29
+ * ```typescript
30
+ * const { hasIcons, atlas, mapping } = getIconConfig(features);
31
+ * if (hasIcons) {
32
+ * // Configure icon layer with atlas and mapping
33
+ * }
34
+ * ```
35
+ */
36
+ function getIconConfig(features) {
37
+ for (const feature of features) {
38
+ const icon = feature.properties?.styleProperties?.icon;
39
+ if (icon) return {
40
+ hasIcons: true,
41
+ atlas: icon.atlas,
42
+ mapping: icon.mapping
43
+ };
44
+ }
45
+ return { hasIcons: false };
46
+ }
47
+ /**
48
+ * Build icon layer props to spread onto a GeoJsonLayer.
49
+ * Returns an empty object when icons are not present.
50
+ *
51
+ * @param hasIcons - Whether any feature has icon config
52
+ * @param iconAtlas - Icon atlas URL or data
53
+ * @param iconMapping - Icon name-to-position mapping
54
+ * @returns Props object to spread onto GeoJsonLayer configuration
55
+ *
56
+ * @example
57
+ * ```typescript
58
+ * const iconProps = getIconLayerProps(hasIcons, iconAtlas, iconMapping);
59
+ * const layer = new GeoJsonLayer({ ...iconProps, data: features });
60
+ * ```
61
+ */
62
+ function getIconLayerProps(hasIcons, iconAtlas, iconMapping) {
63
+ if (!hasIcons) return {};
64
+ const result = {
65
+ getIcon: (d) => d.properties?.styleProperties?.icon?.name ?? "marker",
66
+ getIconSize: (d) => d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE,
67
+ getIconColor: getLineColor,
68
+ getIconPixelOffset: (d) => {
69
+ return [-1, -(d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE) / 2];
70
+ },
71
+ iconBillboard: false
72
+ };
73
+ if (iconAtlas) result.iconAtlas = iconAtlas;
74
+ if (iconMapping) result.iconMapping = iconMapping;
75
+ return result;
76
+ }
77
+ /**
78
+ * Build icon update triggers to spread onto GeoJsonLayer updateTriggers.
79
+ * Returns an empty object when icons are not present.
80
+ *
81
+ * @param hasIcons - Whether any feature has icon config
82
+ * @param features - Features array for trigger dependencies
83
+ * @returns Update triggers object to spread onto updateTriggers
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * const layer = new GeoJsonLayer({
88
+ * updateTriggers: {
89
+ * ...getIconUpdateTriggers(hasIcons, features),
90
+ * },
91
+ * });
92
+ * ```
93
+ */
94
+ function getIconUpdateTriggers(hasIcons, features) {
95
+ if (!hasIcons) return {};
96
+ return {
97
+ getIcon: [features],
98
+ getIconSize: [features],
99
+ getIconColor: [features],
100
+ getIconPixelOffset: [features]
101
+ };
102
+ }
103
+ /** WeakMap memoizing extended icon mappings by baseMapping identity. */
104
+ const coffinCornerCache = /* @__PURE__ */ new WeakMap();
105
+ /**
106
+ * Extend an icon mapping with coffin corners entries for hover/selection feedback.
107
+ * Memoized per baseMapping identity via WeakMap to avoid re-spreading per render frame.
108
+ *
109
+ * @param baseMapping - The original icon mapping from the feature's icon config
110
+ * @returns Extended mapping with coffin corners icons added
111
+ *
112
+ * @example
113
+ * ```typescript
114
+ * const extendedMapping = extendMappingWithCoffinCorners(iconMapping);
115
+ * // Result includes original mapping entries plus coffin corner icons
116
+ * ```
117
+ */
118
+ function extendMappingWithCoffinCorners(baseMapping) {
119
+ const cached = coffinCornerCache.get(baseMapping);
120
+ if (cached) return cached;
121
+ const result = {
122
+ ...baseMapping,
123
+ [COFFIN_CORNERS.HOVER_ICON]: {
124
+ x: 0,
125
+ y: 0,
126
+ width: 76,
127
+ height: 76,
128
+ mask: false
129
+ },
130
+ [COFFIN_CORNERS.SELECTED_ICON]: {
131
+ x: 76,
132
+ y: 0,
133
+ width: 76,
134
+ height: 76,
135
+ mask: false
136
+ },
137
+ [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {
138
+ x: 152,
139
+ y: 0,
140
+ width: 76,
141
+ height: 76,
142
+ mask: false
143
+ }
144
+ };
145
+ coffinCornerCache.set(baseMapping, result);
146
+ return result;
147
+ }
148
+
149
+ //#endregion
150
+ export { extendMappingWithCoffinCorners, getIconConfig, getIconLayerProps, getIconUpdateTriggers };
151
+ //# sourceMappingURL=icon-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"icon-config.js","names":["result: Record<string, unknown>"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/icon-config.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport { getLineColor } from '../../shared/utils/style-utils';\nimport { COFFIN_CORNERS, MAP_INTERACTION } from '../constants';\nimport type { Shape } from '../../shared/types';\n\n/** Icon mapping entry describing position and dimensions within an atlas. */\ntype IconMappingEntry = {\n x: number;\n y: number;\n width: number;\n height: number;\n mask?: boolean;\n};\n\n/** Result of scanning features for icon configuration. */\nexport type IconConfig = {\n /** Whether any feature in the dataset has icon configuration */\n hasIcons: boolean;\n /** Icon atlas image URL or data (shared across all features) */\n atlas?: string;\n /** Icon name-to-position mapping within the atlas */\n mapping?: Record<string, IconMappingEntry>;\n};\n\n/**\n * Extract icon configuration from features in a single pass.\n * Returns the first icon's atlas and mapping (all shapes share the same atlas).\n * Uses early return for O(1) best case when the first feature has icons.\n *\n * @param features - Array of styled features to scan\n * @returns Icon configuration with atlas and mapping if any feature has icons\n *\n * @example\n * ```typescript\n * const { hasIcons, atlas, mapping } = getIconConfig(features);\n * if (hasIcons) {\n * // Configure icon layer with atlas and mapping\n * }\n * ```\n */\nexport function getIconConfig(features: Shape['feature'][]): IconConfig {\n for (const feature of features) {\n const icon = feature.properties?.styleProperties?.icon;\n if (icon) {\n return {\n hasIcons: true,\n atlas: icon.atlas,\n mapping: icon.mapping,\n };\n }\n }\n return { hasIcons: false };\n}\n\n/**\n * Build icon layer props to spread onto a GeoJsonLayer.\n * Returns an empty object when icons are not present.\n *\n * @param hasIcons - Whether any feature has icon config\n * @param iconAtlas - Icon atlas URL or data\n * @param iconMapping - Icon name-to-position mapping\n * @returns Props object to spread onto GeoJsonLayer configuration\n *\n * @example\n * ```typescript\n * const iconProps = getIconLayerProps(hasIcons, iconAtlas, iconMapping);\n * const layer = new GeoJsonLayer({ ...iconProps, data: features });\n * ```\n */\nexport function getIconLayerProps(\n hasIcons: boolean,\n iconAtlas: string | undefined,\n iconMapping: Record<string, IconMappingEntry> | undefined,\n): Record<string, unknown> {\n if (!hasIcons) {\n return {};\n }\n\n const result: Record<string, unknown> = {\n getIcon: (d: Shape['feature']) =>\n d.properties?.styleProperties?.icon?.name ?? 'marker',\n getIconSize: (d: Shape['feature']) =>\n d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE,\n getIconColor: getLineColor,\n getIconPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE;\n return [-1, -iconSize / 2];\n },\n iconBillboard: false,\n };\n\n if (iconAtlas) {\n result.iconAtlas = iconAtlas;\n }\n\n if (iconMapping) {\n result.iconMapping = iconMapping;\n }\n\n return result;\n}\n\n/**\n * Build icon update triggers to spread onto GeoJsonLayer updateTriggers.\n * Returns an empty object when icons are not present.\n *\n * @param hasIcons - Whether any feature has icon config\n * @param features - Features array for trigger dependencies\n * @returns Update triggers object to spread onto updateTriggers\n *\n * @example\n * ```typescript\n * const layer = new GeoJsonLayer({\n * updateTriggers: {\n * ...getIconUpdateTriggers(hasIcons, features),\n * },\n * });\n * ```\n */\nexport function getIconUpdateTriggers(\n hasIcons: boolean,\n features: Shape['feature'][],\n): Record<string, unknown[]> {\n if (!hasIcons) {\n return {};\n }\n\n return {\n getIcon: [features],\n getIconSize: [features],\n getIconColor: [features],\n getIconPixelOffset: [features],\n };\n}\n\n/** WeakMap memoizing extended icon mappings by baseMapping identity. */\nconst coffinCornerCache = new WeakMap<\n Record<string, IconMappingEntry>,\n Record<string, IconMappingEntry>\n>();\n\n/**\n * Extend an icon mapping with coffin corners entries for hover/selection feedback.\n * Memoized per baseMapping identity via WeakMap to avoid re-spreading per render frame.\n *\n * @param baseMapping - The original icon mapping from the feature's icon config\n * @returns Extended mapping with coffin corners icons added\n *\n * @example\n * ```typescript\n * const extendedMapping = extendMappingWithCoffinCorners(iconMapping);\n * // Result includes original mapping entries plus coffin corner icons\n * ```\n */\nexport function extendMappingWithCoffinCorners(\n baseMapping: Record<string, IconMappingEntry>,\n): Record<string, IconMappingEntry> {\n const cached = coffinCornerCache.get(baseMapping);\n if (cached) {\n return cached;\n }\n\n const result = {\n ...baseMapping,\n [COFFIN_CORNERS.HOVER_ICON]: {\n x: 0,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_ICON]: {\n x: 76,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {\n x: 152,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n };\n coffinCornerCache.set(baseMapping, result);\n return result;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,SAAgB,cAAc,UAA0C;AACtE,MAAK,MAAM,WAAW,UAAU;EAC9B,MAAM,OAAO,QAAQ,YAAY,iBAAiB;AAClD,MAAI,KACF,QAAO;GACL,UAAU;GACV,OAAO,KAAK;GACZ,SAAS,KAAK;GACf;;AAGL,QAAO,EAAE,UAAU,OAAO;;;;;;;;;;;;;;;;;AAkB5B,SAAgB,kBACd,UACA,WACA,aACyB;AACzB,KAAI,CAAC,SACH,QAAO,EAAE;CAGX,MAAMA,SAAkC;EACtC,UAAU,MACR,EAAE,YAAY,iBAAiB,MAAM,QAAQ;EAC/C,cAAc,MACZ,EAAE,YAAY,iBAAiB,MAAM,QAAQ,gBAAgB;EAC/D,cAAc;EACd,qBAAqB,MAAwB;AAG3C,UAAO,CAAC,IAAI,EADV,EAAE,YAAY,iBAAiB,MAAM,QAAQ,gBAAgB,aACvC,EAAE;;EAE5B,eAAe;EAChB;AAED,KAAI,UACF,QAAO,YAAY;AAGrB,KAAI,YACF,QAAO,cAAc;AAGvB,QAAO;;;;;;;;;;;;;;;;;;;AAoBT,SAAgB,sBACd,UACA,UAC2B;AAC3B,KAAI,CAAC,SACH,QAAO,EAAE;AAGX,QAAO;EACL,SAAS,CAAC,SAAS;EACnB,aAAa,CAAC,SAAS;EACvB,cAAc,CAAC,SAAS;EACxB,oBAAoB,CAAC,SAAS;EAC/B;;;AAIH,MAAM,oCAAoB,IAAI,SAG3B;;;;;;;;;;;;;;AAeH,SAAgB,+BACd,aACkC;CAClC,MAAM,SAAS,kBAAkB,IAAI,YAAY;AACjD,KAAI,OACF,QAAO;CAGT,MAAM,SAAS;EACb,GAAG;GACF,eAAe,aAAa;GAC3B,GAAG;GACH,GAAG;GACH,OAAO;GACP,QAAQ;GACR,MAAM;GACP;GACA,eAAe,gBAAgB;GAC9B,GAAG;GACH,GAAG;GACH,OAAO;GACP,QAAQ;GACR,MAAM;GACP;GACA,eAAe,sBAAsB;GACpC,GAAG;GACH,GAAG;GACH,OAAO;GACP,QAAQ;GACR,MAAM;GACP;EACF;AACD,mBAAkB,IAAI,aAAa,OAAO;AAC1C,QAAO"}
@@ -0,0 +1,50 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ 'use client';
15
+
16
+ //#region src/deckgl/shapes/display-shape-layer/utils/interaction.ts
17
+ /**
18
+ * Compute hover/selection interaction state for a feature by index.
19
+ *
20
+ * @param feature - The feature to check
21
+ * @param selectedShapeId - Currently selected shape ID
22
+ * @param hoverIndex - Currently hovered feature index
23
+ * @param shapeIdToIndex - Map of shapeId to feature index for O(1) lookup
24
+ * @returns `{ isSelected, isHovered }`
25
+ * @example
26
+ * ```typescript
27
+ * const { isSelected, isHovered } = getPointInteractionState(
28
+ * feature,
29
+ * selectedShapeId,
30
+ * hoverIndex,
31
+ * shapeIdToIndex, // Map<ShapeId, number> from getFeaturesWithId()
32
+ * );
33
+ * if (isSelected || isHovered) {
34
+ * // render interaction feedback
35
+ * }
36
+ * ```
37
+ */
38
+ function getPointInteractionState(feature, selectedShapeId, hoverIndex, shapeIdToIndex) {
39
+ const shapeId = feature.properties?.shapeId;
40
+ const isSelected = shapeId != null && shapeId === selectedShapeId;
41
+ const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : void 0;
42
+ return {
43
+ isSelected,
44
+ isHovered: hoverIndex !== void 0 && featureIndex === hoverIndex
45
+ };
46
+ }
47
+
48
+ //#endregion
49
+ export { getPointInteractionState };
50
+ //# sourceMappingURL=interaction.js.map