@accelint/map-toolkit 2.0.0 → 3.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +50 -0
- package/README.md +8 -1
- package/catalog-info.yaml +9 -6
- package/dist/deckgl/base-map/index.d.ts +2 -2
- package/dist/deckgl/base-map/provider.d.ts +2 -2
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.d.ts +144 -0
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js +535 -0
- package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/index.d.ts +17 -0
- package/dist/deckgl/extensions/coffin-corner/index.js +19 -0
- package/dist/deckgl/extensions/coffin-corner/store.d.ts +96 -0
- package/dist/deckgl/extensions/coffin-corner/store.js +173 -0
- package/dist/deckgl/extensions/coffin-corner/store.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/types.d.ts +76 -0
- package/dist/deckgl/extensions/coffin-corner/types.js +27 -0
- package/dist/deckgl/extensions/coffin-corner/types.js.map +1 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts +81 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js +75 -0
- package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js.map +1 -0
- package/dist/deckgl/extensions/index.d.ts +15 -0
- package/dist/deckgl/extensions/index.js +16 -0
- package/dist/deckgl/index.d.ts +6 -1
- package/dist/deckgl/index.js +5 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +6 -15
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +31 -15
- package/dist/deckgl/shapes/display-shape-layer/index.js +97 -78
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +8 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +3 -48
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js +53 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js.map +1 -0
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +7 -3
- package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -3
- 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 +4 -2
- 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 -2
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +5 -2
- 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 +5 -2
- 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 +7 -2
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
- package/dist/deckgl/shapes/edit-shape-layer/index.js +22 -8
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +3 -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 +4 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +15 -4
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +4 -2
- 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 +7 -3
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
- package/dist/deckgl/shapes/index.d.ts +3 -2
- package/dist/deckgl/shapes/index.js +2 -1
- package/dist/deckgl/shapes/shared/constants.js +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +11 -4
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.d.ts +56 -0
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.js +131 -0
- package/dist/deckgl/shapes/shared/utils/duplicate-shape.js.map +1 -0
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +10 -7
- package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +3 -1
- package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
- package/dist/shared/units.d.ts +15 -56
- package/dist/shared/units.js +1 -52
- package/dist/shared/units.js.map +1 -1
- package/dist/viewport/index.d.ts +2 -3
- package/dist/viewport/index.js +1 -2
- package/dist/viewport/types.d.ts +8 -4
- package/dist/viewport/utils.d.ts +3 -3
- package/dist/viewport/utils.js +16 -8
- package/dist/viewport/utils.js.map +1 -1
- package/dist/viewport/viewport-size.d.ts +4 -3
- package/dist/viewport/viewport-size.js +2 -2
- package/dist/viewport/viewport-size.js.map +1 -1
- package/package.json +13 -6
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +0 -50
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["features: Shape['feature'][]","normalizedLineColors: Rgba255Tuple[]","feature: Shape['feature']","pointFeatures: Shape['feature'][]","lineData: LineSegment[]","getFillColor","layers: GeoJsonLayer[]","layers: Layer[]"],"sources":["../../../../src/deckgl/shapes/display-shape-layer/index.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 { Broadcast } from '@accelint/bus';\nimport { CompositeLayer } from '@deck.gl/core';\nimport { GeoJsonLayer, IconLayer, LineLayer } from '@deck.gl/layers';\nimport { createLoggerDomain } from '@/shared/logger';\nimport { SHAPE_LAYER_IDS } from '../shared/constants';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport {\n isLineGeometry,\n isPointType,\n isPolygonGeometry,\n} from '../shared/types';\nimport {\n getDashArray,\n getFillColor,\n getLineColor,\n} from '../shared/utils/style-utils';\nimport {\n BRIGHTNESS_FACTOR,\n COFFIN_CORNERS,\n DASH_EXTENSION,\n DEFAULT_DISPLAY_PROPS,\n HIGHLIGHT_COLOR_TUPLE,\n MAP_INTERACTION,\n MATERIAL_SETTINGS,\n} from './constants';\nimport { createShapeLabelLayer } from './shape-label-layer';\nimport {\n applyOverlayOpacity,\n brightenColor,\n getHighlightLineWidth,\n getHoverLineWidth,\n getOverlayFillColor,\n} from './utils/display-style';\nimport {\n buildIndicatorLineData,\n classifyElevatedFeatures,\n createCurtainPolygonFeatures,\n flattenFeatureTo2D,\n getFeatureElevation,\n partitionCurtains,\n} from './utils/elevation';\nimport {\n extendMappingWithCoffinCorners,\n getIconConfig,\n getIconLayerProps,\n getIconUpdateTriggers,\n} from './utils/icon-config';\nimport { getPointInteractionState } from './utils/interaction';\nimport type { Rgba255Tuple } from '@accelint/predicates';\nimport type { Layer, PickingInfo } from '@deck.gl/core';\nimport type { Shape, ShapeId } from '../shared/types';\nimport type {\n CurtainFeature,\n DisplayShapeLayerProps,\n DisplayShapeLayerState,\n ElevatedFeatureClassification,\n ElevationCache,\n FeaturesCache,\n IndicatorCache,\n LineSegment,\n} from './types';\n\nconst logger = createLoggerDomain('[DisplayShapeLayer]');\n\n/**\n * Typed event bus instance for shape events.\n * Provides type-safe event emission for shape interactions.\n */\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\n\n/**\n * DisplayShapeLayer - Read-only shapes visualization layer\n *\n * A composite deck.gl layer for displaying geographic shapes with interactive features.\n * Ideal for rendering shapes from external APIs or displaying read-only geographic data.\n *\n * ## Features\n * - **Multiple geometry types**: Point, LineString, Polygon, and Circle\n * - **Icon support**: Custom icons for Point geometries via icon atlases\n * - **Interactive selection**: Click handling with brightness overlay on polygon select, optional highlight effect for non-icon-Point shapes (if showHighlight=true)\n * - **Hover effects**: Polygon fills brighten via material lighting; outline width increases by 2px on hover\n * - **Customizable labels**: Flexible label positioning with per-shape or global options\n * - **Style properties**: Full control over colors, border/outline patterns, and opacity\n * - **Event bus integration**: Automatically emits shape events via @accelint/bus\n * - **Multi-map support**: Events include map instance ID for isolation\n *\n * ## Interaction Philosophy\n * Interactions never modify a shape's innate styling. Hover and selection are always\n * additive overlays rendered apart from the main layer using opacity-scaled fill colors\n * and material-based brightness — the base shape is never altered.\n *\n * ## Layer Structure\n * Renders up to seven sublayers (in order, bottom to top):\n * 1. **Select layer**: Selection brightness overlay for polygon shapes\n * 2. **Hover layer**: Hover brightness overlay for polygon shapes\n * 3. **Coffin corners layer**: Selection/hover feedback for Point shapes with icons\n * 4. **Elevation visualization**: Curtains (LineStrings) or wireframes (polygons) — elevation only\n * 5. **Elevation indicators**: Vertical strut lines for elevated non-polygon shapes — elevation only\n * 6. **Main GeoJsonLayer**: Shape geometries with styling and interaction\n * 7. **Label layer**: Text labels (if showLabels enabled)\n *\n * ## Icon Atlas Constraint\n * When using icons for Point geometries, all shapes in a single layer must share the\n * same icon atlas. The layer uses the first atlas found across all features. If you\n * need icons from different atlases, use separate DisplayShapeLayer instances.\n *\n * ## Event Bus Integration\n * Automatically emits shape events that can be consumed anywhere in your app:\n * - `shapes:selected` - Emitted when a shape is clicked (includes mapId)\n * - `shapes:hovered` - Emitted when the hovered shape changes (deduplicated, includes mapId)\n *\n * For selection with auto-deselection, use the companion `useSelectShape` hook which handles\n * all the event wiring automatically. See the example below.\n *\n * @example Basic usage with useSelectShape hook (recommended)\n * ```tsx\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n * import { uuid } from '@accelint/core';\n *\n * const MAP_ID = uuid();\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"my-shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * showLabels=\"always\"\n * pickable={true}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With custom label positioning\n * ```tsx\n * <displayShapeLayer\n * id=\"my-shapes\"\n * data={shapes}\n * showLabels=\"always\"\n * labelOptions={{\n * // Position circle labels at the top\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom',\n * circleLabelOffset: [0, -10],\n * // Position line labels at the middle\n * lineStringLabelCoordinateAnchor: 'middle',\n * }}\n * />\n * ```\n */\nexport class DisplayShapeLayer extends CompositeLayer<DisplayShapeLayerProps> {\n // State is typed via DisplayShapeLayerState but deck.gl doesn't support generic state\n declare state: DisplayShapeLayerState;\n\n /** Cache for transformed features to avoid recreating objects on every render */\n private featuresCache: FeaturesCache | null = null;\n /** Cache for elevation classification and curtain features */\n private elevationCache: ElevationCache | null = null;\n /** Cache for elevation indicator line segments */\n private indicatorCache: IndicatorCache | null = null;\n\n static override layerName = 'DisplayShapeLayer';\n\n static override defaultProps = {\n ...DEFAULT_DISPLAY_PROPS,\n };\n\n /**\n * Clean up state and caches when layer is destroyed\n */\n override finalizeState(): void {\n // Clear hover state to prevent stale references\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined, lastHoveredId: undefined });\n }\n // Clear features cache\n this.featuresCache = null;\n this.elevationCache = null;\n this.indicatorCache = null;\n }\n\n /**\n * Resolved highlight color — uses prop if provided, falls back to default.\n */\n private get resolvedHighlight(): Rgba255Tuple {\n return this.props.highlightColor ?? HIGHLIGHT_COLOR_TUPLE;\n }\n\n /**\n * Handle picking events from the main shapes layer\n */\n private handleMainLayerPick(info: PickingInfo, mode?: string): void {\n // Handle click events (deck.gl uses 'query' mode for clicks)\n if (mode === 'query') {\n this.handleShapeClick(info);\n }\n\n // Handle hover events (including when mode is undefined, which is hover)\n if (mode === 'hover' || !mode) {\n // Update hover state\n if (info.index !== undefined && info.index !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: info.index });\n }\n\n // Call hover callback\n this.handleShapeHover(info);\n }\n }\n\n /**\n * Handle picking events from curtain layers and map back to original LineString\n */\n private handleCurtainPick(\n info: PickingInfo,\n mode?: string,\n ): PickingInfo | undefined {\n if (!info.object) {\n return undefined;\n }\n\n const curtainShapeId = info.object.properties?.shapeId;\n if (!curtainShapeId) {\n return undefined;\n }\n\n const features = this.getFeaturesWithId();\n const featureIndex = this.featuresCache?.shapeIdToIndex.get(curtainShapeId);\n\n if (featureIndex === undefined) {\n return undefined;\n }\n\n // Create modified info with the original feature\n const modifiedInfo = {\n ...info,\n index: featureIndex,\n object: features[featureIndex],\n };\n\n // Handle click events\n if (mode === 'query') {\n this.handleShapeClick(modifiedInfo);\n }\n\n // Handle hover events\n if (mode === 'hover' || !mode) {\n if (featureIndex !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: featureIndex });\n }\n this.handleShapeHover(modifiedInfo);\n }\n\n return modifiedInfo;\n }\n\n /**\n * Override getPickingInfo to handle events from sublayers\n * This is the correct pattern for CompositeLayer event handling\n */\n override getPickingInfo({\n info,\n mode,\n sourceLayer,\n }: {\n info: PickingInfo;\n mode?: string;\n sourceLayer?: Layer | null;\n }) {\n // Handle hover end (moving off all shapes to empty space)\n // This must be checked BEFORE layer-specific logic to ensure hover state clears properly\n if (\n (mode === 'hover' || !mode) &&\n (info.index === undefined || info.index < 0)\n ) {\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined });\n }\n this.handleShapeHover(info);\n return info;\n }\n\n // Check if this picking event came from our main shapes layer\n if (sourceLayer?.id === `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`) {\n this.handleMainLayerPick(info, mode);\n return info;\n }\n\n // Check if this picking event came from a curtain layer (elevation visualization for LineStrings)\n const isCurtainLayer = sourceLayer?.id?.includes('-elevation-curtain');\n if (isCurtainLayer) {\n const curtainInfo = this.handleCurtainPick(info, mode);\n if (curtainInfo) {\n return curtainInfo;\n }\n }\n\n return info;\n }\n\n /**\n * Convert shapes to GeoJSON features with shapeId in properties.\n * Uses caching to avoid recreating objects on every render cycle.\n */\n private getFeaturesWithId(): Shape['feature'][] {\n const { data } = this.props;\n\n // Return cached features if data hasn't changed (identity check)\n if (this.featuresCache?.data === data) {\n return this.featuresCache.features;\n }\n\n // Transform features and build shapeId->index map in a single pass\n const features: Shape['feature'][] = [];\n const shapeIdToIndex = new Map<ShapeId, number>();\n const normalizedLineColors: Rgba255Tuple[] = [];\n\n for (const [i, shape] of data.entries()) {\n let feature: Shape['feature'] = {\n ...shape.feature,\n properties: {\n ...shape.feature.properties,\n shapeId: shape.id,\n },\n };\n\n // For polygon geometries with elevation: strip Z coordinates to prevent\n // deck.gl double-counting (SolidPolygonLayer adds coordinate Z + getElevation).\n // The feature's maxElevation property is the source of truth for getFeatureElevation.\n if (\n isPolygonGeometry(feature.geometry) &&\n getFeatureElevation(feature) > 0\n ) {\n feature = flattenFeatureTo2D(feature);\n }\n\n features.push(feature);\n shapeIdToIndex.set(shape.id, i);\n normalizedLineColors.push(getLineColor(shape.feature));\n }\n\n this.featuresCache = {\n data,\n features,\n shapeIdToIndex,\n normalizedLineColors,\n };\n return features;\n }\n\n /**\n * Look up a shape by ID from the data prop.\n * Used by event handlers to get full shape without storing in feature properties.\n */\n private getShapeById(shapeId: ShapeId): Shape | undefined {\n const index = this.featuresCache?.shapeIdToIndex.get(shapeId);\n return index !== undefined ? this.props.data[index] : undefined;\n }\n\n /**\n * Handle shape click\n */\n private handleShapeClick = (info: PickingInfo): void => {\n const { onShapeClick, mapId } = this.props;\n\n if (!info.object) {\n return;\n }\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object.properties?.shapeId as ShapeId | undefined;\n if (!shapeId) {\n return;\n }\n\n const shape = this.getShapeById(shapeId);\n if (!shape) {\n return;\n }\n\n // Emit shape selected event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.selected, { shapeId: shape.id, mapId });\n\n // Call callback if provided\n if (onShapeClick) {\n onShapeClick(shape);\n }\n };\n\n /**\n * Handle shape hover\n */\n private handleShapeHover = (info: PickingInfo): void => {\n const { onShapeHover, mapId } = this.props;\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object?.properties?.shapeId as ShapeId | undefined;\n const shape = shapeId ? this.getShapeById(shapeId) : undefined;\n\n // Dedupe hover events - only emit if hovered shape changed\n if (shapeId !== this.state?.lastHoveredId) {\n this.setState({ lastHoveredId: shapeId });\n\n // Emit shape hovered event via bus (include mapId for multi-map isolation)\n // Cast to null at the bus boundary — ShapeHoveredEvent uses null for hover-end\n shapeBus.emit(ShapeEvents.hovered, {\n shapeId: shapeId ?? null,\n mapId,\n });\n }\n\n // Always call callback if provided (for local state updates)\n if (onShapeHover) {\n onShapeHover(shape);\n }\n };\n\n /**\n * Get or compute elevation-derived data (feature classification + curtain features).\n * Cached on features identity and applyBaseOpacity to avoid per-frame recomputation.\n */\n private getElevationData(\n features: Shape['feature'][],\n applyBaseOpacity: boolean | undefined,\n ): {\n classification: ElevatedFeatureClassification;\n curtainFeatures: CurtainFeature[];\n } {\n if (\n this.elevationCache !== null &&\n this.elevationCache.features === features &&\n this.elevationCache.applyBaseOpacity === applyBaseOpacity\n ) {\n return this.elevationCache;\n }\n\n const classification = classifyElevatedFeatures(\n features,\n getFeatureElevation,\n );\n const curtainFeatures = createCurtainPolygonFeatures(\n classification.lines,\n applyBaseOpacity,\n );\n\n this.elevationCache = {\n features,\n applyBaseOpacity,\n classification,\n curtainFeatures,\n };\n return { classification, curtainFeatures };\n }\n\n /**\n * Render highlight sublayer (underneath main layer).\n * Note: Points with icons use coffin corners instead of highlight layer.\n */\n private renderHighlightLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { selectedShapeId, showHighlight } = this.props;\n\n // Skip if no selection or highlight is disabled\n if (!selectedShapeId || showHighlight === false) {\n return [];\n }\n\n const featureIndex =\n this.featuresCache?.shapeIdToIndex.get(selectedShapeId);\n\n const selectedFeature =\n featureIndex !== undefined ? features[featureIndex] : undefined;\n\n if (!selectedFeature) {\n return [];\n }\n\n // Skip highlight layer for Point geometries with icons - they use coffin corners instead\n if (isPointType(selectedFeature.geometry)) {\n const hasIcon = !!selectedFeature.properties?.styleProperties?.icon;\n if (hasIcon) {\n return [];\n }\n }\n\n // Strip Z from LineString coordinates so the highlight outline renders at\n // ground level rather than following the elevated path\n const highlightFeature = flattenFeatureTo2D(selectedFeature);\n const lineColor = this.resolvedHighlight;\n\n // Render 2D highlight layer (outline only)\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,\n data: [highlightFeature],\n\n // Styling - outline only for 2D shapes\n filled: false,\n stroked: true,\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n getLineColor: lineColor,\n getLineWidth: getHighlightLineWidth,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n getLineColor: [this.props.highlightColor],\n getLineWidth: [selectedShapeId, features],\n },\n }),\n ];\n }\n\n /**\n * Render selection overlay layer for polygon shapes.\n * Mirrors renderHoverLayer but triggers on selectedShapeId instead of hover.\n * When a shape is both selected and hovered, both layers stack for a brighter combined effect.\n */\n private renderSelectLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { selectedShapeId, enableElevation } = this.props;\n\n if (!selectedShapeId) {\n return [];\n }\n\n const featureIndex =\n this.featuresCache?.shapeIdToIndex.get(selectedShapeId);\n\n const selectedFeature =\n featureIndex !== undefined ? features[featureIndex] : undefined;\n\n if (!selectedFeature) {\n return [];\n }\n\n // Only render for polygons — non-polygon shapes have no fill to brighten\n if (!isPolygonGeometry(selectedFeature.geometry)) {\n return [];\n }\n\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_SELECTION}`,\n data: [selectedFeature],\n\n filled: true,\n stroked: false,\n getFillColor: getOverlayFillColor,\n\n // Material brightness for selection; extrusion only when elevation enabled\n extruded: enableElevation,\n getElevation: getFeatureElevation,\n material: MATERIAL_SETTINGS.HOVER_OR_SELECT,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n data: [features, selectedShapeId],\n getFillColor: [features],\n getElevation: [features],\n },\n }),\n ];\n }\n\n /**\n * Render hover layer for all polygon shapes (2D and 3D).\n * Overlays the shape's base fill with brighter material lighting.\n * Stacks with other interaction layers (e.g. selection highlight underneath).\n */\n private renderHoverLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { enableElevation, selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Only render if something is hovered\n if (hoverIndex === undefined) {\n return [];\n }\n\n const hoveredFeature = features[hoverIndex];\n if (!hoveredFeature) {\n return [];\n }\n\n // Only render for polygons\n if (!isPolygonGeometry(hoveredFeature.geometry)) {\n return [];\n }\n\n const isAlsoSelected =\n hoveredFeature.properties?.shapeId === selectedShapeId;\n const material = isAlsoSelected\n ? MATERIAL_SETTINGS.HOVER_AND_SELECT\n : MATERIAL_SETTINGS.HOVER_OR_SELECT;\n\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-hover`,\n data: [hoveredFeature],\n\n // Styling\n filled: true,\n stroked: false, // Main layer handles strokes; this layer is fill-only\n getFillColor: getOverlayFillColor,\n\n // Material brightness scales with interaction state; extrusion only when elevation enabled\n extruded: enableElevation,\n getElevation: getFeatureElevation,\n material,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n data: [features, hoverIndex],\n getFillColor: [features],\n getElevation: [features],\n material: [selectedShapeId, hoverIndex],\n },\n }),\n ];\n }\n\n /**\n * Render coffin corners layer for Point geometries that have icons on hover/select\n * Coffin corners provide visual feedback for points instead of select layer\n */\n private renderCoffinCornersLayer(features: Shape['feature'][]): IconLayer[] {\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Use cached shapeId->index map for O(1) lookup\n const shapeIdToIndex = this.featuresCache?.shapeIdToIndex;\n if (!shapeIdToIndex) {\n return [];\n }\n\n // Find point features that need coffin corners (hovered or selected)\n const pointFeatures: Shape['feature'][] = [];\n for (const f of features) {\n if (f.geometry.type !== 'Point') {\n continue;\n }\n if (!f.properties?.styleProperties?.icon) {\n continue;\n }\n const { isSelected, isHovered } = getPointInteractionState(\n f,\n selectedShapeId,\n hoverIndex,\n shapeIdToIndex,\n );\n if (isSelected || isHovered) {\n pointFeatures.push(f);\n }\n }\n\n if (pointFeatures.length === 0) {\n return [];\n }\n\n // Get icon atlas from first point feature (all should share the same atlas)\n const firstPointIcon = pointFeatures[0]?.properties?.styleProperties?.icon;\n const iconAtlas = firstPointIcon?.atlas;\n const iconMapping = firstPointIcon?.mapping;\n\n if (!(iconAtlas && iconMapping)) {\n logger.warn(\n 'Point shape has icon style but missing iconAtlas or iconMapping - coffin corners will not render',\n );\n return [];\n }\n\n const extendedMapping = extendMappingWithCoffinCorners(iconMapping);\n\n return [\n new IconLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-coffin-corners`,\n data: pointFeatures,\n iconAtlas,\n iconMapping: extendedMapping,\n getIcon: (d: Shape['feature']) => {\n const { isSelected, isHovered } = getPointInteractionState(\n d,\n selectedShapeId,\n hoverIndex,\n shapeIdToIndex,\n );\n if (isSelected && isHovered) {\n return COFFIN_CORNERS.SELECTED_HOVER_ICON;\n }\n if (isSelected) {\n return COFFIN_CORNERS.SELECTED_ICON;\n }\n return COFFIN_CORNERS.HOVER_ICON;\n },\n getSize: COFFIN_CORNERS.SIZE,\n getPosition: (d: Shape['feature']) => {\n const coords = isPointType(d.geometry)\n ? d.geometry.coordinates\n : [0, 0];\n return coords as [number, number];\n },\n getPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n // Center the coffin corners on the point icon\n return [-1, -iconSize / 2];\n },\n billboard: false,\n pickable: false,\n updateTriggers: {\n getIcon: [selectedShapeId, this.state?.hoverIndex],\n data: [features, selectedShapeId, this.state?.hoverIndex],\n },\n }),\n ];\n }\n\n /**\n * Render main shapes layer\n */\n private renderMainLayer(features: Shape['feature'][]): GeoJsonLayer {\n const { pickable, applyBaseOpacity, selectedShapeId } = this.props;\n\n // Single-pass icon config extraction (O(1) best case with early return)\n const {\n hasIcons,\n atlas: iconAtlas,\n mapping: iconMapping,\n } = getIconConfig(features);\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,\n data: features,\n\n // Styling\n filled: true,\n stroked: true,\n getFillColor: (d: Shape['feature']) => getFillColor(d, applyBaseOpacity),\n getLineColor: (d: Shape['feature'], info) => {\n // Read pre-normalized color from cache — avoids normalizeColor allocation per feature per trigger\n const baseColor =\n this.featuresCache?.normalizedLineColors[info?.index ?? -1] ??\n getLineColor(d);\n const isHovered = info?.index === this.state?.hoverIndex;\n const isSelected = d.properties?.shapeId === selectedShapeId;\n if (isHovered && isSelected) {\n return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_AND_SELECT);\n }\n if (isHovered || isSelected) {\n return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_OR_SELECT);\n }\n return baseColor;\n },\n getLineWidth: (d, info) => {\n // Skip hover line width for elevated LineStrings - curtain handles it\n if (\n this.props.enableElevation &&\n isLineGeometry(d.geometry) &&\n getFeatureElevation(d) > 0\n ) {\n return d.properties?.styleProperties?.lineWidth ?? 2;\n }\n\n const isHovered = info?.index === this.state?.hoverIndex;\n return getHoverLineWidth(d, isHovered);\n },\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n lineWidthMaxPixels: 20,\n\n // Polygon extrusion (when elevation enabled)\n // NOTE: deck.gl has a limitation where stroked polygon outlines only render when !extruded && stroked\n // This means hover/selection styling (dash arrays, line width) won't work for extruded polygons\n // See: https://github.com/visgl/deck.gl/blob/master/modules/layers/src/geojson-layer/geojson-layer.ts#L467-508\n // Tested fix: Changing condition to `!wireframe && stroked` in deck.gl source did NOT resolve the issue\n // Solution: Separate hover/select layers with material-based lighting for extruded polygons\n extruded: this.props.enableElevation ?? false,\n getElevation: getFeatureElevation,\n ...(this.props.enableElevation\n ? {\n material: MATERIAL_SETTINGS.NORMAL,\n }\n : {}),\n\n // Points - use icons if any feature has icon config, otherwise circles\n pointType: hasIcons ? 'icon' : 'circle',\n getPointRadius: (d) => {\n const iconSize = d.properties?.styleProperties?.icon?.size;\n return iconSize ?? 2;\n },\n pointRadiusUnits: 'pixels',\n\n // Icon configuration (only used if pointType includes 'icon')\n ...getIconLayerProps(hasIcons, iconAtlas, iconMapping),\n\n // Dash pattern support for shape-configured line patterns (solid/dashed/dotted)\n extensions: DASH_EXTENSION,\n getDashArray,\n\n // Behavior\n pickable,\n autoHighlight: false, // We handle highlighting manually\n // Note: onClick and onHover are handled via getPickingInfo() override\n\n // Depth testing - enable for 3D shapes to prevent rendering through globe\n ...(this.props.enableElevation\n ? {\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n }\n : {}),\n\n // Update triggers\n updateTriggers: {\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features, this.state?.hoverIndex, selectedShapeId],\n getLineWidth: [features, this.state?.hoverIndex],\n getDashArray: [features],\n getPointRadius: [features],\n ...getIconUpdateTriggers(hasIcons, features),\n },\n });\n }\n\n /**\n * Render labels layer\n * Supports three modes:\n * - 'always': Show labels for all shapes\n * - 'hover': Show label only for the currently hovered shape\n * - 'never': No labels\n */\n private renderLabelsLayer(): ReturnType<typeof createShapeLabelLayer>[] {\n const { showLabels, data, labelOptions } = this.props;\n\n // No labels if disabled\n if (showLabels === 'never') {\n return [];\n }\n\n // Determine which shapes to show labels for\n let labelData = data;\n if (showLabels === 'hover') {\n const hoverIndex = this.state?.hoverIndex;\n if (hoverIndex === undefined) {\n return []; // No shape hovered, no label to show\n }\n const hoveredShape = data[hoverIndex];\n labelData = hoveredShape ? [hoveredShape] : [];\n }\n\n if (labelData.length === 0) {\n return [];\n }\n\n return [\n createShapeLabelLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,\n data: labelData,\n labelOptions,\n }),\n ];\n }\n\n /**\n * Render vertical elevation indicator lines for non-polygon shapes.\n * Creates vertical \"strut\" lines from ground level to elevated features.\n * For LineStrings, creates a \"curtain\" effect with vertical lines at each coordinate.\n * Polygons use wireframe extrusion instead.\n */\n private renderElevationIndicatorLayer(\n features: Shape['feature'][],\n elevatedNonPolygons: Shape['feature'][],\n ): LineLayer[] {\n if (elevatedNonPolygons.length === 0) {\n return [];\n }\n\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Return stable lineData when geometry and interaction state haven't changed.\n // A new array reference would force deck.gl to rebuild GPU buffers every frame.\n const cache = this.indicatorCache;\n let lineData: LineSegment[];\n\n if (\n cache !== null &&\n cache.features === features &&\n cache.selectedShapeId === selectedShapeId &&\n cache.hoverIndex === hoverIndex\n ) {\n lineData = cache.lineData;\n } else {\n lineData = buildIndicatorLineData(\n elevatedNonPolygons,\n features,\n selectedShapeId,\n hoverIndex,\n );\n this.indicatorCache = { features, selectedShapeId, hoverIndex, lineData };\n }\n\n if (lineData.length === 0) {\n return [];\n }\n\n return [\n new LineLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-indicators`,\n data: lineData,\n getSourcePosition: (d: LineSegment) => d.source,\n getTargetPosition: (d: LineSegment) => d.target,\n getColor: (d: LineSegment) => d.color,\n getWidth: 2,\n widthUnits: 'pixels',\n pickable: false,\n updateTriggers: {\n data: [features, selectedShapeId, hoverIndex],\n getColor: [features, selectedShapeId, hoverIndex],\n },\n }),\n ];\n }\n\n /**\n * Create a single curtain GeoJsonLayer with shared configuration.\n */\n private createCurtainGeoJsonLayer(\n idSuffix: string,\n data: CurtainFeature[],\n getFillColor: (d: CurtainFeature) => Rgba255Tuple,\n dataTriggers: unknown[],\n fillColorTriggers: unknown[],\n ): GeoJsonLayer {\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-${idSuffix}`,\n data: data,\n filled: true,\n stroked: false,\n // biome-ignore lint/style/useNamingConvention: deck.gl uses _full3d naming\n _full3d: true,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accessor type incompatible with CurtainFeature\n getFillColor: getFillColor as any,\n pickable: this.props.pickable ?? true,\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n updateTriggers: {\n data: dataTriggers,\n getFillColor: fillColorTriggers,\n },\n });\n }\n\n /**\n * Render curtain layers for elevated LineStrings.\n * Creates three separate layers for main, hovered, and selected states.\n */\n private renderCurtainLayers(\n features: Shape['feature'][],\n allCurtainFeatures: CurtainFeature[],\n ): GeoJsonLayer[] {\n const layers: GeoJsonLayer[] = [];\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n const hoveredShapeId =\n hoverIndex !== undefined && features[hoverIndex]\n ? features[hoverIndex].properties?.shapeId\n : undefined;\n\n const { main, hovered, selected } = partitionCurtains(\n allCurtainFeatures,\n hoveredShapeId,\n selectedShapeId,\n );\n\n const isSelectedHovered = selectedShapeId === hoveredShapeId;\n const dataTriggers = [features, hoveredShapeId, selectedShapeId];\n\n if (main.length > 0) {\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain',\n main,\n (d) => d.properties.fillColor,\n dataTriggers,\n [features, this.props.applyBaseOpacity],\n ),\n );\n }\n\n if (hovered.length > 0) {\n // All curtain segments in a partition share the same lineColor (same hovered shape).\n // Precompute once to avoid N allocations in the getFillColor accessor.\n const hoveredColor = applyOverlayOpacity(\n brightenColor(\n (hovered[0] as CurtainFeature).properties.lineColor,\n BRIGHTNESS_FACTOR.HOVER_OR_SELECT,\n ),\n );\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain-hover',\n hovered,\n () => hoveredColor,\n dataTriggers,\n [features],\n ),\n );\n }\n\n if (selected.length > 0) {\n // Selected + hovered stacks to HOVER_AND_SELECT; selected only is HOVER_OR_SELECT\n const factor = isSelectedHovered\n ? BRIGHTNESS_FACTOR.HOVER_AND_SELECT\n : BRIGHTNESS_FACTOR.HOVER_OR_SELECT;\n const selectedColor = applyOverlayOpacity(\n brightenColor(\n (selected[0] as CurtainFeature).properties.lineColor,\n factor,\n ),\n );\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain-selected',\n selected,\n () => selectedColor,\n dataTriggers,\n [features, isSelectedHovered],\n ),\n );\n }\n\n return layers;\n }\n\n /**\n * Render elevation visualization layers (curtains for lines, wireframes for polygons).\n */\n private renderElevationVisualizationLayer(\n features: Shape['feature'][],\n allCurtainFeatures: CurtainFeature[],\n elevatedPolygons: Shape['feature'][],\n ): GeoJsonLayer[] {\n const layers: GeoJsonLayer[] = [];\n\n // LineString curtains (filled vertical surfaces) — pre-computed by getElevationData()\n if (allCurtainFeatures.length > 0) {\n layers.push(...this.renderCurtainLayers(features, allCurtainFeatures));\n }\n\n // Polygon wireframes (extruded 3D boxes with edges)\n if (elevatedPolygons.length > 0) {\n const { applyBaseOpacity } = this.props;\n layers.push(\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-wireframe`,\n data: elevatedPolygons,\n filled: false,\n stroked: false,\n extruded: true,\n wireframe: true,\n getElevation: getFeatureElevation,\n getFillColor: (d: Shape['feature']) =>\n getFillColor(d, applyBaseOpacity),\n getLineColor,\n pickable: false,\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n updateTriggers: {\n getElevation: [features],\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features],\n },\n }),\n );\n }\n\n return layers;\n }\n\n /**\n * Render all sublayers\n */\n renderLayers(): Layer[] {\n // Compute features once per render cycle for performance\n const features = this.getFeaturesWithId();\n const enableElevation = this.props.enableElevation ?? false;\n\n const layers: Layer[] = [\n ...this.renderHighlightLayer(features),\n ...this.renderSelectLayer(features),\n ...this.renderHoverLayer(features),\n ...this.renderCoffinCornersLayer(features),\n ];\n\n // Elevation visualization layers (wireframe for polygons, curtains for lines)\n // These render UNDER the main layer\n // Single classification pass replaces 3 separate .filter() calls per frame\n if (enableElevation) {\n const { classification, curtainFeatures } = this.getElevationData(\n features,\n this.props.applyBaseOpacity,\n );\n const { polygons, nonPolygons } = classification;\n layers.push(\n ...this.renderElevationVisualizationLayer(\n features,\n curtainFeatures,\n polygons,\n ),\n );\n layers.push(...this.renderElevationIndicatorLayer(features, nonPolygons));\n }\n\n // Main layer with fills and styled strokes\n // When elevation enabled, uses explicit 3D coordinates instead of extrusion\n // so PathStyleExtension works for hover/selection effects\n layers.push(this.renderMainLayer(features));\n\n // Labels on top of everything\n layers.push(...this.renderLabelsLayer());\n\n return layers;\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["features: Shape['feature'][]","normalizedLineColors: Rgba255Tuple[]","feature: Shape['feature']","pixelOffset: [number, number]","lineData: LineSegment[]","getFillColor","layers: GeoJsonLayer[]","layers: Layer[]"],"sources":["../../../../src/deckgl/shapes/display-shape-layer/index.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 { Broadcast } from '@accelint/bus';\nimport { CompositeLayer } from '@deck.gl/core';\nimport { GeoJsonLayer, LineLayer, TextLayer } from '@deck.gl/layers';\nimport { DEFAULT_TEXT_SIZE, DEFAULT_TEXT_STYLE } from '../../text-settings';\nimport { SHAPE_LAYER_IDS } from '../shared/constants';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport {\n isCircleShape,\n isLineGeometry,\n isPointType,\n isPolygonGeometry,\n} from '../shared/types';\nimport {\n getDashArray,\n getFillColor,\n getLineColor,\n} from '../shared/utils/style-utils';\nimport {\n BRIGHTNESS_FACTOR,\n DEFAULT_DISPLAY_PROPS,\n DISPLAY_EXTENSIONS,\n HIGHLIGHT_COLOR_TUPLE,\n MAP_INTERACTION,\n MATERIAL_SETTINGS,\n} from './constants';\nimport { createShapeLabelLayer } from './shape-label-layer';\nimport {\n applyOverlayOpacity,\n brightenColor,\n getHighlightLineWidth,\n getHoverLineWidth,\n getOverlayFillColor,\n} from './utils/display-style';\nimport {\n buildIndicatorLineData,\n classifyElevatedFeatures,\n createCurtainPolygonFeatures,\n flattenFeatureTo2D,\n getFeatureElevation,\n partitionCurtains,\n} from './utils/elevation';\nimport {\n getIconConfig,\n getIconLayerProps,\n getIconUpdateTriggers,\n} from './utils/icon-config';\nimport { getLabelPosition2d } from './utils/labels';\nimport { getRadiusLabelText } from './utils/radius-label';\nimport type { Rgba255Tuple } from '@accelint/predicates';\nimport type { Layer, PickingInfo } from '@deck.gl/core';\nimport type { Shape, ShapeId } from '../shared/types';\nimport type {\n CurtainFeature,\n DisplayShapeLayerProps,\n DisplayShapeLayerState,\n ElevatedFeatureClassification,\n ElevationCache,\n FeaturesCache,\n IndicatorCache,\n LineSegment,\n} from './types';\n\n/**\n * Typed event bus instance for shape events.\n * Provides type-safe event emission for shape interactions.\n */\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\n\n/**\n * DisplayShapeLayer - Read-only shapes visualization layer\n *\n * A composite deck.gl layer for displaying geographic shapes with interactive features.\n * Ideal for rendering shapes from external APIs or displaying read-only geographic data.\n *\n * ## Features\n * - **Multiple geometry types**: Point, LineString, Polygon, and Circle\n * - **Icon support**: Custom icons for Point geometries via icon atlases\n * - **Interactive selection**: Click handling with brightness overlay on polygon select, optional highlight outline for non-Point shapes (if showHighlight=true). Point geometries use coffin corner brackets instead.\n * - **Hover effects**: Polygon fills brighten via material lighting; outline width increases by 2px on hover\n * - **Customizable labels**: Flexible label positioning with per-shape or global options\n * - **Style properties**: Full control over colors, border/outline patterns, and opacity\n * - **Event bus integration**: Automatically emits shape events via @accelint/bus\n * - **Multi-map support**: Events include map instance ID for isolation\n *\n * ## Interaction Philosophy\n * Interactions never modify a shape's innate styling. Hover and selection are always\n * additive overlays rendered apart from the main layer using opacity-scaled fill colors\n * and material-based brightness — the base shape is never altered.\n *\n * ## Layer Structure\n * Renders sublayers in this order (bottom to top):\n * 1. **Highlight layer**: Selection outline for non-polygon, non-Point shapes (when showHighlight enabled)\n * 2. **Select layer**: Selection brightness overlay for polygon shapes\n * 3. **Hover layer**: Hover brightness overlay for polygon shapes\n * 4. **Elevation visualization**: Curtains (LineStrings) or wireframes (polygons) — elevation only\n * 5. **Elevation indicators**: Vertical strut lines for elevated non-polygon shapes — elevation only\n * 6. **Main GeoJsonLayer**: Shape geometries with styling and interaction; includes CoffinCornerExtension\n * which propagates to the icon/scatterplot sublayer for Point hover/select bracket feedback\n * 7. **Label layer**: Text labels (if showLabels enabled)\n *\n * ## Icon Atlas Constraint\n * When using icons for Point geometries, all shapes in a single layer must share the\n * same icon atlas. The layer uses the first atlas found across all features. If you\n * need icons from different atlases, use separate DisplayShapeLayer instances.\n *\n * ## Event Bus Integration\n * Automatically emits shape events that can be consumed anywhere in your app:\n * - `shapes:selected` - Emitted when a shape is clicked (includes mapId)\n * - `shapes:hovered` - Emitted when the hovered shape changes (deduplicated, includes mapId)\n *\n * For selection with auto-deselection, use the companion `useSelectShape` hook which handles\n * all the event wiring automatically. See the example below.\n *\n * @example Basic usage with useSelectShape hook (recommended)\n * ```tsx\n * import '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/fiber';\n * import { useSelectShape } from '@accelint/map-toolkit/deckgl/shapes';\n * import { uuid } from '@accelint/core';\n *\n * const MAP_ID = uuid();\n *\n * function MapWithShapes() {\n * const { selectedId } = useSelectShape(MAP_ID);\n *\n * return (\n * <BaseMap id={MAP_ID}>\n * <displayShapeLayer\n * id=\"my-shapes\"\n * mapId={MAP_ID}\n * data={shapes}\n * selectedShapeId={selectedId}\n * showLabels=\"always\"\n * pickable={true}\n * />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example With custom label positioning\n * ```tsx\n * <displayShapeLayer\n * id=\"my-shapes\"\n * data={shapes}\n * showLabels=\"always\"\n * labelOptions={{\n * // Position circle labels at the top\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom',\n * circleLabelOffset: [0, -10],\n * // Position line labels at the middle\n * lineStringLabelCoordinateAnchor: 'middle',\n * }}\n * />\n * ```\n */\nexport class DisplayShapeLayer extends CompositeLayer<DisplayShapeLayerProps> {\n // State is typed via DisplayShapeLayerState but deck.gl doesn't support generic state\n declare state: DisplayShapeLayerState;\n\n /** Cache for transformed features to avoid recreating objects on every render */\n private featuresCache: FeaturesCache | null = null;\n /** Cache for elevation classification and curtain features */\n private elevationCache: ElevationCache | null = null;\n /** Cache for elevation indicator line segments */\n private indicatorCache: IndicatorCache | null = null;\n /** Cached coffin corner color tuple — avoids new array reference per render when highlight hasn't changed. */\n private coffinCornerColorCache: {\n source: Rgba255Tuple;\n color: Rgba255Tuple;\n } | null = null;\n\n static override layerName = 'DisplayShapeLayer';\n\n static override defaultProps = {\n ...DEFAULT_DISPLAY_PROPS,\n };\n\n /**\n * Clean up state and caches when layer is destroyed\n */\n override finalizeState(): void {\n // Clear hover state to prevent stale references\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined, lastHoveredId: undefined });\n }\n // Clear caches\n this.featuresCache = null;\n this.elevationCache = null;\n this.indicatorCache = null;\n this.coffinCornerColorCache = null;\n }\n\n /**\n * Resolved highlight color — uses prop if provided, falls back to default.\n */\n private get resolvedHighlight(): Rgba255Tuple {\n return this.props.highlightColor ?? HIGHLIGHT_COLOR_TUPLE;\n }\n\n /**\n * Coffin corner bracket color derived from resolvedHighlight with forced full opacity.\n * Cached to avoid a new array reference per render triggering deck.gl prop-change detection.\n */\n private get coffinCornerColor(): Rgba255Tuple {\n const highlight = this.resolvedHighlight;\n if (this.coffinCornerColorCache?.source !== highlight) {\n this.coffinCornerColorCache = {\n source: highlight,\n color: [highlight[0], highlight[1], highlight[2], 255],\n };\n }\n return this.coffinCornerColorCache.color;\n }\n\n /**\n * Handle picking events from the main shapes layer\n */\n private handleMainLayerPick(info: PickingInfo, mode?: string): void {\n // Handle click events (deck.gl uses 'query' mode for clicks)\n if (mode === 'query') {\n this.handleShapeClick(info);\n }\n\n // Handle hover events (including when mode is undefined, which is hover)\n if (mode === 'hover' || !mode) {\n // Update hover state\n if (info.index !== undefined && info.index !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: info.index });\n }\n\n // Call hover callback\n this.handleShapeHover(info);\n }\n }\n\n /**\n * Handle picking events from curtain layers and map back to original LineString\n */\n private handleCurtainPick(\n info: PickingInfo,\n mode?: string,\n ): PickingInfo | undefined {\n if (!info.object) {\n return undefined;\n }\n\n const curtainShapeId = info.object.properties?.shapeId;\n if (!curtainShapeId) {\n return undefined;\n }\n\n const features = this.getFeaturesWithId();\n const featureIndex = this.featuresCache?.shapeIdToIndex.get(curtainShapeId);\n\n if (featureIndex === undefined) {\n return undefined;\n }\n\n // Create modified info with the original feature\n const modifiedInfo = {\n ...info,\n index: featureIndex,\n object: features[featureIndex],\n };\n\n // Handle click events\n if (mode === 'query') {\n this.handleShapeClick(modifiedInfo);\n }\n\n // Handle hover events\n if (mode === 'hover' || !mode) {\n if (featureIndex !== this.state?.hoverIndex) {\n this.setState({ hoverIndex: featureIndex });\n }\n this.handleShapeHover(modifiedInfo);\n }\n\n return modifiedInfo;\n }\n\n /**\n * Override getPickingInfo to handle events from sublayers\n * This is the correct pattern for CompositeLayer event handling\n */\n override getPickingInfo({\n info,\n mode,\n sourceLayer,\n }: {\n info: PickingInfo;\n mode?: string;\n sourceLayer?: Layer | null;\n }) {\n // Handle hover end (moving off all shapes to empty space)\n // This must be checked BEFORE layer-specific logic to ensure hover state clears properly\n if (\n (mode === 'hover' || !mode) &&\n (info.index === undefined || info.index < 0)\n ) {\n if (this.state?.hoverIndex !== undefined) {\n this.setState({ hoverIndex: undefined });\n }\n this.handleShapeHover(info);\n return info;\n }\n\n // Check if this picking event came from our main shapes layer\n if (sourceLayer?.id === `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`) {\n this.handleMainLayerPick(info, mode);\n return info;\n }\n\n // Check if this picking event came from a curtain layer (elevation visualization for LineStrings)\n const isCurtainLayer = sourceLayer?.id?.includes('-elevation-curtain');\n if (isCurtainLayer) {\n const curtainInfo = this.handleCurtainPick(info, mode);\n if (curtainInfo) {\n return curtainInfo;\n }\n }\n\n return info;\n }\n\n /**\n * Convert shapes to GeoJSON features with shapeId in properties.\n * Uses caching to avoid recreating objects on every render cycle.\n */\n private getFeaturesWithId(): Shape['feature'][] {\n const { data } = this.props;\n\n // Return cached features if data hasn't changed (identity check)\n if (this.featuresCache?.data === data) {\n return this.featuresCache.features;\n }\n\n // Transform features and build shapeId->index map in a single pass\n const features: Shape['feature'][] = [];\n const shapeIdToIndex = new Map<ShapeId, number>();\n const normalizedLineColors: Rgba255Tuple[] = [];\n\n for (const [i, shape] of data.entries()) {\n let feature: Shape['feature'] = {\n ...shape.feature,\n properties: {\n ...shape.feature.properties,\n shapeId: shape.id,\n },\n };\n\n // For polygon geometries with elevation: strip Z coordinates to prevent\n // deck.gl double-counting (SolidPolygonLayer adds coordinate Z + getElevation).\n // The feature's maxElevation property is the source of truth for getFeatureElevation.\n if (\n isPolygonGeometry(feature.geometry) &&\n getFeatureElevation(feature) > 0\n ) {\n feature = flattenFeatureTo2D(feature);\n }\n\n features.push(feature);\n shapeIdToIndex.set(shape.id, i);\n normalizedLineColors.push(getLineColor(shape.feature));\n }\n\n this.featuresCache = {\n data,\n features,\n shapeIdToIndex,\n normalizedLineColors,\n };\n return features;\n }\n\n /**\n * Look up a shape by ID from the data prop.\n * Used by event handlers to get full shape without storing in feature properties.\n */\n private getShapeById(shapeId: ShapeId): Shape | undefined {\n const index = this.featuresCache?.shapeIdToIndex.get(shapeId);\n return index !== undefined ? this.props.data[index] : undefined;\n }\n\n /**\n * Handle shape click — emits `shapes:selected` via bus and calls `onShapeClick` callback.\n * @param info - deck.gl picking info from the clicked sublayer.\n */\n private handleShapeClick = (info: PickingInfo): void => {\n const { onShapeClick, mapId } = this.props;\n\n if (!info.object) {\n return;\n }\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object.properties?.shapeId as ShapeId | undefined;\n if (!shapeId) {\n return;\n }\n\n const shape = this.getShapeById(shapeId);\n if (!shape) {\n return;\n }\n\n // Emit shape selected event via bus (include mapId for multi-map isolation)\n shapeBus.emit(ShapeEvents.selected, { shapeId: shape.id, mapId });\n\n // Call callback if provided\n if (onShapeClick) {\n onShapeClick(shape);\n }\n };\n\n /**\n * Handle shape hover — emits `shapes:hovered` via bus (deduplicated by shapeId) and calls `onShapeHover` callback.\n * @param info - deck.gl picking info from the hovered sublayer.\n */\n private handleShapeHover = (info: PickingInfo): void => {\n const { onShapeHover, mapId } = this.props;\n\n // Look up shape from data prop using shapeId stored in feature properties\n const shapeId = info.object?.properties?.shapeId as ShapeId | undefined;\n const shape = shapeId ? this.getShapeById(shapeId) : undefined;\n\n // Dedupe hover events - only emit if hovered shape changed\n if (shapeId !== this.state?.lastHoveredId) {\n this.setState({ lastHoveredId: shapeId });\n\n // Emit shape hovered event via bus (include mapId for multi-map isolation)\n // Cast to null at the bus boundary — ShapeHoveredEvent uses null for hover-end\n shapeBus.emit(ShapeEvents.hovered, {\n shapeId: shapeId ?? null,\n mapId,\n });\n }\n\n // Always call callback if provided (for local state updates)\n if (onShapeHover) {\n onShapeHover(shape);\n }\n };\n\n /**\n * Get or compute elevation-derived data (feature classification + curtain features).\n * Cached on features identity and applyBaseOpacity to avoid per-frame recomputation.\n */\n private getElevationData(\n features: Shape['feature'][],\n applyBaseOpacity: boolean | undefined,\n ): {\n classification: ElevatedFeatureClassification;\n curtainFeatures: CurtainFeature[];\n } {\n if (\n this.elevationCache !== null &&\n this.elevationCache.features === features &&\n this.elevationCache.applyBaseOpacity === applyBaseOpacity\n ) {\n return this.elevationCache;\n }\n\n const classification = classifyElevatedFeatures(\n features,\n getFeatureElevation,\n );\n const curtainFeatures = createCurtainPolygonFeatures(\n classification.lines,\n applyBaseOpacity,\n );\n\n this.elevationCache = {\n features,\n applyBaseOpacity,\n classification,\n curtainFeatures,\n };\n return { classification, curtainFeatures };\n }\n\n /**\n * Render highlight sublayer (underneath main layer).\n * Point geometries skip this layer — they use coffin corner brackets\n * via CoffinCornerExtension on the main layer's icon/scatterplot sublayer instead.\n */\n private renderHighlightLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { selectedShapeId, showHighlight } = this.props;\n\n // Skip if no selection or highlight is disabled\n if (!selectedShapeId || showHighlight === false) {\n return [];\n }\n\n const featureIndex =\n this.featuresCache?.shapeIdToIndex.get(selectedShapeId);\n\n const selectedFeature =\n featureIndex !== undefined ? features[featureIndex] : undefined;\n\n if (!selectedFeature) {\n return [];\n }\n\n // Skip highlight layer for Point geometries — they use coffin corner brackets instead.\n if (isPointType(selectedFeature.geometry)) {\n return [];\n }\n\n // Strip Z from LineString coordinates so the highlight outline renders at\n // ground level rather than following the elevated path\n const highlightFeature = flattenFeatureTo2D(selectedFeature);\n const lineColor = this.resolvedHighlight;\n\n // Render 2D highlight layer (outline only)\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,\n data: [highlightFeature],\n\n // Styling - outline only for 2D shapes\n filled: false,\n stroked: true,\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n getLineColor: lineColor,\n getLineWidth: getHighlightLineWidth,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n getLineColor: [this.props.highlightColor],\n getLineWidth: [selectedShapeId, features],\n },\n }),\n ];\n }\n\n /**\n * Render selection overlay layer for polygon shapes.\n * Mirrors renderHoverLayer but triggers on selectedShapeId instead of hover.\n * When a shape is both selected and hovered, both layers stack for a brighter combined effect.\n */\n private renderSelectLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { selectedShapeId, enableElevation } = this.props;\n\n if (!selectedShapeId) {\n return [];\n }\n\n const featureIndex =\n this.featuresCache?.shapeIdToIndex.get(selectedShapeId);\n\n const selectedFeature =\n featureIndex !== undefined ? features[featureIndex] : undefined;\n\n if (!selectedFeature) {\n return [];\n }\n\n // Only render for polygons — non-polygon shapes have no fill to brighten\n if (!isPolygonGeometry(selectedFeature.geometry)) {\n return [];\n }\n\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_SELECTION}`,\n data: [selectedFeature],\n\n filled: true,\n stroked: false,\n getFillColor: getOverlayFillColor,\n\n // Material brightness for selection; extrusion only when elevation enabled\n extruded: enableElevation,\n getElevation: getFeatureElevation,\n material: MATERIAL_SETTINGS.HOVER_OR_SELECT,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n data: [features, selectedShapeId],\n getFillColor: [features],\n getElevation: [features],\n },\n }),\n ];\n }\n\n /**\n * Render hover layer for all polygon shapes (2D and 3D).\n * Overlays the shape's base fill with brighter material lighting.\n * Stacks with other interaction layers (e.g. selection highlight underneath).\n */\n private renderHoverLayer(features: Shape['feature'][]): GeoJsonLayer[] {\n const { enableElevation, selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Only render if something is hovered\n if (hoverIndex === undefined) {\n return [];\n }\n\n const hoveredFeature = features[hoverIndex];\n if (!hoveredFeature) {\n return [];\n }\n\n // Only render for polygons\n if (!isPolygonGeometry(hoveredFeature.geometry)) {\n return [];\n }\n\n const isAlsoSelected =\n hoveredFeature.properties?.shapeId === selectedShapeId;\n const material = isAlsoSelected\n ? MATERIAL_SETTINGS.HOVER_AND_SELECT\n : MATERIAL_SETTINGS.HOVER_OR_SELECT;\n\n return [\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-hover`,\n data: [hoveredFeature],\n\n // Styling\n filled: true,\n stroked: false, // Main layer handles strokes; this layer is fill-only\n getFillColor: getOverlayFillColor,\n\n // Material brightness scales with interaction state; extrusion only when elevation enabled\n extruded: enableElevation,\n getElevation: getFeatureElevation,\n material,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n data: [features, hoverIndex],\n getFillColor: [features],\n getElevation: [features],\n material: [selectedShapeId, hoverIndex],\n },\n }),\n ];\n }\n\n /**\n * Render main shapes layer\n */\n private renderMainLayer(features: Shape['feature'][]): GeoJsonLayer {\n const { pickable, applyBaseOpacity, selectedShapeId } = this.props;\n\n // Single-pass icon config extraction (O(1) best case with early return)\n const {\n hasIcons,\n atlas: iconAtlas,\n mapping: iconMapping,\n } = getIconConfig(features);\n\n // Resolve hovered entity ID for coffin corner extension on point sublayers.\n // Geometry check kept to avoid unnecessary attribute invalidation on non-point hovers.\n const hoveredFeature =\n this.state?.hoverIndex !== undefined\n ? features[this.state.hoverIndex]\n : undefined;\n const hoveredEntityId =\n hoveredFeature?.geometry.type === 'Point'\n ? (hoveredFeature.properties?.shapeId as ShapeId)\n : undefined;\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,\n data: features,\n\n // Styling\n filled: true,\n stroked: true,\n getFillColor: (d: Shape['feature']) => getFillColor(d, applyBaseOpacity),\n getLineColor: (d: Shape['feature'], info) => {\n // Read pre-normalized color from cache — avoids normalizeColor allocation per feature per trigger\n const baseColor =\n this.featuresCache?.normalizedLineColors[info?.index ?? -1] ??\n getLineColor(d);\n const isHovered = info?.index === this.state?.hoverIndex;\n const isSelected = d.properties?.shapeId === selectedShapeId;\n if (isHovered && isSelected) {\n return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_AND_SELECT);\n }\n if (isHovered || isSelected) {\n return brightenColor(baseColor, BRIGHTNESS_FACTOR.HOVER_OR_SELECT);\n }\n return baseColor;\n },\n getLineWidth: (d, info) => {\n // Skip hover line width for elevated LineStrings - curtain handles it\n if (\n this.props.enableElevation &&\n isLineGeometry(d.geometry) &&\n getFeatureElevation(d) > 0\n ) {\n return d.properties?.styleProperties?.lineWidth ?? 2;\n }\n\n const isHovered = info?.index === this.state?.hoverIndex;\n return getHoverLineWidth(d, isHovered);\n },\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n lineWidthMaxPixels: 20,\n\n // Polygon extrusion (when elevation enabled)\n // NOTE: deck.gl has a limitation where stroked polygon outlines only render when !extruded && stroked\n // This means hover/selection styling (dash arrays, line width) won't work for extruded polygons\n // See: https://github.com/visgl/deck.gl/blob/master/modules/layers/src/geojson-layer/geojson-layer.ts#L467-508\n // Tested fix: Changing condition to `!wireframe && stroked` in deck.gl source did NOT resolve the issue\n // Solution: Separate hover/select layers with material-based lighting for extruded polygons\n extruded: this.props.enableElevation ?? false,\n getElevation: getFeatureElevation,\n ...(this.props.enableElevation\n ? {\n material: MATERIAL_SETTINGS.NORMAL,\n }\n : {}),\n\n // Points - use icons if any feature has icon config, otherwise circles\n pointType: hasIcons ? 'icon' : 'circle',\n getPointRadius: (d) => {\n const iconSize = d.properties?.styleProperties?.icon?.size;\n return iconSize ?? 2;\n },\n pointRadiusUnits: 'pixels',\n\n // Icon configuration (only used if pointType includes 'icon')\n ...getIconLayerProps(hasIcons, iconAtlas, iconMapping),\n\n // Coffin corner extension props — the extension is a no-op on non-icon/scatterplot sublayers,\n // so it's safe to include on the GeoJsonLayer directly. deck.gl's extension propagation\n // forwards these props to all sublayers via LayerExtension.getSubLayerProps().\n selectedEntityId: selectedShapeId,\n hoveredEntityId,\n getEntityId: (d: Shape['feature']) => d.properties?.shapeId as ShapeId,\n selectedCoffinCornerColor: this.coffinCornerColor,\n\n // Extensions: PathStyleExtension for dash patterns + CoffinCornerExtension for bracket indicators.\n // CoffinCornerExtension returns null from getShaders() on unsupported sublayer types\n // (PathLayer, SolidPolygonLayer), so shader injection only occurs on IconLayer/ScatterplotLayer.\n extensions: DISPLAY_EXTENSIONS,\n getDashArray,\n\n // Behavior\n pickable,\n autoHighlight: false, // We handle highlighting manually\n // Note: onClick and onHover are handled via getPickingInfo() override\n\n // Depth testing - enable for 3D shapes to prevent rendering through globe\n ...(this.props.enableElevation\n ? {\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n }\n : {}),\n\n // Update triggers\n updateTriggers: {\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features, this.state?.hoverIndex, selectedShapeId],\n getLineWidth: [features, this.state?.hoverIndex],\n getDashArray: [features],\n getPointRadius: [features],\n ...getIconUpdateTriggers(hasIcons, features),\n },\n });\n }\n\n /**\n * Render labels layer\n * Supports three modes:\n * - 'always': Show labels for all shapes\n * - 'hover': Show label only for the currently hovered shape\n * - 'never': No labels\n */\n private renderLabelsLayer(): ReturnType<typeof createShapeLabelLayer>[] {\n const { showLabels, data, labelOptions } = this.props;\n\n // No labels if disabled\n if (showLabels === 'never') {\n return [];\n }\n\n // Determine which shapes to show labels for\n let labelData = data;\n if (showLabels === 'hover') {\n const hoverIndex = this.state?.hoverIndex;\n if (hoverIndex === undefined) {\n return []; // No shape hovered, no label to show\n }\n const hoveredShape = data[hoverIndex];\n labelData = hoveredShape ? [hoveredShape] : [];\n }\n\n if (labelData.length === 0) {\n return [];\n }\n\n return [\n createShapeLabelLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,\n data: labelData,\n labelOptions,\n }),\n ];\n }\n\n /**\n * Render radius label layer for hovered circle shapes.\n * Shows the circle's radius value converted to the configured display unit.\n *\n * Positioning relative to the shape's label:\n * - When showLabels is 'always' or 'hover': appears below the label\n * - When showLabels is 'never': appears in the label's position\n */\n private renderRadiusLabelLayer(): TextLayer[] {\n const { data, unit, showLabels, labelOptions } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n const hoveredShape =\n hoverIndex !== undefined ? data[hoverIndex] : undefined;\n const radiusText =\n hoveredShape && isCircleShape(hoveredShape)\n ? getRadiusLabelText(hoveredShape, unit)\n : undefined;\n\n // Use the same position the label would occupy\n const labelPosition =\n radiusText && hoveredShape\n ? getLabelPosition2d(hoveredShape, labelOptions)\n : undefined;\n\n if (!labelPosition) {\n return [];\n }\n\n // When label is visible, offset below it; otherwise take its place\n const labelVisible = showLabels !== 'never';\n const pixelOffset: [number, number] = labelVisible\n ? [\n labelPosition.pixelOffset[0],\n labelPosition.pixelOffset[1] + DEFAULT_TEXT_SIZE + 2,\n ]\n : labelPosition.pixelOffset;\n\n return [\n new TextLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-radius-label`,\n data: [{ text: radiusText, position: labelPosition.coordinates }],\n getText: (d) => d.text,\n getPosition: (d) => d.position as [number, number],\n getPixelOffset: pixelOffset,\n getTextAnchor: labelPosition.textAnchor,\n getAlignmentBaseline: labelPosition.alignmentBaseline,\n ...DEFAULT_TEXT_STYLE,\n getAngle: 0,\n background: false,\n fontFamily: 'Roboto MonoVariable, monospace',\n pickable: false,\n updateTriggers: {\n getText: [hoverIndex, unit],\n getPosition: [hoverIndex, labelOptions],\n getPixelOffset: [hoverIndex, labelOptions, showLabels],\n getTextAnchor: [hoverIndex, labelOptions],\n getAlignmentBaseline: [hoverIndex, labelOptions],\n },\n }),\n ];\n }\n\n /**\n * Render vertical elevation indicator lines for non-polygon shapes.\n * Creates vertical \"strut\" lines from ground level to elevated features.\n * For LineStrings, creates a \"curtain\" effect with vertical lines at each coordinate.\n * Polygons use wireframe extrusion instead.\n */\n private renderElevationIndicatorLayer(\n features: Shape['feature'][],\n elevatedNonPolygons: Shape['feature'][],\n ): LineLayer[] {\n if (elevatedNonPolygons.length === 0) {\n return [];\n }\n\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n // Return stable lineData when geometry and interaction state haven't changed.\n // A new array reference would force deck.gl to rebuild GPU buffers every frame.\n const cache = this.indicatorCache;\n let lineData: LineSegment[];\n\n if (\n cache !== null &&\n cache.features === features &&\n cache.selectedShapeId === selectedShapeId &&\n cache.hoverIndex === hoverIndex\n ) {\n lineData = cache.lineData;\n } else {\n lineData = buildIndicatorLineData(\n elevatedNonPolygons,\n features,\n selectedShapeId,\n hoverIndex,\n );\n this.indicatorCache = { features, selectedShapeId, hoverIndex, lineData };\n }\n\n if (lineData.length === 0) {\n return [];\n }\n\n return [\n new LineLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-indicators`,\n data: lineData,\n getSourcePosition: (d: LineSegment) => d.source,\n getTargetPosition: (d: LineSegment) => d.target,\n getColor: (d: LineSegment) => d.color,\n getWidth: 2,\n widthUnits: 'pixels',\n pickable: false,\n updateTriggers: {\n data: [features, selectedShapeId, hoverIndex],\n getColor: [features, selectedShapeId, hoverIndex],\n },\n }),\n ];\n }\n\n /**\n * Create a single curtain GeoJsonLayer with shared configuration.\n */\n private createCurtainGeoJsonLayer(\n idSuffix: string,\n data: CurtainFeature[],\n getFillColor: (d: CurtainFeature) => Rgba255Tuple,\n dataTriggers: unknown[],\n fillColorTriggers: unknown[],\n ): GeoJsonLayer {\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-${idSuffix}`,\n data: data,\n filled: true,\n stroked: false,\n // biome-ignore lint/style/useNamingConvention: deck.gl uses _full3d naming\n _full3d: true,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accessor type incompatible with CurtainFeature\n getFillColor: getFillColor as any,\n pickable: this.props.pickable ?? true,\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n updateTriggers: {\n data: dataTriggers,\n getFillColor: fillColorTriggers,\n },\n });\n }\n\n /**\n * Render curtain layers for elevated LineStrings.\n * Creates three separate layers for main, hovered, and selected states.\n */\n private renderCurtainLayers(\n features: Shape['feature'][],\n allCurtainFeatures: CurtainFeature[],\n ): GeoJsonLayer[] {\n const layers: GeoJsonLayer[] = [];\n const { selectedShapeId } = this.props;\n const hoverIndex = this.state?.hoverIndex;\n\n const hoveredShapeId =\n hoverIndex !== undefined && features[hoverIndex]\n ? features[hoverIndex].properties?.shapeId\n : undefined;\n\n const { main, hovered, selected } = partitionCurtains(\n allCurtainFeatures,\n hoveredShapeId,\n selectedShapeId,\n );\n\n const isSelectedHovered = selectedShapeId === hoveredShapeId;\n const dataTriggers = [features, hoveredShapeId, selectedShapeId];\n\n if (main.length > 0) {\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain',\n main,\n (d) => d.properties.fillColor,\n dataTriggers,\n [features, this.props.applyBaseOpacity],\n ),\n );\n }\n\n if (hovered.length > 0) {\n // All curtain segments in a partition share the same lineColor (same hovered shape).\n // Precompute once to avoid N allocations in the getFillColor accessor.\n const hoveredColor = applyOverlayOpacity(\n brightenColor(\n (hovered[0] as CurtainFeature).properties.lineColor,\n BRIGHTNESS_FACTOR.HOVER_OR_SELECT,\n ),\n );\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain-hover',\n hovered,\n () => hoveredColor,\n dataTriggers,\n [features],\n ),\n );\n }\n\n if (selected.length > 0) {\n // Selected + hovered stacks to HOVER_AND_SELECT; selected only is HOVER_OR_SELECT\n const factor = isSelectedHovered\n ? BRIGHTNESS_FACTOR.HOVER_AND_SELECT\n : BRIGHTNESS_FACTOR.HOVER_OR_SELECT;\n const selectedColor = applyOverlayOpacity(\n brightenColor(\n (selected[0] as CurtainFeature).properties.lineColor,\n factor,\n ),\n );\n layers.push(\n this.createCurtainGeoJsonLayer(\n 'elevation-curtain-selected',\n selected,\n () => selectedColor,\n dataTriggers,\n [features, isSelectedHovered],\n ),\n );\n }\n\n return layers;\n }\n\n /**\n * Render elevation visualization layers (curtains for lines, wireframes for polygons).\n */\n private renderElevationVisualizationLayer(\n features: Shape['feature'][],\n allCurtainFeatures: CurtainFeature[],\n elevatedPolygons: Shape['feature'][],\n ): GeoJsonLayer[] {\n const layers: GeoJsonLayer[] = [];\n\n // LineString curtains (filled vertical surfaces) — pre-computed by getElevationData()\n if (allCurtainFeatures.length > 0) {\n layers.push(...this.renderCurtainLayers(features, allCurtainFeatures));\n }\n\n // Polygon wireframes (extruded 3D boxes with edges)\n if (elevatedPolygons.length > 0) {\n const { applyBaseOpacity } = this.props;\n layers.push(\n new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}-elevation-wireframe`,\n data: elevatedPolygons,\n filled: false,\n stroked: false,\n extruded: true,\n wireframe: true,\n getElevation: getFeatureElevation,\n getFillColor: (d: Shape['feature']) =>\n getFillColor(d, applyBaseOpacity),\n getLineColor,\n pickable: false,\n parameters: {\n depthTest: true,\n depthCompare: 'less-equal',\n },\n updateTriggers: {\n getElevation: [features],\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features],\n },\n }),\n );\n }\n\n return layers;\n }\n\n /**\n * Render all sublayers.\n * @returns Ordered array of sublayers (highlight, select, hover, elevation, main, labels) from bottom to top.\n */\n renderLayers(): Layer[] {\n // Compute features once per render cycle for performance\n const features = this.getFeaturesWithId();\n const enableElevation = this.props.enableElevation ?? false;\n\n const layers: Layer[] = [\n ...this.renderHighlightLayer(features),\n ...this.renderSelectLayer(features),\n ...this.renderHoverLayer(features),\n ];\n\n // Elevation visualization layers (wireframe for polygons, curtains for lines)\n // These render UNDER the main layer\n // Single classification pass replaces 3 separate .filter() calls per frame\n if (enableElevation) {\n const { classification, curtainFeatures } = this.getElevationData(\n features,\n this.props.applyBaseOpacity,\n );\n const { polygons, nonPolygons } = classification;\n layers.push(\n ...this.renderElevationVisualizationLayer(\n features,\n curtainFeatures,\n polygons,\n ),\n );\n layers.push(...this.renderElevationIndicatorLayer(features, nonPolygons));\n }\n\n // Main layer with fills and styled strokes\n // When elevation enabled, uses explicit 3D coordinates instead of extrusion\n // so PathStyleExtension works for hover/selection effects\n layers.push(this.renderMainLayer(features));\n\n // Labels on top of everything\n layers.push(...this.renderLabelsLayer());\n\n // Radius label on hover (above name labels)\n layers.push(...this.renderRadiusLabelLayer());\n\n return layers;\n }\n}\n"],"mappings":"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"}
|
|
@@ -14,6 +14,7 @@ import { Shape, ShapeId } from "../shared/types.js";
|
|
|
14
14
|
import { LabelPositionOptions } from "./utils/labels.js";
|
|
15
15
|
import { UniqueId } from "@accelint/core";
|
|
16
16
|
import { CompositeLayerProps } from "@deck.gl/core";
|
|
17
|
+
import { DistanceUnitSymbol } from "@accelint/constants/units";
|
|
17
18
|
import { Rgba255Tuple } from "@accelint/predicates";
|
|
18
19
|
|
|
19
20
|
//#region src/deckgl/shapes/display-shape-layer/types.d.ts
|
|
@@ -209,6 +210,13 @@ type DisplayShapeLayerProps = CompositeLayerProps & {
|
|
|
209
210
|
* ```
|
|
210
211
|
*/
|
|
211
212
|
enableElevation?: boolean;
|
|
213
|
+
/**
|
|
214
|
+
* Distance unit symbol for displaying measurements (e.g., radius on hover).
|
|
215
|
+
* If the shape's stored radius uses a different unit, the value is converted.
|
|
216
|
+
* Matches the `unit` prop on DrawShapeLayer and EditShapeLayer.
|
|
217
|
+
* @default 'NM'
|
|
218
|
+
*/
|
|
219
|
+
unit?: DistanceUnitSymbol;
|
|
212
220
|
};
|
|
213
221
|
//#endregion
|
|
214
222
|
export { CurtainFeature, DisplayShapeLayerProps, DisplayShapeLayerState, ElevatedFeatureClassification, ElevationCache, FeaturesCache, IndicatorCache, LineSegment, ShowLabelsMode };
|
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
16
|
import { getLineColor } from "../../shared/utils/style-utils.js";
|
|
17
|
-
import {
|
|
17
|
+
import { MAP_INTERACTION } from "../constants.js";
|
|
18
18
|
|
|
19
19
|
//#region src/deckgl/shapes/display-shape-layer/utils/icon-config.ts
|
|
20
20
|
/**
|
|
@@ -68,7 +68,7 @@ function getIconLayerProps(hasIcons, iconAtlas, iconMapping) {
|
|
|
68
68
|
getIconPixelOffset: (d) => {
|
|
69
69
|
return [-1, -(d.properties?.styleProperties?.icon?.size ?? MAP_INTERACTION.ICON_SIZE) / 2];
|
|
70
70
|
},
|
|
71
|
-
iconBillboard:
|
|
71
|
+
iconBillboard: true
|
|
72
72
|
};
|
|
73
73
|
if (iconAtlas) result.iconAtlas = iconAtlas;
|
|
74
74
|
if (iconMapping) result.iconMapping = iconMapping;
|
|
@@ -100,52 +100,7 @@ function getIconUpdateTriggers(hasIcons, features) {
|
|
|
100
100
|
getIconPixelOffset: [features]
|
|
101
101
|
};
|
|
102
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
103
|
|
|
149
104
|
//#endregion
|
|
150
|
-
export {
|
|
105
|
+
export { getIconConfig, getIconLayerProps, getIconUpdateTriggers };
|
|
151
106
|
//# sourceMappingURL=icon-config.js.map
|
|
@@ -1 +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 {
|
|
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 { 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: true,\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"],"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"}
|
|
@@ -0,0 +1,53 @@
|
|
|
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
|
+
import { isCircleShape } from "../../shared/types.js";
|
|
15
|
+
import { formatDistance } from "../../shared/constants.js";
|
|
16
|
+
import { convertLength } from "@turf/turf";
|
|
17
|
+
import { DISTANCE_UNIT_BY_SYMBOL, DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
|
|
18
|
+
|
|
19
|
+
//#region src/deckgl/shapes/display-shape-layer/utils/radius-label.ts
|
|
20
|
+
/** Default distance unit symbol for radius display when no unit is specified. */
|
|
21
|
+
const DEFAULT_RADIUS_DISPLAY_SYMBOL = DISTANCE_UNIT_SYMBOLS.nauticalmiles;
|
|
22
|
+
/**
|
|
23
|
+
* Get the formatted radius label text for a circle shape.
|
|
24
|
+
* Returns an empty string for non-circle shapes.
|
|
25
|
+
*
|
|
26
|
+
* Uses the same `formatDistance` formatter as draw/edit tooltips for consistency.
|
|
27
|
+
* Unlike `formatCircleTooltip` (which shows both radius and area during draw/edit),
|
|
28
|
+
* this returns only the radius line for hover display.
|
|
29
|
+
*
|
|
30
|
+
* @param shape - The shape to get radius text for
|
|
31
|
+
* @param unitSymbol - Display unit symbol (e.g., 'km', 'NM'). Defaults to 'NM'.
|
|
32
|
+
* @returns Formatted radius string (e.g., "r: 250.00 NM") or empty string for non-circle shapes
|
|
33
|
+
*
|
|
34
|
+
* @example
|
|
35
|
+
* ```typescript
|
|
36
|
+
* const label = getRadiusLabelText(circleShape, 'km');
|
|
37
|
+
* // 'r: 250.00 km'
|
|
38
|
+
*
|
|
39
|
+
* const empty = getRadiusLabelText(polygonShape);
|
|
40
|
+
* // ''
|
|
41
|
+
* ```
|
|
42
|
+
*/
|
|
43
|
+
function getRadiusLabelText(shape, unitSymbol) {
|
|
44
|
+
if (!isCircleShape(shape)) return "";
|
|
45
|
+
const { value, units } = shape.feature.properties.circleProperties.radius;
|
|
46
|
+
const displaySymbol = unitSymbol ?? DEFAULT_RADIUS_DISPLAY_SYMBOL;
|
|
47
|
+
const targetUnit = DISTANCE_UNIT_BY_SYMBOL[displaySymbol];
|
|
48
|
+
return `r: ${formatDistance(units === targetUnit ? value : convertLength(value, units, targetUnit))} ${displaySymbol}`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
//#endregion
|
|
52
|
+
export { getRadiusLabelText };
|
|
53
|
+
//# sourceMappingURL=radius-label.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"radius-label.js","names":["DEFAULT_RADIUS_DISPLAY_SYMBOL: DistanceUnitSymbol"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/radius-label.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\nimport {\n DISTANCE_UNIT_BY_SYMBOL,\n DISTANCE_UNIT_SYMBOLS,\n} from '@accelint/constants/units';\nimport { convertLength } from '@turf/turf';\nimport { formatDistance } from '../../shared/constants';\nimport { isCircleShape } from '../../shared/types';\nimport type { DistanceUnitSymbol } from '@accelint/constants/units';\nimport type { Shape } from '../../shared/types';\n\n/** Default distance unit symbol for radius display when no unit is specified. */\nconst DEFAULT_RADIUS_DISPLAY_SYMBOL: DistanceUnitSymbol =\n DISTANCE_UNIT_SYMBOLS.nauticalmiles;\n\n/**\n * Get the formatted radius label text for a circle shape.\n * Returns an empty string for non-circle shapes.\n *\n * Uses the same `formatDistance` formatter as draw/edit tooltips for consistency.\n * Unlike `formatCircleTooltip` (which shows both radius and area during draw/edit),\n * this returns only the radius line for hover display.\n *\n * @param shape - The shape to get radius text for\n * @param unitSymbol - Display unit symbol (e.g., 'km', 'NM'). Defaults to 'NM'.\n * @returns Formatted radius string (e.g., \"r: 250.00 NM\") or empty string for non-circle shapes\n *\n * @example\n * ```typescript\n * const label = getRadiusLabelText(circleShape, 'km');\n * // 'r: 250.00 km'\n *\n * const empty = getRadiusLabelText(polygonShape);\n * // ''\n * ```\n */\nexport function getRadiusLabelText(\n shape: Shape,\n unitSymbol?: DistanceUnitSymbol,\n): string {\n if (!isCircleShape(shape)) {\n return '';\n }\n\n const { value, units } = shape.feature.properties.circleProperties.radius;\n const displaySymbol = unitSymbol ?? DEFAULT_RADIUS_DISPLAY_SYMBOL;\n const targetUnit = DISTANCE_UNIT_BY_SYMBOL[displaySymbol];\n\n const convertedValue =\n units === targetUnit ? value : convertLength(value, units, targetUnit);\n\n return `r: ${formatDistance(convertedValue)} ${displaySymbol}`;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAuBA,MAAMA,gCACJ,sBAAsB;;;;;;;;;;;;;;;;;;;;;;AAuBxB,SAAgB,mBACd,OACA,YACQ;AACR,KAAI,CAAC,cAAc,MAAM,CACvB,QAAO;CAGT,MAAM,EAAE,OAAO,UAAU,MAAM,QAAQ,WAAW,iBAAiB;CACnE,MAAM,gBAAgB,cAAc;CACpC,MAAM,aAAa,wBAAwB;AAK3C,QAAO,MAAM,eAFX,UAAU,aAAa,QAAQ,cAAc,OAAO,OAAO,WAAW,CAE7B,CAAC,GAAG"}
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { DrawShapeLayerProps } from "./types.js";
|
|
14
|
+
import "./fiber.js";
|
|
14
15
|
import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
15
16
|
|
|
16
17
|
//#region src/deckgl/shapes/draw-shape-layer/index.d.ts
|
|
@@ -28,11 +29,14 @@ import * as react_jsx_runtime2 from "react/jsx-runtime";
|
|
|
28
29
|
* - Protected drawing mode (rejects mode change requests while drawing)
|
|
29
30
|
* - Distance/area tooltips during drawing
|
|
30
31
|
*
|
|
32
|
+
* ## Fiber Registration
|
|
33
|
+
* Unlike `DisplayShapeLayer` (a deck.gl layer class), `DrawShapeLayer` is a React
|
|
34
|
+
* component that handles its own fiber registration internally. You do **not** need to
|
|
35
|
+
* import `draw-shape-layer/fiber` — but you **do** still need to import
|
|
36
|
+
* `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.
|
|
37
|
+
*
|
|
31
38
|
* @example
|
|
32
39
|
* ```tsx
|
|
33
|
-
* // Import the fiber registration for JSX support
|
|
34
|
-
* import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';
|
|
35
|
-
*
|
|
36
40
|
* function Map({ mapId }) {
|
|
37
41
|
* return (
|
|
38
42
|
* <BaseMap id={mapId}>
|
|
@@ -20,6 +20,7 @@ import { MapContext } from "../../base-map/provider.js";
|
|
|
20
20
|
import { useShiftZoomDisable } from "../shared/hooks/use-shift-zoom-disable.js";
|
|
21
21
|
import { getDefaultEditableLayerProps } from "../shared/utils/layer-config.js";
|
|
22
22
|
import { getModeInstance } from "./modes/index.js";
|
|
23
|
+
import "./fiber.js";
|
|
23
24
|
import { useContext } from "react";
|
|
24
25
|
import { jsx } from "react/jsx-runtime";
|
|
25
26
|
|
|
@@ -37,11 +38,14 @@ import { jsx } from "react/jsx-runtime";
|
|
|
37
38
|
* - Protected drawing mode (rejects mode change requests while drawing)
|
|
38
39
|
* - Distance/area tooltips during drawing
|
|
39
40
|
*
|
|
41
|
+
* ## Fiber Registration
|
|
42
|
+
* Unlike `DisplayShapeLayer` (a deck.gl layer class), `DrawShapeLayer` is a React
|
|
43
|
+
* component that handles its own fiber registration internally. You do **not** need to
|
|
44
|
+
* import `draw-shape-layer/fiber` — but you **do** still need to import
|
|
45
|
+
* `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.
|
|
46
|
+
*
|
|
40
47
|
* @example
|
|
41
48
|
* ```tsx
|
|
42
|
-
* // Import the fiber registration for JSX support
|
|
43
|
-
* import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';
|
|
44
|
-
*
|
|
45
49
|
* function Map({ mapId }) {
|
|
46
50
|
* return (
|
|
47
51
|
* <BaseMap id={mapId}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/index.tsx"],"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 { useContext } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport {\n DEFAULT_TENTATIVE_COLORS,\n EMPTY_FEATURE_COLLECTION,\n} from '../shared/constants';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { DRAW_SHAPE_LAYER_ID } from './constants';\nimport { getModeInstance } from './modes';\nimport {\n cancelDrawingFromLayer,\n completeDrawingFromLayer,\n drawStore,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { DrawShapeLayerProps } from './types';\n\n/**\n * DrawShapeLayer - A React component for drawing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively drawing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the drawing store for state management\n * - Protected drawing mode (rejects mode change requests while drawing)\n * - Distance/area tooltips during drawing\n *\n *
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/index.tsx"],"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 { useContext } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport {\n DEFAULT_TENTATIVE_COLORS,\n EMPTY_FEATURE_COLLECTION,\n} from '../shared/constants';\nimport { useShiftZoomDisable } from '../shared/hooks/use-shift-zoom-disable';\nimport { getDefaultEditableLayerProps } from '../shared/utils/layer-config';\nimport { DRAW_SHAPE_LAYER_ID } from './constants';\nimport { getModeInstance } from './modes';\nimport {\n cancelDrawingFromLayer,\n completeDrawingFromLayer,\n drawStore,\n} from './store';\nimport type {\n EditAction,\n FeatureCollection,\n} from '@deck.gl-community/editable-layers';\nimport type { DrawShapeLayerProps } from './types';\nimport './fiber';\n\n/**\n * DrawShapeLayer - A React component for drawing shapes on the map.\n *\n * This component wraps the EditableGeoJsonLayer from @deck.gl-community/editable-layers\n * and integrates with the map-mode and map-cursor systems for proper coordination.\n *\n * Key features:\n * - Renders only when actively drawing (returns null otherwise)\n * - Uses cached mode instances to prevent deck.gl assertion errors\n * - Integrates with the drawing store for state management\n * - Protected drawing mode (rejects mode change requests while drawing)\n * - Distance/area tooltips during drawing\n *\n * ## Fiber Registration\n * Unlike `DisplayShapeLayer` (a deck.gl layer class), `DrawShapeLayer` is a React\n * component that handles its own fiber registration internally. You do **not** need to\n * import `draw-shape-layer/fiber` — but you **do** still need to import\n * `display-shape-layer/fiber` if you use `<displayShapeLayer>` in the same tree.\n *\n * @example\n * ```tsx\n * function Map({ mapId }) {\n * return (\n * <BaseMap id={mapId}>\n * <displayShapeLayer data={shapes} mapId={mapId} />\n * <DrawShapeLayer mapId={mapId} />\n * </BaseMap>\n * );\n * }\n * ```\n */\nexport function DrawShapeLayer({\n id = DRAW_SHAPE_LAYER_ID,\n mapId,\n unit,\n}: DrawShapeLayerProps) {\n // Get mapId from context if not provided\n const contextId = useContext(MapContext);\n const actualMapId = mapId ?? contextId;\n\n if (!actualMapId) {\n throw new Error(\n 'DrawShapeLayer requires either a mapId prop or to be used within a MapProvider',\n );\n }\n\n // Subscribe to drawing state using the v2 store API\n const { state: drawingState } = drawStore.use(actualMapId);\n\n const activeShapeType = drawingState?.activeShapeType ?? null;\n\n // Disable zoom while Shift is held during rectangle drawing\n // This prevents boxZoom (Shift+drag) from interfering with Shift-to-square constraint\n useShiftZoomDisable(actualMapId, activeShapeType === 'Rectangle');\n\n // If not drawing, return null (don't render the editable layer)\n if (!activeShapeType) {\n return null;\n }\n\n const styleDefaults = drawingState?.styleDefaults ?? null;\n\n // Get the cached mode instance\n const mode = getModeInstance(activeShapeType);\n\n // Handle edit events from EditableGeoJsonLayer\n const handleEdit = ({\n updatedData,\n editType,\n }: EditAction<FeatureCollection>) => {\n // Only process addFeature (drawing complete) and cancelFeature (ESC pressed)\n if (editType === 'addFeature') {\n const feature = updatedData.features.at(-1);\n if (feature) {\n // Type assertion: editable-layers Feature type differs slightly from geojson Feature\n // but they are structurally compatible for our conversion purposes\n completeDrawingFromLayer(\n actualMapId,\n feature as Parameters<typeof completeDrawingFromLayer>[1],\n );\n }\n } else if (editType === 'cancelFeature') {\n cancelDrawingFromLayer(actualMapId);\n }\n // Ignore other edit types during drawing (tentative updates, etc.)\n };\n\n // Get colors from style defaults or use tentative defaults\n const fillColor = styleDefaults?.fillColor ?? DEFAULT_TENTATIVE_COLORS.fill;\n const lineColor = styleDefaults?.lineColor ?? DEFAULT_TENTATIVE_COLORS.line;\n\n return (\n <editableGeoJsonLayer\n id={id}\n data={EMPTY_FEATURE_COLLECTION}\n mode={mode}\n selectedFeatureIndexes={[]}\n onEdit={handleEdit}\n getTentativeFillColor={fillColor}\n getTentativeLineColor={lineColor}\n {...getDefaultEditableLayerProps(unit)}\n />\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmEA,SAAgB,eAAe,EAC7B,KAAK,qBACL,OACA,QACsB;CAEtB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,cAAc,SAAS;AAE7B,KAAI,CAAC,YACH,OAAM,IAAI,MACR,iFACD;CAIH,MAAM,EAAE,OAAO,iBAAiB,UAAU,IAAI,YAAY;CAE1D,MAAM,kBAAkB,cAAc,mBAAmB;AAIzD,qBAAoB,aAAa,oBAAoB,YAAY;AAGjE,KAAI,CAAC,gBACH,QAAO;CAGT,MAAM,gBAAgB,cAAc,iBAAiB;CAGrD,MAAM,OAAO,gBAAgB,gBAAgB;CAG7C,MAAM,cAAc,EAClB,aACA,eACmC;AAEnC,MAAI,aAAa,cAAc;GAC7B,MAAM,UAAU,YAAY,SAAS,GAAG,GAAG;AAC3C,OAAI,QAGF,0BACE,aACA,QACD;aAEM,aAAa,gBACtB,wBAAuB,YAAY;;AASvC,QACE,oBAAC;EACK;EACJ,MAAM;EACA;EACN,wBAAwB,EAAE;EAC1B,QAAQ;EACR,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,uBAVc,eAAe,aAAa,yBAAyB;EAWnE,GAAI,6BAA6B,KAAK;GACtC"}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { DEFAULT_DISTANCE_UNITS
|
|
14
|
+
import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
|
|
15
15
|
import { formatCircleTooltip } from "../../shared/constants.js";
|
|
16
16
|
import { computeCircleMeasurements } from "../../shared/utils/geometry-measurements.js";
|
|
17
|
+
import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
|
|
17
18
|
import { DrawCircleFromCenterMode } from "@deck.gl-community/editable-layers";
|
|
18
19
|
|
|
19
20
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts
|
|
@@ -61,9 +62,10 @@ var DrawCircleModeWithTooltip = class extends DrawCircleFromCenterMode {
|
|
|
61
62
|
const { mapCoords } = event;
|
|
62
63
|
const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
|
|
63
64
|
const { radius, area } = computeCircleMeasurements(clickSequence.at(-1), mapCoords, distanceUnits);
|
|
65
|
+
const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
|
|
64
66
|
this.tooltip = {
|
|
65
67
|
position: mapCoords,
|
|
66
|
-
text: formatCircleTooltip(radius, area,
|
|
68
|
+
text: formatCircleTooltip(radius, area, unitAbbrev)
|
|
67
69
|
};
|
|
68
70
|
}
|
|
69
71
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.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\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport {
|
|
1
|
+
{"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.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\nimport {\n DISTANCE_UNIT_SYMBOLS,\n type DistanceUnit,\n} from '@accelint/constants/units';\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { DEFAULT_DISTANCE_UNITS } from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\n\n/**\n * Extends DrawCircleFromCenterMode to display radius and area tooltip.\n *\n * Shows the radius and area of the circle being drawn based on the radius\n * from center point to cursor position. The tooltip updates in real-time as\n * the cursor moves, displaying measurements in the configured distance units.\n *\n * ## Usage\n * This mode is automatically used by DrawShapeLayer when drawing circles.\n * The mode is cached at module level to prevent deck.gl assertion failures.\n *\n * ## Drawing Flow\n * 1. Click to set center point\n * 2. Move cursor to set radius (tooltip shows radius and area)\n * 3. Click to finish the circle\n *\n * @example\n * ```typescript\n * import { DrawCircleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawCircleModeWithTooltip();\n * ```\n */\nexport class DrawCircleModeWithTooltip extends DrawCircleFromCenterMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip with circle measurements.\n * Calculates radius and area based on the distance from center to cursor.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n (props.modeConfig?.distanceUnits as DistanceUnit) ??\n DEFAULT_DISTANCE_UNITS;\n\n const centerPoint = clickSequence.at(-1) as [number, number];\n const edgePoint = mapCoords as [number, number];\n\n const { radius, area } = computeCircleMeasurements(\n centerPoint,\n edgePoint,\n distanceUnits,\n );\n const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];\n\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(radius, area, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDA,IAAa,4BAAb,cAA+C,yBAAyB;;CAEtE,AAAQ,UAA0B;;;;;;;;CASlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACH,MAAM,YAAY,iBACnB;EAKF,MAAM,EAAE,QAAQ,SAAS,0BAHL,cAAc,GAAG,GAAG,EACtB,WAKhB,cACD;EACD,MAAM,aAAa,sBAAsB;AAEzC,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,QAAQ,MAAM,WAAW;GACpD;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
|
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
|
|
14
|
-
import { DEFAULT_DISTANCE_UNITS
|
|
14
|
+
import { DEFAULT_DISTANCE_UNITS } from "../../../../shared/units.js";
|
|
15
15
|
import { formatDistanceTooltip, formatEllipseTooltip } from "../../shared/constants.js";
|
|
16
16
|
import { distance } from "@turf/turf";
|
|
17
|
+
import { DISTANCE_UNIT_SYMBOLS } from "@accelint/constants/units";
|
|
17
18
|
import { DrawEllipseUsingThreePointsMode } from "@deck.gl-community/editable-layers";
|
|
18
19
|
|
|
19
20
|
//#region src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts
|
|
@@ -66,7 +67,7 @@ var DrawEllipseModeWithTooltip = class extends DrawEllipseUsingThreePointsMode {
|
|
|
66
67
|
}
|
|
67
68
|
const { mapCoords } = event;
|
|
68
69
|
const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
|
|
69
|
-
const unitAbbrev =
|
|
70
|
+
const unitAbbrev = DISTANCE_UNIT_SYMBOLS[distanceUnits];
|
|
70
71
|
const tooltipPosition = mapCoords;
|
|
71
72
|
if (clickSequence.length === 1) {
|
|
72
73
|
const firstPoint = clickSequence[0];
|