@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.
- package/CHANGELOG.md +45 -0
- package/catalog-info.yaml +4 -4
- package/dist/camera/events.js +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/camera/store.d.ts +1 -1
- package/dist/camera/store.js +4 -6
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +1 -1
- package/dist/camera/types.js +1 -1
- package/dist/cursor-coordinates/constants.js +1 -1
- package/dist/cursor-coordinates/index.d.ts +1 -1
- package/dist/cursor-coordinates/index.js +1 -1
- package/dist/cursor-coordinates/store.d.ts +1 -1
- package/dist/cursor-coordinates/store.js +1 -1
- package/dist/cursor-coordinates/types.d.ts +1 -1
- package/dist/cursor-coordinates/types.js +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.js +1 -1
- package/dist/deckgl/base-map/controls.d.ts +1 -1
- package/dist/deckgl/base-map/controls.js +1 -1
- package/dist/deckgl/base-map/events.js +1 -1
- package/dist/deckgl/base-map/index.d.ts +3 -3
- package/dist/deckgl/base-map/index.js +15 -7
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/base-map/provider.d.ts +3 -3
- package/dist/deckgl/base-map/provider.js +3 -5
- package/dist/deckgl/base-map/provider.js.map +1 -1
- package/dist/deckgl/base-map/types.d.ts +1 -1
- package/dist/deckgl/base-map/types.js +1 -1
- package/dist/deckgl/index.d.ts +4 -4
- package/dist/deckgl/index.js +4 -4
- package/dist/deckgl/saved-viewports/index.d.ts +1 -1
- package/dist/deckgl/saved-viewports/index.js +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
- package/dist/deckgl/saved-viewports/storage.js +5 -10
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +66 -13
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +74 -35
- package/dist/deckgl/shapes/display-shape-layer/index.js +381 -154
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js +8 -2
- package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +108 -19
- package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +151 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +50 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -17
- package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +8 -9
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +4 -21
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +4 -34
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +11 -12
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +38 -4
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +4 -9
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
- package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
- package/dist/deckgl/shapes/edit-shape-layer/index.js +52 -21
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +7 -7
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +78 -14
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +14 -2
- package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +2 -2
- package/dist/deckgl/shapes/index.d.ts +4 -4
- package/dist/deckgl/shapes/index.js +5 -5
- package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
- package/dist/deckgl/shapes/shared/constants.js +55 -15
- package/dist/deckgl/shapes/shared/constants.js.map +1 -1
- package/dist/deckgl/shapes/shared/events.d.ts +5 -1
- package/dist/deckgl/shapes/shared/events.js +1 -1
- package/dist/deckgl/shapes/shared/events.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +174 -53
- package/dist/deckgl/shapes/shared/types.js +155 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +9 -6
- package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
- package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
- package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
- package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +1 -1
- package/dist/deckgl/symbol-layer/fiber.js +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +1 -1
- package/dist/deckgl/symbol-layer/index.js +1 -1
- package/dist/deckgl/text-layer/character-sets.js +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
- package/dist/deckgl/text-layer/default-settings.js +1 -1
- package/dist/deckgl/text-layer/fiber.d.ts +1 -1
- package/dist/deckgl/text-layer/fiber.js +1 -1
- package/dist/deckgl/text-layer/index.d.ts +1 -1
- package/dist/deckgl/text-layer/index.js +1 -1
- package/dist/deckgl/text-settings.d.ts +3 -3
- package/dist/deckgl/text-settings.js +1 -1
- package/dist/map-cursor/events.js +1 -1
- package/dist/map-cursor/index.d.ts +1 -1
- package/dist/map-cursor/index.js +1 -1
- package/dist/map-cursor/store.d.ts +1 -1
- package/dist/map-cursor/store.js +1 -1
- package/dist/map-cursor/types.d.ts +1 -1
- package/dist/map-cursor/types.js +1 -1
- package/dist/map-cursor/use-map-cursor.d.ts +1 -1
- package/dist/map-cursor/use-map-cursor.js +1 -1
- package/dist/map-mode/events.js +1 -1
- package/dist/map-mode/index.d.ts +1 -1
- package/dist/map-mode/index.js +1 -1
- package/dist/map-mode/store.d.ts +1 -1
- package/dist/map-mode/store.js +3 -8
- package/dist/map-mode/store.js.map +1 -1
- package/dist/map-mode/types.d.ts +1 -1
- package/dist/map-mode/types.js +1 -1
- package/dist/map-mode/use-map-mode.d.ts +1 -1
- package/dist/map-mode/use-map-mode.js +1 -1
- package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
- package/dist/maplibre/hooks/use-maplibre.js +1 -1
- package/dist/maplibre/index.d.ts +1 -1
- package/dist/maplibre/index.js +1 -1
- package/dist/shared/cleanup.d.ts +58 -0
- package/dist/shared/cleanup.js +93 -0
- package/dist/shared/cleanup.js.map +1 -0
- package/dist/shared/constants.js +1 -1
- package/dist/shared/create-map-store.d.ts +13 -1
- package/dist/shared/create-map-store.js +9 -4
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/shared/logger.js +31 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/units.js +1 -1
- package/dist/viewport/index.d.ts +1 -1
- package/dist/viewport/index.js +1 -1
- package/dist/viewport/store.d.ts +1 -1
- package/dist/viewport/store.js +1 -1
- package/dist/viewport/types.d.ts +1 -1
- package/dist/viewport/types.js +1 -1
- package/dist/viewport/utils.d.ts +1 -1
- package/dist/viewport/utils.js +1 -1
- package/dist/viewport/viewport-size.d.ts +3 -3
- package/dist/viewport/viewport-size.js +1 -1
- package/package.json +22 -19
- package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
- 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
|