@accelint/map-toolkit 1.4.0 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/catalog-info.yaml +4 -4
- package/dist/camera/events.js +1 -1
- package/dist/camera/index.d.ts +1 -1
- package/dist/camera/index.js +1 -1
- package/dist/camera/store.d.ts +1 -1
- package/dist/camera/store.js +4 -6
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +1 -1
- package/dist/camera/types.js +1 -1
- package/dist/cursor-coordinates/constants.js +1 -1
- package/dist/cursor-coordinates/index.d.ts +1 -1
- package/dist/cursor-coordinates/index.js +1 -1
- package/dist/cursor-coordinates/store.d.ts +1 -1
- package/dist/cursor-coordinates/store.js +1 -1
- package/dist/cursor-coordinates/types.d.ts +1 -1
- package/dist/cursor-coordinates/types.js +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.js +4 -9
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.js +1 -1
- package/dist/deckgl/base-map/controls.d.ts +1 -1
- package/dist/deckgl/base-map/controls.js +1 -1
- package/dist/deckgl/base-map/events.js +1 -1
- package/dist/deckgl/base-map/index.d.ts +3 -3
- package/dist/deckgl/base-map/index.js +15 -7
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/base-map/provider.d.ts +3 -3
- package/dist/deckgl/base-map/provider.js +3 -5
- package/dist/deckgl/base-map/provider.js.map +1 -1
- package/dist/deckgl/base-map/types.d.ts +1 -1
- package/dist/deckgl/base-map/types.js +1 -1
- package/dist/deckgl/index.d.ts +4 -4
- package/dist/deckgl/index.js +4 -4
- package/dist/deckgl/saved-viewports/index.d.ts +1 -1
- package/dist/deckgl/saved-viewports/index.js +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +1 -1
- package/dist/deckgl/saved-viewports/storage.js +5 -10
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +66 -13
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/index.d.ts +74 -35
- package/dist/deckgl/shapes/display-shape-layer/index.js +381 -154
- package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/store.js +8 -2
- package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/types.d.ts +108 -19
- package/dist/deckgl/shapes/display-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +66 -36
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js +407 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/elevation.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +151 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +50 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +1 -0
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +28 -39
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -17
- package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +8 -9
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +4 -21
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +4 -34
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +11 -12
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +2 -32
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +38 -4
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +2 -2
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +4 -9
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/constants.js +17 -2
- package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/fiber.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
- package/dist/deckgl/shapes/edit-shape-layer/index.js +52 -21
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +4 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +7 -7
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/point-translate-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +78 -14
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +14 -2
- package/dist/deckgl/shapes/edit-shape-layer/types.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +2 -2
- package/dist/deckgl/shapes/index.d.ts +4 -4
- package/dist/deckgl/shapes/index.js +5 -5
- package/dist/deckgl/shapes/shared/constants.d.ts +4 -3
- package/dist/deckgl/shapes/shared/constants.js +55 -15
- package/dist/deckgl/shapes/shared/constants.js.map +1 -1
- package/dist/deckgl/shapes/shared/events.d.ts +5 -1
- package/dist/deckgl/shapes/shared/events.js +1 -1
- package/dist/deckgl/shapes/shared/events.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +19 -16
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +174 -53
- package/dist/deckgl/shapes/shared/types.js +155 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +29 -24
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/layer-config.js +9 -6
- package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/mode-utils.js +50 -20
- package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +22 -15
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +38 -14
- package/dist/deckgl/shapes/shared/utils/style-utils.js +43 -32
- package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +1 -1
- package/dist/deckgl/symbol-layer/fiber.js +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +1 -1
- package/dist/deckgl/symbol-layer/index.js +1 -1
- package/dist/deckgl/text-layer/character-sets.js +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +1 -1
- package/dist/deckgl/text-layer/default-settings.js +1 -1
- package/dist/deckgl/text-layer/fiber.d.ts +1 -1
- package/dist/deckgl/text-layer/fiber.js +1 -1
- package/dist/deckgl/text-layer/index.d.ts +1 -1
- package/dist/deckgl/text-layer/index.js +1 -1
- package/dist/deckgl/text-settings.d.ts +3 -3
- package/dist/deckgl/text-settings.js +1 -1
- package/dist/map-cursor/events.js +1 -1
- package/dist/map-cursor/index.d.ts +1 -1
- package/dist/map-cursor/index.js +1 -1
- package/dist/map-cursor/store.d.ts +1 -1
- package/dist/map-cursor/store.js +1 -1
- package/dist/map-cursor/types.d.ts +1 -1
- package/dist/map-cursor/types.js +1 -1
- package/dist/map-cursor/use-map-cursor.d.ts +1 -1
- package/dist/map-cursor/use-map-cursor.js +1 -1
- package/dist/map-mode/events.js +1 -1
- package/dist/map-mode/index.d.ts +1 -1
- package/dist/map-mode/index.js +1 -1
- package/dist/map-mode/store.d.ts +1 -1
- package/dist/map-mode/store.js +3 -8
- package/dist/map-mode/store.js.map +1 -1
- package/dist/map-mode/types.d.ts +1 -1
- package/dist/map-mode/types.js +1 -1
- package/dist/map-mode/use-map-mode.d.ts +1 -1
- package/dist/map-mode/use-map-mode.js +1 -1
- package/dist/maplibre/hooks/use-maplibre.d.ts +1 -1
- package/dist/maplibre/hooks/use-maplibre.js +1 -1
- package/dist/maplibre/index.d.ts +1 -1
- package/dist/maplibre/index.js +1 -1
- package/dist/shared/cleanup.d.ts +58 -0
- package/dist/shared/cleanup.js +93 -0
- package/dist/shared/cleanup.js.map +1 -0
- package/dist/shared/constants.js +1 -1
- package/dist/shared/create-map-store.d.ts +13 -1
- package/dist/shared/create-map-store.js +9 -4
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/shared/logger.js +31 -0
- package/dist/shared/logger.js.map +1 -0
- package/dist/shared/units.js +1 -1
- package/dist/viewport/index.d.ts +1 -1
- package/dist/viewport/index.js +1 -1
- package/dist/viewport/store.d.ts +1 -1
- package/dist/viewport/store.js +1 -1
- package/dist/viewport/types.d.ts +1 -1
- package/dist/viewport/types.js +1 -1
- package/dist/viewport/utils.d.ts +1 -1
- package/dist/viewport/utils.js +1 -1
- package/dist/viewport/viewport-size.d.ts +3 -3
- package/dist/viewport/viewport-size.js +1 -1
- package/package.json +22 -19
- package/dist/hotkey-manager/dist/react/use-hotkey.js +0 -39
- package/dist/hotkey-manager/dist/react/use-hotkey.js.map +0 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["features: Shape['feature'][]"],"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 { getLogger } from '@accelint/logger';\nimport { CompositeLayer } from '@deck.gl/core';\nimport { PathStyleExtension } from '@deck.gl/extensions';\nimport { GeoJsonLayer, IconLayer } from '@deck.gl/layers';\nimport { DASH_ARRAYS, SHAPE_LAYER_IDS } from '../shared/constants';\nimport { type ShapeEvent, ShapeEvents } from '../shared/events';\nimport {\n getDashArray,\n getFillColor,\n getLineColor,\n} from '../shared/utils/style-utils';\nimport {\n COFFIN_CORNERS,\n DEFAULT_DISPLAY_PROPS,\n MAP_INTERACTION,\n} from './constants';\nimport { createShapeLabelLayer } from './shape-label-layer';\nimport {\n getHighlightColor,\n getHighlightLineWidth,\n getHoverLineWidth,\n} from './utils/display-style';\nimport type { Layer, PickingInfo } from '@deck.gl/core';\nimport type { Shape, ShapeId } from '../shared/types';\nimport type { DisplayShapeLayerProps } from './types';\n\nconst logger = getLogger({\n enabled: process.env.NODE_ENV !== 'production',\n level: 'warn',\n prefix: '[DisplayShapeLayer]',\n pretty: true,\n});\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 * State type for DisplayShapeLayer\n */\ntype DisplayShapeLayerState = {\n /** Index of currently hovered shape, undefined when not hovering */\n hoverIndex?: number;\n /** ID of the last hovered shape for event deduplication */\n lastHoveredId?: ShapeId | null;\n /** Allow additional properties from base layer state */\n [key: string]: unknown;\n};\n\n/**\n * Cache for transformed features to avoid recreating objects on every render.\n */\ntype FeaturesCache = {\n /** Reference to the original data array for identity comparison */\n data: Shape[];\n /** Transformed features with shapeId added to properties */\n features: Shape['feature'][];\n /** Map of shapeId to feature index for O(1) lookup */\n shapeIdToIndex: Map<ShapeId, number>;\n};\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 dotted border and optional highlight\n * - **Hover effects**: Border/outline width increases on hover for better UX\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 * ## Selection Visual Feedback\n * When a shape is selected via `selectedShapeId`:\n * - The shape's border/outline pattern changes to dotted\n * - An optional highlight renders underneath (controlled by `showHighlight` prop)\n *\n * ## Layer Structure\n * Renders up to four sublayers (in order, bottom to top):\n * 1. **Highlight layer**: Selection highlight effect for non-icon-Point shapes (if showHighlight=true)\n * 2. **Coffin corners layer**: Selection/hover feedback for Point shapes with icons\n * 3. **Main GeoJsonLayer**: Shape geometries with styling and interaction\n * 4. **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\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 }\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 // biome-ignore lint/suspicious/noExplicitAny: sourceLayer type from deck.gl is not well-typed\n sourceLayer?: any;\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 // 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 } else if (\n info.index === undefined &&\n this.state?.hoverIndex !== undefined\n ) {\n this.setState({ hoverIndex: undefined });\n }\n\n // Call hover callback\n this.handleShapeHover(info);\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\n for (const [i, shape] of data.entries()) {\n features.push({\n ...shape.feature,\n properties: {\n ...shape.feature.properties,\n shapeId: shape.id,\n },\n });\n shapeIdToIndex.set(shape.id, i);\n }\n\n this.featuresCache = { data, features, shapeIdToIndex };\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 return this.props.data.find((shape) => shape.id === shapeId);\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 =\n (info.object?.properties?.shapeId as ShapeId | undefined) ?? null;\n const shape = shapeId ? (this.getShapeById(shapeId) ?? null) : null;\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 shapeBus.emit(ShapeEvents.hovered, {\n shapeId,\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 * Render highlight sublayer (underneath main layer)\n * Note: Points with icons use coffin corners instead of highlight layer\n */\n private renderHighlightLayer(\n features: Shape['feature'][],\n ): GeoJsonLayer | null {\n const { selectedShapeId, showHighlight, highlightColor } = this.props;\n\n if (!selectedShapeId || showHighlight === false) {\n return null;\n }\n\n const selectedFeature = features.find(\n (f) => f.properties?.shapeId === selectedShapeId,\n );\n\n if (!selectedFeature) {\n return null;\n }\n\n // Skip highlight layer for Point geometries with icons - they use coffin corners instead\n // Points without icons should still show the highlight layer\n if (selectedFeature.geometry.type === 'Point') {\n const hasIcon = !!selectedFeature.properties?.styleProperties?.icon;\n if (hasIcon) {\n return null;\n }\n }\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_HIGHLIGHT}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: [selectedFeature] as any,\n\n // Styling\n filled: true,\n stroked: true,\n lineWidthUnits: 'pixels',\n lineWidthMinPixels: MAP_INTERACTION.LINE_WIDTH_MIN_PIXELS,\n getFillColor: () => [0, 0, 0, 0], // Transparent fill\n getLineColor: () => highlightColor || getHighlightColor(),\n getLineWidth: getHighlightLineWidth,\n\n // Behavior\n pickable: false,\n updateTriggers: {\n getLineColor: [highlightColor],\n getLineWidth: [selectedShapeId, features],\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 highlight layer\n */\n private renderCoffinCornersLayer(\n features: Shape['feature'][],\n ): IconLayer | null {\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 null;\n }\n\n // Find point features that need coffin corners (hovered or selected)\n const pointFeatures = features.filter((f) => {\n if (f.geometry.type !== 'Point') {\n return false;\n }\n const hasIcon = !!f.properties?.styleProperties?.icon;\n if (!hasIcon) {\n return false;\n }\n\n const shapeId = f.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered = hoverIndex !== undefined && featureIndex === hoverIndex;\n\n return isSelected || isHovered;\n });\n\n if (pointFeatures.length === 0) {\n return null;\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 null;\n }\n\n // Add coffin corners icons to the mapping\n const extendedMapping = {\n ...iconMapping,\n [COFFIN_CORNERS.HOVER_ICON]: {\n x: 0,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_ICON]: {\n x: 76,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n [COFFIN_CORNERS.SELECTED_HOVER_ICON]: {\n x: 152,\n y: 0,\n width: 76,\n height: 76,\n mask: false,\n },\n };\n\n return 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 shapeId = d.properties?.shapeId;\n const isSelected = shapeId === selectedShapeId;\n const featureIndex = shapeId ? shapeIdToIndex.get(shapeId) : undefined;\n const isHovered =\n hoverIndex !== undefined && featureIndex === hoverIndex;\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 =\n d.geometry.type === 'Point' ? d.geometry.coordinates : [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 * 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 first feature has icons.\n */\n private getIconConfig(features: Shape['feature'][]): {\n hasIcons: boolean;\n atlas?: string;\n mapping?: Record<\n string,\n { x: number; y: number; width: number; height: number; mask?: boolean }\n >;\n } {\n for (const f of features) {\n const icon = f.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 * 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 } = this.getIconConfig(features);\n\n return new GeoJsonLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY}`,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJsonLayer accepts various feature formats\n data: features as any,\n\n // Styling\n filled: true,\n stroked: true,\n getFillColor: (d: Shape['feature']) => getFillColor(d, applyBaseOpacity),\n getLineColor,\n getLineWidth: (d, info) => {\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 // 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 ...(hasIcons && iconAtlas ? { iconAtlas } : {}),\n ...(hasIcons && iconMapping ? { iconMapping } : {}),\n ...(hasIcons\n ? {\n getIcon: (d: Shape['feature']) =>\n d.properties?.styleProperties?.icon?.name ?? 'marker',\n getIconSize: (d: Shape['feature']) => {\n return (\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE\n );\n },\n getIconColor: getLineColor,\n getIconPixelOffset: (d: Shape['feature']) => {\n const iconSize =\n d.properties?.styleProperties?.icon?.size ??\n MAP_INTERACTION.ICON_SIZE;\n return [-1, -iconSize / 2];\n },\n iconBillboard: false,\n }\n : {}),\n\n // Dash pattern support - selected shapes get dotted border\n extensions: [new PathStyleExtension({ dash: true })],\n getDashArray: (d: Shape['feature']) => {\n const isSelected = d.properties?.shapeId === selectedShapeId;\n if (isSelected) {\n return DASH_ARRAYS.dotted;\n }\n return getDashArray(d);\n },\n\n // Behavior\n pickable,\n autoHighlight: false, // We handle highlighting manually\n // Note: onClick and onHover are handled via getPickingInfo() override\n\n // Update triggers\n updateTriggers: {\n getFillColor: [features, applyBaseOpacity],\n getLineColor: [features],\n getLineWidth: [features, this.state?.hoverIndex],\n getDashArray: [features, selectedShapeId],\n getPointRadius: [features],\n ...(hasIcons\n ? {\n getIcon: [features],\n getIconSize: [features],\n getIconColor: [features],\n getIconPixelOffset: [features],\n }\n : {}),\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> | null {\n const { showLabels, data, labelOptions } = this.props;\n\n // No labels if disabled\n if (showLabels === 'never') {\n return null;\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 null; // 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 null;\n }\n\n return createShapeLabelLayer({\n id: `${this.props.id}-${SHAPE_LAYER_IDS.DISPLAY_LABELS}`,\n data: labelData,\n labelOptions,\n });\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\n return [\n this.renderHighlightLayer(features),\n this.renderCoffinCornersLayer(features),\n this.renderMainLayer(features),\n this.renderLabelsLayer(),\n ].filter(Boolean) as Layer[];\n }\n}\n"],"mappings":"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"}
|
|
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,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -96,7 +96,13 @@ const shapeSelectionStore = createMapStore({
|
|
|
96
96
|
* ```
|
|
97
97
|
*/
|
|
98
98
|
const useSelectShape = shapeSelectionStore.use;
|
|
99
|
+
/**
|
|
100
|
+
* Clear selection state (for tests/cleanup)
|
|
101
|
+
*/
|
|
102
|
+
function clearSelectionState(mapId) {
|
|
103
|
+
shapeSelectionStore.clear(mapId);
|
|
104
|
+
}
|
|
99
105
|
|
|
100
106
|
//#endregion
|
|
101
|
-
export { shapeSelectionStore };
|
|
107
|
+
export { clearSelectionState, shapeSelectionStore };
|
|
102
108
|
//# sourceMappingURL=store.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/store.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/**\n * Shape Selection Store\n *\n * Manages shape selection state per map instance.\n *\n * @example\n * ```tsx\n * import { shapeSelectionStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function ShapePanel({ mapId }) {\n * const { state, setSelectedId, clearSelection } = shapeSelectionStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Selected: {state.selectedId ?? 'none'}</p>\n * <button onClick={() => setSelectedId('shape-1')}>Select Shape 1</button>\n * <button onClick={clearSelection}>Clear</button>\n * </div>\n * );\n * }\n *\n * // Or with selector for specific values:\n * function SelectedIndicator({ mapId }) {\n * const selectedId = shapeSelectionStore.useSelector(\n * mapId,\n * (s) => s.selectedId\n * );\n * return selectedId ? <Badge>Selected</Badge> : null;\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { MapEvents } from '../../base-map/events';\nimport { ShapeEvents } from '../shared/events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapClickEvent, MapEventType } from '../../base-map/types';\nimport type { ShapeEvent } from '../shared/events';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * State shape for shape selection\n */\ntype ShapeSelectionState = {\n selectedId: ShapeId | undefined;\n};\n\n/**\n * Actions for shape selection\n */\ntype ShapeSelectionActions = {\n /** Set the selected shape ID (emits appropriate events) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Clear the current selection */\n clearSelection: () => void;\n};\n\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\nconst mapBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Shape selection store\n */\nexport const shapeSelectionStore = createMapStore<\n ShapeSelectionState,\n ShapeSelectionActions\n>({\n defaultState: { selectedId: undefined },\n\n actions: (mapId, { get }) => ({\n setSelectedId: (id: ShapeId | undefined) => {\n const currentId = get().selectedId;\n\n if (id === undefined && currentId !== undefined) {\n // Emit deselection event\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n } else if (id !== undefined && currentId !== id) {\n // Emit selection event\n shapeBus.emit(ShapeEvents.selected, { shapeId: id, mapId });\n }\n },\n\n clearSelection: () => {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Listen for shape selection events\n const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== event.payload.shapeId) {\n set({ selectedId: event.payload.shapeId });\n }\n });\n\n // Listen for shape deselection events\n const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== undefined) {\n set({ selectedId: undefined });\n }\n });\n\n // Listen for map clicks to detect clicks on empty space\n const unsubClick = mapBus.on(MapEvents.click, (event: MapClickEvent) => {\n // Deselect if clicked on empty space (index === -1)\n if (\n get().selectedId !== undefined &&\n event.payload.id === mapId &&\n event.payload.info.index === -1\n ) {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n }\n });\n\n return () => {\n unsubSelected();\n unsubDeselected();\n unsubClick();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports for common patterns\n// =============================================================================\n\n/**\n * Hook for shape selection - primary API\n *\n * @example\n * ```tsx\n * const { state, setSelectedId, clearSelection } = useSelectShape(mapId);\n * ```\n */\nexport const useSelectShape = shapeSelectionStore.use;\n\n/**\n * Hook to get just the selected ID\n *\n * @example\n * ```tsx\n * const selectedId = useSelectedShapeId(mapId);\n * ```\n */\nexport function useSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.useSelector(mapId, (s) => s.selectedId);\n}\n\n/**\n * Get selected shape ID imperatively (non-reactive)\n */\nexport function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.get(mapId).selectedId;\n}\n\n/**\n * Clear selection state (for tests/cleanup)\n */\nexport function clearSelectionState(mapId: UniqueId): void {\n shapeSelectionStore.clear(mapId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,WAAW,UAAU,aAAyB;AACpD,MAAM,SAAS,UAAU,aAA2B;;;;AAKpD,MAAa,sBAAsB,eAGjC;CACA,cAAc,EAAE,YAAY,QAAW;CAEvC,UAAU,OAAO,EAAE,WAAW;EAC5B,gBAAgB,OAA4B;GAC1C,MAAM,YAAY,KAAK,CAAC;AAExB,OAAI,OAAO,UAAa,cAAc,OAEpC,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;YACvC,OAAO,UAAa,cAAc,GAE3C,UAAS,KAAK,YAAY,UAAU;IAAE,SAAS;IAAI;IAAO,CAAC;;EAI/D,sBAAsB;AACpB,YAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;;EAEnD;CAED,MAAM,OAAO,EAAE,KAAK,UAAU;EAE5B,MAAM,gBAAgB,SAAS,GAAG,YAAY,WAAW,UAAU;AACjE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,MAAM,QAAQ,QACrC,KAAI,EAAE,YAAY,MAAM,QAAQ,SAAS,CAAC;IAE5C;EAGF,MAAM,kBAAkB,SAAS,GAAG,YAAY,aAAa,UAAU;AACrE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,OACvB,KAAI,EAAE,YAAY,QAAW,CAAC;IAEhC;EAGF,MAAM,aAAa,OAAO,GAAG,UAAU,QAAQ,UAAyB;AAEtE,OACE,KAAK,CAAC,eAAe,UACrB,MAAM,QAAQ,OAAO,SACrB,MAAM,QAAQ,KAAK,UAAU,GAE7B,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;IAElD;AAEF,eAAa;AACX,kBAAe;AACf,oBAAiB;AACjB,eAAY;;;CAGjB,CAAC;;;;;;;;;AAcF,MAAa,iBAAiB,oBAAoB"}
|
|
1
|
+
{"version":3,"file":"store.js","names":[],"sources":["../../../../src/deckgl/shapes/display-shape-layer/store.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/**\n * Shape Selection Store\n *\n * Manages shape selection state per map instance.\n *\n * @example\n * ```tsx\n * import { shapeSelectionStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function ShapePanel({ mapId }) {\n * const { state, setSelectedId, clearSelection } = shapeSelectionStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Selected: {state.selectedId ?? 'none'}</p>\n * <button onClick={() => setSelectedId('shape-1')}>Select Shape 1</button>\n * <button onClick={clearSelection}>Clear</button>\n * </div>\n * );\n * }\n *\n * // Or with selector for specific values:\n * function SelectedIndicator({ mapId }) {\n * const selectedId = shapeSelectionStore.useSelector(\n * mapId,\n * (s) => s.selectedId\n * );\n * return selectedId ? <Badge>Selected</Badge> : null;\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { MapEvents } from '../../base-map/events';\nimport { ShapeEvents } from '../shared/events';\nimport type { UniqueId } from '@accelint/core';\nimport type { MapClickEvent, MapEventType } from '../../base-map/types';\nimport type { ShapeEvent } from '../shared/events';\nimport type { ShapeId } from '../shared/types';\n\n/**\n * State shape for shape selection\n */\ntype ShapeSelectionState = {\n selectedId: ShapeId | undefined;\n};\n\n/**\n * Actions for shape selection\n */\ntype ShapeSelectionActions = {\n /** Set the selected shape ID (emits appropriate events) */\n setSelectedId: (id: ShapeId | undefined) => void;\n /** Clear the current selection */\n clearSelection: () => void;\n};\n\nconst shapeBus = Broadcast.getInstance<ShapeEvent>();\nconst mapBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Shape selection store\n */\nexport const shapeSelectionStore = createMapStore<\n ShapeSelectionState,\n ShapeSelectionActions\n>({\n defaultState: { selectedId: undefined },\n\n actions: (mapId, { get }) => ({\n setSelectedId: (id: ShapeId | undefined) => {\n const currentId = get().selectedId;\n\n if (id === undefined && currentId !== undefined) {\n // Emit deselection event\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n } else if (id !== undefined && currentId !== id) {\n // Emit selection event\n shapeBus.emit(ShapeEvents.selected, { shapeId: id, mapId });\n }\n },\n\n clearSelection: () => {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n },\n }),\n\n bus: (mapId, { get, set }) => {\n // Listen for shape selection events\n const unsubSelected = shapeBus.on(ShapeEvents.selected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== event.payload.shapeId) {\n set({ selectedId: event.payload.shapeId });\n }\n });\n\n // Listen for shape deselection events\n const unsubDeselected = shapeBus.on(ShapeEvents.deselected, (event) => {\n if (event.payload.mapId !== mapId) {\n return;\n }\n if (get().selectedId !== undefined) {\n set({ selectedId: undefined });\n }\n });\n\n // Listen for map clicks to detect clicks on empty space\n const unsubClick = mapBus.on(MapEvents.click, (event: MapClickEvent) => {\n // Deselect if clicked on empty space (index === -1)\n if (\n get().selectedId !== undefined &&\n event.payload.id === mapId &&\n event.payload.info.index === -1\n ) {\n shapeBus.emit(ShapeEvents.deselected, { mapId });\n }\n });\n\n return () => {\n unsubSelected();\n unsubDeselected();\n unsubClick();\n };\n },\n});\n\n// =============================================================================\n// Convenience exports for common patterns\n// =============================================================================\n\n/**\n * Hook for shape selection - primary API\n *\n * @example\n * ```tsx\n * const { state, setSelectedId, clearSelection } = useSelectShape(mapId);\n * ```\n */\nexport const useSelectShape = shapeSelectionStore.use;\n\n/**\n * Hook to get just the selected ID\n *\n * @example\n * ```tsx\n * const selectedId = useSelectedShapeId(mapId);\n * ```\n */\nexport function useSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.useSelector(mapId, (s) => s.selectedId);\n}\n\n/**\n * Get selected shape ID imperatively (non-reactive)\n */\nexport function getSelectedShapeId(mapId: UniqueId): ShapeId | undefined {\n return shapeSelectionStore.get(mapId).selectedId;\n}\n\n/**\n * Clear selection state (for tests/cleanup)\n */\nexport function clearSelectionState(mapId: UniqueId): void {\n shapeSelectionStore.clear(mapId);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAsEA,MAAM,WAAW,UAAU,aAAyB;AACpD,MAAM,SAAS,UAAU,aAA2B;;;;AAKpD,MAAa,sBAAsB,eAGjC;CACA,cAAc,EAAE,YAAY,QAAW;CAEvC,UAAU,OAAO,EAAE,WAAW;EAC5B,gBAAgB,OAA4B;GAC1C,MAAM,YAAY,KAAK,CAAC;AAExB,OAAI,OAAO,UAAa,cAAc,OAEpC,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;YACvC,OAAO,UAAa,cAAc,GAE3C,UAAS,KAAK,YAAY,UAAU;IAAE,SAAS;IAAI;IAAO,CAAC;;EAI/D,sBAAsB;AACpB,YAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;;EAEnD;CAED,MAAM,OAAO,EAAE,KAAK,UAAU;EAE5B,MAAM,gBAAgB,SAAS,GAAG,YAAY,WAAW,UAAU;AACjE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,MAAM,QAAQ,QACrC,KAAI,EAAE,YAAY,MAAM,QAAQ,SAAS,CAAC;IAE5C;EAGF,MAAM,kBAAkB,SAAS,GAAG,YAAY,aAAa,UAAU;AACrE,OAAI,MAAM,QAAQ,UAAU,MAC1B;AAEF,OAAI,KAAK,CAAC,eAAe,OACvB,KAAI,EAAE,YAAY,QAAW,CAAC;IAEhC;EAGF,MAAM,aAAa,OAAO,GAAG,UAAU,QAAQ,UAAyB;AAEtE,OACE,KAAK,CAAC,eAAe,UACrB,MAAM,QAAQ,OAAO,SACrB,MAAM,QAAQ,KAAK,UAAU,GAE7B,UAAS,KAAK,YAAY,YAAY,EAAE,OAAO,CAAC;IAElD;AAEF,eAAa;AACX,kBAAe;AACf,oBAAiB;AACjB,eAAY;;;CAGjB,CAAC;;;;;;;;;AAcF,MAAa,iBAAiB,oBAAoB;;;;AAwBlD,SAAgB,oBAAoB,OAAuB;AACzD,qBAAoB,MAAM,MAAM"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -10,12 +10,89 @@
|
|
|
10
10
|
* governing permissions and limitations under the License.
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
-
import { Shape, ShapeId
|
|
13
|
+
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 { Rgba255Tuple } from "@accelint/predicates";
|
|
17
18
|
|
|
18
19
|
//#region src/deckgl/shapes/display-shape-layer/types.d.ts
|
|
20
|
+
/**
|
|
21
|
+
* A vertical curtain polygon feature for elevation visualization.
|
|
22
|
+
* Used to render filled vertical surfaces from ground to elevation for LineStrings.
|
|
23
|
+
*/
|
|
24
|
+
type CurtainFeature = {
|
|
25
|
+
type: 'Feature';
|
|
26
|
+
geometry: {
|
|
27
|
+
type: 'Polygon';
|
|
28
|
+
coordinates: number[][][];
|
|
29
|
+
};
|
|
30
|
+
properties: {
|
|
31
|
+
fillColor: Rgba255Tuple;
|
|
32
|
+
lineColor: Rgba255Tuple;
|
|
33
|
+
shapeId?: ShapeId;
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/** A vertical line segment from ground to an elevated position. */
|
|
37
|
+
type LineSegment = {
|
|
38
|
+
source: [number, number, number];
|
|
39
|
+
target: [number, number, number];
|
|
40
|
+
color: Rgba255Tuple;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Result of classifying features by geometry type and elevation.
|
|
44
|
+
*/
|
|
45
|
+
type ElevatedFeatureClassification = {
|
|
46
|
+
lines: Shape['feature'][];
|
|
47
|
+
polygons: Shape['feature'][];
|
|
48
|
+
nonPolygons: Shape['feature'][];
|
|
49
|
+
};
|
|
50
|
+
/**
|
|
51
|
+
* State type for DisplayShapeLayer
|
|
52
|
+
*/
|
|
53
|
+
type DisplayShapeLayerState = {
|
|
54
|
+
/** Index of currently hovered shape, undefined when not hovering */
|
|
55
|
+
hoverIndex?: number;
|
|
56
|
+
/** ID of the last hovered shape for event deduplication */
|
|
57
|
+
lastHoveredId?: ShapeId;
|
|
58
|
+
/** Allow additional properties from base layer state */
|
|
59
|
+
[key: string]: unknown;
|
|
60
|
+
};
|
|
61
|
+
/**
|
|
62
|
+
* Cache for transformed features to avoid recreating objects on every render.
|
|
63
|
+
*/
|
|
64
|
+
type FeaturesCache = {
|
|
65
|
+
/** Reference to the original data array for identity comparison */
|
|
66
|
+
data: Shape[];
|
|
67
|
+
/** Transformed features with shapeId added to properties */
|
|
68
|
+
features: Shape['feature'][];
|
|
69
|
+
/** Map of shapeId to feature index for O(1) lookup */
|
|
70
|
+
shapeIdToIndex: Map<ShapeId, number>;
|
|
71
|
+
/** Pre-normalized line colors parallel to features, for O(1) accessor lookup */
|
|
72
|
+
normalizedLineColors: Rgba255Tuple[];
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Cache for elevation-derived data (feature classification + curtain features).
|
|
76
|
+
* Keyed on features identity and applyBaseOpacity to avoid per-frame recomputation.
|
|
77
|
+
* deck.gl calls renderLayers() every frame during map interaction; without this cache,
|
|
78
|
+
* curtain polygon arrays are recreated every frame, forcing deck.gl GPU buffer rebuilds.
|
|
79
|
+
*/
|
|
80
|
+
type ElevationCache = {
|
|
81
|
+
features: Shape['feature'][];
|
|
82
|
+
applyBaseOpacity: boolean | undefined;
|
|
83
|
+
classification: ElevatedFeatureClassification;
|
|
84
|
+
curtainFeatures: CurtainFeature[];
|
|
85
|
+
};
|
|
86
|
+
/**
|
|
87
|
+
* Cache for elevation indicator line segments.
|
|
88
|
+
* Keyed on features, selectedShapeId, and hoverIndex since all three affect output.
|
|
89
|
+
*/
|
|
90
|
+
type IndicatorCache = {
|
|
91
|
+
features: Shape['feature'][];
|
|
92
|
+
selectedShapeId: ShapeId | undefined;
|
|
93
|
+
hoverIndex: number | undefined;
|
|
94
|
+
lineData: LineSegment[];
|
|
95
|
+
};
|
|
19
96
|
/**
|
|
20
97
|
* Label display mode for shapes
|
|
21
98
|
* - `'always'`: Show labels for all shapes
|
|
@@ -23,14 +100,6 @@ import { CompositeLayerProps } from "@deck.gl/core";
|
|
|
23
100
|
* - `'never'`: Never show labels
|
|
24
101
|
*/
|
|
25
102
|
type ShowLabelsMode = 'always' | 'hover' | 'never';
|
|
26
|
-
/**
|
|
27
|
-
* Re-export StyledFeature from shared types
|
|
28
|
-
*/
|
|
29
|
-
type StyledFeature = StyledFeature$1;
|
|
30
|
-
/**
|
|
31
|
-
* Re-export StyledFeatureProperties from shared types
|
|
32
|
-
*/
|
|
33
|
-
type StyledFeatureProperties = StyledFeature$1['properties'];
|
|
34
103
|
/**
|
|
35
104
|
* Props for DisplayShapeLayer
|
|
36
105
|
*
|
|
@@ -44,7 +113,7 @@ type StyledFeatureProperties = StyledFeature$1['properties'];
|
|
|
44
113
|
* };
|
|
45
114
|
* ```
|
|
46
115
|
*/
|
|
47
|
-
|
|
116
|
+
type DisplayShapeLayerProps = CompositeLayerProps & {
|
|
48
117
|
/** Unique layer ID - required for deck.gl layer management */
|
|
49
118
|
id: string;
|
|
50
119
|
/**
|
|
@@ -59,8 +128,8 @@ interface DisplayShapeLayerProps extends CompositeLayerProps {
|
|
|
59
128
|
*/
|
|
60
129
|
data: Shape[];
|
|
61
130
|
/**
|
|
62
|
-
* Currently selected shape ID
|
|
63
|
-
* When set, renders a
|
|
131
|
+
* Currently selected shape ID.
|
|
132
|
+
* When set, renders a brightness overlay for polygon shapes.
|
|
64
133
|
*/
|
|
65
134
|
selectedShapeId?: ShapeId;
|
|
66
135
|
/**
|
|
@@ -71,10 +140,10 @@ interface DisplayShapeLayerProps extends CompositeLayerProps {
|
|
|
71
140
|
onShapeClick?: (shape: Shape) => void;
|
|
72
141
|
/**
|
|
73
142
|
* Callback when a shape is hovered
|
|
74
|
-
* Called with
|
|
75
|
-
* @param shape - The hovered shape, or
|
|
143
|
+
* Called with no argument when hover ends
|
|
144
|
+
* @param shape - The hovered shape, or undefined when hover ends
|
|
76
145
|
*/
|
|
77
|
-
onShapeHover?: (shape
|
|
146
|
+
onShapeHover?: (shape?: Shape) => void;
|
|
78
147
|
/**
|
|
79
148
|
* Label display mode for shapes
|
|
80
149
|
* - `'always'`: Show labels for all shapes
|
|
@@ -107,7 +176,7 @@ interface DisplayShapeLayerProps extends CompositeLayerProps {
|
|
|
107
176
|
* highlightColor={[255, 0, 0, 128]} // Red at 50% opacity
|
|
108
177
|
* ```
|
|
109
178
|
*/
|
|
110
|
-
highlightColor?:
|
|
179
|
+
highlightColor?: Rgba255Tuple;
|
|
111
180
|
/**
|
|
112
181
|
* When true (default), multiplies fill color alpha by 0.2 (reducing to 20% of original opacity)
|
|
113
182
|
* for a standard semi-transparent look.
|
|
@@ -120,7 +189,27 @@ interface DisplayShapeLayerProps extends CompositeLayerProps {
|
|
|
120
189
|
* ```
|
|
121
190
|
*/
|
|
122
191
|
applyBaseOpacity?: boolean;
|
|
123
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Enable 3D elevation rendering features (extrusion, curtains, vertical indicators).
|
|
194
|
+
* When false, shapes render as flat 2D with standard styling.
|
|
195
|
+
* When true, enables:
|
|
196
|
+
* - Polygon extrusion based on elevation coordinates
|
|
197
|
+
* - Filled curtains for elevated LineStrings
|
|
198
|
+
* - Vertical indicator lines from ground to elevated points
|
|
199
|
+
*
|
|
200
|
+
* Typically controlled by camera view: enable in 2.5D/3D, disable in 2D.
|
|
201
|
+
* @default false
|
|
202
|
+
* @example Enable elevation in 2.5D/3D views
|
|
203
|
+
* ```tsx
|
|
204
|
+
* const { cameraState } = useMapCamera(mapId);
|
|
205
|
+
* <DisplayShapeLayer
|
|
206
|
+
* data={shapes}
|
|
207
|
+
* enableElevation={cameraState.view !== '2D'}
|
|
208
|
+
* />
|
|
209
|
+
* ```
|
|
210
|
+
*/
|
|
211
|
+
enableElevation?: boolean;
|
|
212
|
+
};
|
|
124
213
|
//#endregion
|
|
125
|
-
export { DisplayShapeLayerProps,
|
|
214
|
+
export { CurtainFeature, DisplayShapeLayerProps, DisplayShapeLayerState, ElevatedFeatureClassification, ElevationCache, FeaturesCache, IndicatorCache, LineSegment, ShowLabelsMode };
|
|
126
215
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/*
|
|
2
|
-
* Copyright
|
|
2
|
+
* Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
|
|
3
3
|
* This file is licensed to you under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
* you may not use this file except in compliance with the License. You may obtain a copy
|
|
5
5
|
* of the License at https://www.apache.org/licenses/LICENSE-2.0
|
|
@@ -13,8 +13,9 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
16
|
+
import { HIGHLIGHT_WIDTH_INCREASE, HOVER_WIDTH_INCREASE } from "../../shared/constants.js";
|
|
17
|
+
import { getFillColor, getLineWidth } from "../../shared/utils/style-utils.js";
|
|
18
|
+
import { OVERLAY_FILL_OPACITY } from "../constants.js";
|
|
18
19
|
|
|
19
20
|
//#region src/deckgl/shapes/display-shape-layer/utils/display-style.ts
|
|
20
21
|
/**
|
|
@@ -45,38 +46,6 @@ function getHoverLineWidth(feature, isHovered) {
|
|
|
45
46
|
return isHovered ? baseWidth + HOVER_WIDTH_INCREASE : baseWidth;
|
|
46
47
|
}
|
|
47
48
|
/**
|
|
48
|
-
* Get selection highlight color.
|
|
49
|
-
*
|
|
50
|
-
* Returns the default highlight color with optional custom opacity override.
|
|
51
|
-
* The highlight color is used to indicate selected features on the map.
|
|
52
|
-
*
|
|
53
|
-
* @param opacity - Optional opacity value (0-1 range), overrides default opacity
|
|
54
|
-
* @returns RGBA color array [red, green, blue, alpha] with values 0-255
|
|
55
|
-
*
|
|
56
|
-
* @example
|
|
57
|
-
* ```typescript
|
|
58
|
-
* import { getHighlightColor } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/display-style';
|
|
59
|
-
*
|
|
60
|
-
* // Use default highlight color with default opacity
|
|
61
|
-
* const defaultColor = getHighlightColor();
|
|
62
|
-
* // Returns: [r, g, b, a] from DEFAULT_COLORS.highlight
|
|
63
|
-
*
|
|
64
|
-
* // Use custom opacity (50% transparent)
|
|
65
|
-
* const semiTransparent = getHighlightColor(0.5);
|
|
66
|
-
* // Returns: [r, g, b, 127]
|
|
67
|
-
* ```
|
|
68
|
-
*/
|
|
69
|
-
function getHighlightColor(opacity) {
|
|
70
|
-
const rgba = normalizeColor(DEFAULT_COLORS.highlight);
|
|
71
|
-
if (opacity !== void 0) return [
|
|
72
|
-
rgba[0],
|
|
73
|
-
rgba[1],
|
|
74
|
-
rgba[2],
|
|
75
|
-
Math.round(opacity * 255)
|
|
76
|
-
];
|
|
77
|
-
return rgba;
|
|
78
|
-
}
|
|
79
|
-
/**
|
|
80
49
|
* Get highlight border/outline width.
|
|
81
50
|
*
|
|
82
51
|
* Calculates the line width for a selected/highlighted feature by adding a fixed
|
|
@@ -101,7 +70,68 @@ function getHighlightColor(opacity) {
|
|
|
101
70
|
function getHighlightLineWidth(feature) {
|
|
102
71
|
return getLineWidth(feature) + HIGHLIGHT_WIDTH_INCREASE;
|
|
103
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* Scale the alpha channel of a raw RGBA color by OVERLAY_FILL_OPACITY.
|
|
75
|
+
* Used by interaction overlay layers (hover, select, curtains) to sit at
|
|
76
|
+
* a consistent opacity between the base layer (0.2) and fully solid (1.0).
|
|
77
|
+
*
|
|
78
|
+
* @param color - RGBA color tuple [r, g, b, a] (0-255)
|
|
79
|
+
* @returns Color with alpha multiplied by OVERLAY_FILL_OPACITY
|
|
80
|
+
* @example
|
|
81
|
+
* ```typescript
|
|
82
|
+
* applyOverlayOpacity([255, 128, 0, 200]);
|
|
83
|
+
* // → [255, 128, 0, 50] (200 × 0.25)
|
|
84
|
+
* ```
|
|
85
|
+
*/
|
|
86
|
+
function applyOverlayOpacity(color) {
|
|
87
|
+
return [
|
|
88
|
+
color[0],
|
|
89
|
+
color[1],
|
|
90
|
+
color[2],
|
|
91
|
+
Math.round(color[3] * OVERLAY_FILL_OPACITY)
|
|
92
|
+
];
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Get fill color for interaction overlay layers (hover, select).
|
|
96
|
+
*
|
|
97
|
+
* Returns the shape's fill color with alpha scaled by OVERLAY_FILL_OPACITY —
|
|
98
|
+
* more opaque than the base-opacity main layer but not fully solid, so the
|
|
99
|
+
* material brightness effect reads clearly without looking like a solid block.
|
|
100
|
+
*
|
|
101
|
+
* @param feature - The styled feature
|
|
102
|
+
* @returns RGBA color with alpha multiplied by OVERLAY_FILL_OPACITY
|
|
103
|
+
* @example
|
|
104
|
+
* ```typescript
|
|
105
|
+
* // Feature with fillColor [98, 166, 255, 255]
|
|
106
|
+
* getOverlayFillColor(feature); // → [98, 166, 255, 64]
|
|
107
|
+
* ```
|
|
108
|
+
*/
|
|
109
|
+
function getOverlayFillColor(feature) {
|
|
110
|
+
return applyOverlayOpacity(getFillColor(feature));
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Brighten an RGBA color by multiplying RGB channels by a factor.
|
|
114
|
+
* Alpha is preserved unchanged.
|
|
115
|
+
*
|
|
116
|
+
* @param color - RGBA color tuple [r, g, b, a] (0-255)
|
|
117
|
+
* @param factor - Multiplier for RGB channels (e.g. 1.5 = 50% brighter)
|
|
118
|
+
* @returns Brightened RGBA color, clamped to 0-255
|
|
119
|
+
*
|
|
120
|
+
* @example
|
|
121
|
+
* ```typescript
|
|
122
|
+
* const brighter = brightenColor([100, 150, 200, 255], 1.5);
|
|
123
|
+
* // Returns: [150, 225, 255, 255] — RGB multiplied by 1.5, alpha unchanged
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
function brightenColor(color, factor) {
|
|
127
|
+
return [
|
|
128
|
+
Math.min(255, Math.round(color[0] * factor)),
|
|
129
|
+
Math.min(255, Math.round(color[1] * factor)),
|
|
130
|
+
Math.min(255, Math.round(color[2] * factor)),
|
|
131
|
+
color[3]
|
|
132
|
+
];
|
|
133
|
+
}
|
|
104
134
|
|
|
105
135
|
//#endregion
|
|
106
|
-
export {
|
|
136
|
+
export { applyOverlayOpacity, brightenColor, getHighlightLineWidth, getHoverLineWidth, getOverlayFillColor };
|
|
107
137
|
//# sourceMappingURL=display-style.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"display-style.js","names":[],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/display-style.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport {\n
|
|
1
|
+
{"version":3,"file":"display-style.js","names":[],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/display-style.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport {\n HIGHLIGHT_WIDTH_INCREASE,\n HOVER_WIDTH_INCREASE,\n} from '../../shared/constants';\nimport { getFillColor, getLineWidth } from '../../shared/utils/style-utils';\nimport { OVERLAY_FILL_OPACITY } from '../constants';\nimport type { Rgba255Tuple } from '@accelint/predicates';\nimport type { StyledFeature } from '../../shared/types';\n\n/**\n * Get hover-enhanced border/outline width.\n *\n * Calculates the line width for a feature, increasing it when hovered to provide\n * visual feedback. The hover effect adds a fixed pixel increase to the base width.\n *\n * @param feature - The styled feature to calculate width for\n * @param isHovered - Whether the feature is currently being hovered\n * @returns The calculated line width in pixels\n *\n * @example\n * ```typescript\n * import { getHoverLineWidth } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/display-style';\n * import type { StyledFeature } from '@accelint/map-toolkit/deckgl/shapes/shared/types';\n *\n * const feature: StyledFeature = {\n * properties: { styleProperties: { lineWidth: 2 } }\n * };\n *\n * const width = getHoverLineWidth(feature, true);\n * // Returns: 2 + HOVER_WIDTH_INCREASE (typically 4 pixels total)\n * ```\n */\nexport function getHoverLineWidth(\n feature: StyledFeature,\n isHovered: boolean,\n): number {\n const baseWidth = getLineWidth(feature);\n return isHovered ? baseWidth + HOVER_WIDTH_INCREASE : baseWidth;\n}\n\n/**\n * Get highlight border/outline width.\n *\n * Calculates the line width for a selected/highlighted feature by adding a fixed\n * pixel increase to the base width. This makes selected features more prominent.\n *\n * @param feature - The styled feature to calculate width for\n * @returns The calculated line width in pixels (base width + HIGHLIGHT_WIDTH_INCREASE)\n *\n * @example\n * ```typescript\n * import { getHighlightLineWidth } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/display-style';\n * import type { StyledFeature } from '@accelint/map-toolkit/deckgl/shapes/shared/types';\n *\n * const feature: StyledFeature = {\n * properties: { styleProperties: { lineWidth: 2 } }\n * };\n *\n * const width = getHighlightLineWidth(feature);\n * // Returns: 2 + HIGHLIGHT_WIDTH_INCREASE (typically 5 pixels total)\n * ```\n */\nexport function getHighlightLineWidth(feature: StyledFeature): number {\n const baseWidth = getLineWidth(feature);\n return baseWidth + HIGHLIGHT_WIDTH_INCREASE;\n}\n\n/**\n * Scale the alpha channel of a raw RGBA color by OVERLAY_FILL_OPACITY.\n * Used by interaction overlay layers (hover, select, curtains) to sit at\n * a consistent opacity between the base layer (0.2) and fully solid (1.0).\n *\n * @param color - RGBA color tuple [r, g, b, a] (0-255)\n * @returns Color with alpha multiplied by OVERLAY_FILL_OPACITY\n * @example\n * ```typescript\n * applyOverlayOpacity([255, 128, 0, 200]);\n * // → [255, 128, 0, 50] (200 × 0.25)\n * ```\n */\nexport function applyOverlayOpacity(\n color: Rgba255Tuple,\n): [number, number, number, number] {\n return [\n color[0],\n color[1],\n color[2],\n Math.round(color[3] * OVERLAY_FILL_OPACITY),\n ];\n}\n\n/**\n * Get fill color for interaction overlay layers (hover, select).\n *\n * Returns the shape's fill color with alpha scaled by OVERLAY_FILL_OPACITY —\n * more opaque than the base-opacity main layer but not fully solid, so the\n * material brightness effect reads clearly without looking like a solid block.\n *\n * @param feature - The styled feature\n * @returns RGBA color with alpha multiplied by OVERLAY_FILL_OPACITY\n * @example\n * ```typescript\n * // Feature with fillColor [98, 166, 255, 255]\n * getOverlayFillColor(feature); // → [98, 166, 255, 64]\n * ```\n */\nexport function getOverlayFillColor(\n feature: StyledFeature,\n): [number, number, number, number] {\n return applyOverlayOpacity(getFillColor(feature));\n}\n\n/**\n * Brighten an RGBA color by multiplying RGB channels by a factor.\n * Alpha is preserved unchanged.\n *\n * @param color - RGBA color tuple [r, g, b, a] (0-255)\n * @param factor - Multiplier for RGB channels (e.g. 1.5 = 50% brighter)\n * @returns Brightened RGBA color, clamped to 0-255\n *\n * @example\n * ```typescript\n * const brighter = brightenColor([100, 150, 200, 255], 1.5);\n * // Returns: [150, 225, 255, 255] — RGB multiplied by 1.5, alpha unchanged\n * ```\n */\nexport function brightenColor(\n color: Rgba255Tuple,\n factor: number,\n): [number, number, number, number] {\n return [\n Math.min(255, Math.round(color[0] * factor)),\n Math.min(255, Math.round(color[1] * factor)),\n Math.min(255, Math.round(color[2] * factor)),\n color[3],\n ];\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8CA,SAAgB,kBACd,SACA,WACQ;CACR,MAAM,YAAY,aAAa,QAAQ;AACvC,QAAO,YAAY,YAAY,uBAAuB;;;;;;;;;;;;;;;;;;;;;;;;AAyBxD,SAAgB,sBAAsB,SAAgC;AAEpE,QADkB,aAAa,QAAQ,GACpB;;;;;;;;;;;;;;;AAgBrB,SAAgB,oBACd,OACkC;AAClC,QAAO;EACL,MAAM;EACN,MAAM;EACN,MAAM;EACN,KAAK,MAAM,MAAM,KAAK,qBAAqB;EAC5C;;;;;;;;;;;;;;;;;AAkBH,SAAgB,oBACd,SACkC;AAClC,QAAO,oBAAoB,aAAa,QAAQ,CAAC;;;;;;;;;;;;;;;;AAiBnD,SAAgB,cACd,OACA,QACkC;AAClC,QAAO;EACL,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK,OAAO,CAAC;EAC5C,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK,OAAO,CAAC;EAC5C,KAAK,IAAI,KAAK,KAAK,MAAM,MAAM,KAAK,OAAO,CAAC;EAC5C,MAAM;EACP"}
|