@accelint/map-toolkit 2.0.0 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (88) hide show
  1. package/CHANGELOG.md +50 -0
  2. package/README.md +8 -1
  3. package/catalog-info.yaml +9 -6
  4. package/dist/deckgl/base-map/index.d.ts +2 -2
  5. package/dist/deckgl/base-map/provider.d.ts +2 -2
  6. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.d.ts +144 -0
  7. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js +535 -0
  8. package/dist/deckgl/extensions/coffin-corner/coffin-corner-extension.js.map +1 -0
  9. package/dist/deckgl/extensions/coffin-corner/index.d.ts +17 -0
  10. package/dist/deckgl/extensions/coffin-corner/index.js +19 -0
  11. package/dist/deckgl/extensions/coffin-corner/store.d.ts +96 -0
  12. package/dist/deckgl/extensions/coffin-corner/store.js +173 -0
  13. package/dist/deckgl/extensions/coffin-corner/store.js.map +1 -0
  14. package/dist/deckgl/extensions/coffin-corner/types.d.ts +76 -0
  15. package/dist/deckgl/extensions/coffin-corner/types.js +27 -0
  16. package/dist/deckgl/extensions/coffin-corner/types.js.map +1 -0
  17. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.d.ts +81 -0
  18. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js +75 -0
  19. package/dist/deckgl/extensions/coffin-corner/use-coffin-corner.js.map +1 -0
  20. package/dist/deckgl/extensions/index.d.ts +15 -0
  21. package/dist/deckgl/extensions/index.js +16 -0
  22. package/dist/deckgl/index.d.ts +6 -1
  23. package/dist/deckgl/index.js +5 -1
  24. package/dist/deckgl/shapes/display-shape-layer/constants.js +6 -15
  25. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  26. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +31 -15
  27. package/dist/deckgl/shapes/display-shape-layer/index.js +97 -78
  28. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  29. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +8 -0
  30. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js +3 -48
  31. package/dist/deckgl/shapes/display-shape-layer/utils/icon-config.js.map +1 -1
  32. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js +53 -0
  33. package/dist/deckgl/shapes/display-shape-layer/utils/radius-label.js.map +1 -0
  34. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +7 -3
  35. package/dist/deckgl/shapes/draw-shape-layer/index.js +7 -3
  36. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -1
  37. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +4 -2
  38. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  39. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +3 -2
  40. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  41. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +5 -2
  42. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  43. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +5 -2
  44. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  45. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +7 -2
  46. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  47. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +2 -2
  48. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +7 -4
  49. package/dist/deckgl/shapes/edit-shape-layer/index.js +22 -8
  50. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  51. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +3 -2
  52. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  53. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +4 -2
  54. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  55. package/dist/deckgl/shapes/edit-shape-layer/store.js +15 -4
  56. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  57. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +4 -2
  58. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +1 -1
  59. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +7 -3
  60. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -1
  61. package/dist/deckgl/shapes/index.d.ts +3 -2
  62. package/dist/deckgl/shapes/index.js +2 -1
  63. package/dist/deckgl/shapes/shared/constants.js +1 -1
  64. package/dist/deckgl/shapes/shared/types.d.ts +11 -4
  65. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  66. package/dist/deckgl/shapes/shared/utils/duplicate-shape.d.ts +56 -0
  67. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js +131 -0
  68. package/dist/deckgl/shapes/shared/utils/duplicate-shape.js.map +1 -0
  69. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  70. package/dist/deckgl/shapes/shared/utils/layer-config.js +10 -7
  71. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -1
  72. package/dist/deckgl/symbol-layer/fiber.d.ts +3 -1
  73. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  74. package/dist/shared/units.d.ts +15 -56
  75. package/dist/shared/units.js +1 -52
  76. package/dist/shared/units.js.map +1 -1
  77. package/dist/viewport/index.d.ts +2 -3
  78. package/dist/viewport/index.js +1 -2
  79. package/dist/viewport/types.d.ts +8 -4
  80. package/dist/viewport/utils.d.ts +3 -3
  81. package/dist/viewport/utils.js +16 -8
  82. package/dist/viewport/utils.js.map +1 -1
  83. package/dist/viewport/viewport-size.d.ts +4 -3
  84. package/dist/viewport/viewport-size.js +2 -2
  85. package/dist/viewport/viewport-size.js.map +1 -1
  86. package/package.json +13 -6
  87. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js +0 -50
  88. package/dist/deckgl/shapes/display-shape-layer/utils/interaction.js.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"store.js","names":["DEFAULT_EDITING_STATE: EditingState"],"sources":["../../../../src/deckgl/shapes/edit-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'use client';\n\n/**\n * Edit Shape Store\n *\n * Manages editing state for shape modification.\n *\n * @example\n * ```typescript\n * import { editStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function EditControls({ mapId }) {\n * const { state, edit, save, cancel } = editStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Editing: {state.editingShape?.name ?? 'none'}</p>\n * <button onClick={save}>Save</button>\n * <button onClick={cancel}>Cancel</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { createLoggerDomain } from '@/shared/logger';\nimport { MapEvents } from '../../base-map/events';\nimport {\n isCircleShape,\n isEllipseShape,\n isPointShape,\n isRectangleShape,\n} from '../shared/types';\nimport {\n releaseModeAndCursor,\n requestCursorChange,\n requestModeChange,\n} from '../shared/utils/mode-utils';\nimport {\n EDIT_CURSOR_MAP,\n EDIT_SHAPE_LAYER_ID,\n EDIT_SHAPE_MODE,\n} from './constants';\nimport { EditShapeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { Feature } from 'geojson';\nimport type { MapEventType } from '../../base-map/types';\nimport type { Shape } from '../shared/types';\nimport type {\n EditShapeEvent,\n ShapeEditCanceledEvent,\n ShapeEditingEvent,\n ShapeUpdatedEvent,\n} from './events';\nimport type {\n EditFunction,\n EditingState,\n EditMode,\n EditShapeOptions,\n} from './types';\n\nconst logger = createLoggerDomain('[EditShapeLayer]');\n\n/**\n * Typed event bus instances\n */\nconst editShapeBus = Broadcast.getInstance<EditShapeEvent>();\nconst mapEventBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Default editing state\n */\nconst DEFAULT_EDITING_STATE: EditingState = {\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n};\n\n/**\n * Actions for edit shape store\n */\ntype EditShapeActions = {\n /** Start editing a shape */\n edit: EditFunction;\n /** Save the current edits */\n save: () => void;\n /** Cancel editing */\n cancel: () => void;\n};\n\n/**\n * Determine the appropriate edit mode for a shape type\n *\n * @param shape - The shape to determine the edit mode for\n * @returns The edit mode to use for this shape type\n */\nfunction getEditModeForShape(shape: Shape): EditMode {\n if (isPointShape(shape)) {\n return 'point-translate';\n }\n if (isCircleShape(shape)) {\n return 'circle-transform';\n }\n if (isEllipseShape(shape) || isRectangleShape(shape)) {\n return 'bounding-transform';\n }\n return 'vertex-transform';\n}\n\n/**\n * Start editing a shape\n */\nfunction startEditing(\n mapId: UniqueId,\n state: EditingState,\n shape: Shape,\n options: EditShapeOptions | undefined,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): void {\n // Prevent editing locked shapes\n if (shape.locked) {\n logger.warn(`Cannot edit locked shape: \"${shape.name}\"`);\n return;\n }\n\n // Already editing - cancel first\n if (state.editingShape) {\n cancelEditingInternal(mapId, state, notify, setState);\n }\n\n // Determine edit mode (can be overridden via options)\n const editMode = options?.mode ?? getEditModeForShape(shape);\n\n // Update state with new object reference\n setState({\n editingShape: shape,\n editMode,\n featureBeingEdited: shape.feature,\n });\n\n // Request map mode and cursor\n requestModeChange(mapId, EDIT_SHAPE_MODE, EDIT_SHAPE_LAYER_ID);\n const cursor = EDIT_CURSOR_MAP[editMode];\n requestCursorChange(mapId, cursor, EDIT_SHAPE_LAYER_ID);\n\n // Disable map panning during editing\n mapEventBus.emit(MapEvents.disablePan, { id: mapId });\n\n // Emit editing started event\n editShapeBus.emit(EditShapeEvents.editing, {\n shape,\n mapId,\n } as unknown as ShapeEditingEvent['payload']);\n\n notify();\n}\n\n/**\n * Save editing and create updated shape\n */\nfunction saveEditingInternal(\n mapId: UniqueId,\n state: EditingState,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): Shape | null {\n if (!(state.editingShape && state.featureBeingEdited)) {\n return null;\n }\n\n const originalShape = state.editingShape;\n const updatedFeature = state.featureBeingEdited;\n\n // Create updated shape with new geometry\n const updatedShape = {\n ...originalShape,\n feature: {\n ...updatedFeature,\n properties: {\n ...originalShape.feature.properties,\n ...updatedFeature.properties,\n },\n },\n lastUpdated: Date.now(),\n } as Shape;\n\n // Reset state\n setState({\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n });\n\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit shape updated event\n editShapeBus.emit(EditShapeEvents.updated, {\n shape: updatedShape,\n mapId,\n } as unknown as ShapeUpdatedEvent['payload']);\n\n notify();\n\n return updatedShape;\n}\n\n/**\n * Cancel the current editing operation\n */\nfunction cancelEditingInternal(\n mapId: UniqueId,\n state: EditingState,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): void {\n if (!state.editingShape) {\n return; // Nothing to cancel\n }\n\n const originalShape = state.editingShape;\n\n // Reset state\n setState({\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n });\n\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit canceled event\n editShapeBus.emit(EditShapeEvents.canceled, {\n shape: originalShape,\n mapId,\n } as unknown as ShapeEditCanceledEvent['payload']);\n\n notify();\n}\n\n/**\n * Edit shape store\n */\nexport const editStore = createMapStore<EditingState, EditShapeActions>({\n defaultState: { ...DEFAULT_EDITING_STATE },\n\n actions: (mapId, { get, set, notify }) => ({\n edit: (shape: Shape, options?: EditShapeOptions) => {\n startEditing(mapId, get(), shape, options, notify, set);\n },\n\n save: () => {\n saveEditingInternal(mapId, get(), notify, set);\n },\n\n cancel: () => {\n cancelEditingInternal(mapId, get(), notify, set);\n },\n }),\n\n // Note: EditShapeLayer is \"neutral\" regarding mode change authorization.\n // It doesn't auto-cancel or reject mode changes - those decisions are\n // left to UI components that can prompt the user.\n\n onCleanup: (mapId, state) => {\n // Cancel any active editing before cleanup\n if (state.editingShape) {\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit canceled event\n editShapeBus.emit(EditShapeEvents.canceled, {\n shape: state.editingShape,\n mapId,\n } as unknown as ShapeEditCanceledEvent['payload']);\n }\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current editing state for a mapId\n * Returns null if no store instance exists\n *\n * @param mapId - The map instance ID.\n * @returns The current editing state, or null if no store instance exists.\n */\nexport function getEditingState(mapId: UniqueId): EditingState | null {\n if (!editStore.exists(mapId)) {\n return null;\n }\n return editStore.get(mapId);\n}\n\n/**\n * Hook for editing state\n * @param mapId - The map instance ID.\n * @returns The current editing state and edit actions.\n *\n * @example\n * ```typescript\n * const { state, edit, save, cancel } = useEditingState(mapId);\n */\nexport function useEditingState(\n mapId: UniqueId,\n): { state: EditingState } & EditShapeActions {\n return editStore.use(mapId);\n}\n\n/**\n * Manually clear editing state for a specific mapId.\n * @param mapId - The map instance ID.\n */\nexport function clearEditingState(mapId: UniqueId): void {\n editStore.clear(mapId);\n}\n\n/**\n * Update feature from the layer component (called during drag operations)\n * @param mapId - The map instance ID.\n * @param feature - The updated GeoJSON feature from the editable layer.\n */\nexport function updateFeatureFromLayer(\n mapId: UniqueId,\n feature: Feature,\n): void {\n editStore.set(mapId, { featureBeingEdited: feature });\n}\n\n/**\n * Cancel editing (called by the layer component on ESC)\n * @param mapId - The map instance ID.\n */\nexport function cancelEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).cancel();\n}\n\n/**\n * Save editing (called by the layer component on Enter)\n * @param mapId - The map instance ID.\n */\nexport function saveEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).save();\n}\n\n/**\n * Enables map panning while editing by switching to view mode and storing the\n * current edit mode for restoration.\n *\n * No-op if no shape is currently being edited.\n *\n * @param mapId - The map instance ID.\n *\n * @example\n * ```typescript\n * enableEditPanning(mapId);\n * ```\n */\nexport function enableEditPanning(mapId: UniqueId): void {\n const state = editStore.get(mapId);\n if (state?.previousMode !== null || !state?.editingShape) {\n return;\n }\n\n editStore.set(mapId, { previousMode: state.editMode, editMode: 'view' });\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n requestCursorChange(mapId, 'grab', EDIT_SHAPE_LAYER_ID);\n}\n\n/**\n * Disables map panning and restores the previous edit mode.\n *\n * If no shape is being edited, clears the stored `previousMode` and returns.\n * Otherwise, restores the edit mode from `previousMode`, re-disables panning,\n * and sets the cursor back to the shape-appropriate edit cursor.\n *\n * @param mapId - The map instance ID.\n *\n * @example\n * ```typescript\n * disableEditPanning(mapId);\n * ```\n */\nexport function disableEditPanning(mapId: UniqueId): void {\n const state = editStore.get(mapId);\n\n if (!state?.editingShape) {\n editStore.set(mapId, { previousMode: null });\n return;\n }\n\n const previousMode = state?.previousMode;\n if (!previousMode) {\n editStore.set(mapId, { previousMode: null });\n return;\n }\n\n editStore.set(mapId, { editMode: previousMode, previousMode: null });\n mapEventBus.emit(MapEvents.disablePan, { id: mapId });\n requestCursorChange(\n mapId,\n EDIT_CURSOR_MAP[previousMode],\n EDIT_SHAPE_LAYER_ID,\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"store.js","names":["DEFAULT_EDITING_STATE: EditingState"],"sources":["../../../../src/deckgl/shapes/edit-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'use client';\n\n/**\n * Edit Shape Store\n *\n * Manages editing state for shape modification.\n *\n * @example\n * ```typescript\n * import { editStore } from '@accelint/map-toolkit/deckgl/shapes';\n *\n * function EditControls({ mapId }) {\n * const { state, edit, save, cancel } = editStore.use(mapId);\n *\n * return (\n * <div>\n * <p>Editing: {state.editingShape?.name ?? 'none'}</p>\n * <button onClick={save}>Save</button>\n * <button onClick={cancel}>Cancel</button>\n * </div>\n * );\n * }\n * ```\n */\n\nimport { Broadcast } from '@accelint/bus';\nimport { createMapStore } from '@/shared/create-map-store';\nimport { createLoggerDomain } from '@/shared/logger';\nimport { MapEvents } from '../../base-map/events';\nimport {\n isCircleShape,\n isEllipseShape,\n isPointShape,\n isRectangleShape,\n} from '../shared/types';\nimport {\n releaseModeAndCursor,\n requestCursorChange,\n requestModeChange,\n} from '../shared/utils/mode-utils';\nimport {\n EDIT_CURSOR_MAP,\n EDIT_SHAPE_LAYER_ID,\n EDIT_SHAPE_MODE,\n} from './constants';\nimport { EditShapeEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { Feature } from 'geojson';\nimport type { MapEventType } from '../../base-map/types';\nimport type { Shape } from '../shared/types';\nimport type {\n EditShapeEvent,\n ShapeEditCanceledEvent,\n ShapeEditingEvent,\n ShapeUpdatedEvent,\n} from './events';\nimport type {\n EditFunction,\n EditingState,\n EditMode,\n EditShapeOptions,\n} from './types';\n\nconst logger = createLoggerDomain('[EditShapeLayer]');\n\n/**\n * Typed event bus instances\n */\nconst editShapeBus = Broadcast.getInstance<EditShapeEvent>();\nconst mapEventBus = Broadcast.getInstance<MapEventType>();\n\n/**\n * Default editing state\n */\nconst DEFAULT_EDITING_STATE: EditingState = {\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n};\n\n/**\n * Actions for edit shape store\n */\ntype EditShapeActions = {\n /** Start editing a shape */\n edit: EditFunction;\n /** Save the current edits */\n save: () => void;\n /** Cancel editing */\n cancel: () => void;\n};\n\n/**\n * Determine the appropriate edit mode for a shape type\n *\n * @param shape - The shape to determine the edit mode for\n * @returns The edit mode to use for this shape type\n */\nfunction getEditModeForShape(shape: Shape): EditMode {\n if (isPointShape(shape)) {\n return 'point-translate';\n }\n if (isCircleShape(shape)) {\n return 'circle-transform';\n }\n if (isEllipseShape(shape) || isRectangleShape(shape)) {\n return 'bounding-transform';\n }\n return 'vertex-transform';\n}\n\n/**\n * Start editing a shape\n */\nfunction startEditing(\n mapId: UniqueId,\n state: EditingState,\n shape: Shape,\n options: EditShapeOptions | undefined,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): void {\n // Prevent editing locked shapes\n if (shape.locked) {\n logger.warn(`Cannot edit locked shape: \"${shape.name}\"`);\n return;\n }\n\n // Already editing - cancel first\n if (state.editingShape) {\n cancelEditingInternal(mapId, state, notify, setState);\n }\n\n // Determine edit mode (can be overridden via options)\n const editMode = options?.mode ?? getEditModeForShape(shape);\n\n // Update state with new object reference\n setState({\n editingShape: shape,\n editMode,\n featureBeingEdited: shape.feature,\n });\n\n // Request map mode and cursor\n requestModeChange(mapId, EDIT_SHAPE_MODE, EDIT_SHAPE_LAYER_ID);\n const cursor = EDIT_CURSOR_MAP[editMode];\n requestCursorChange(mapId, cursor, EDIT_SHAPE_LAYER_ID);\n\n // Disable map panning during editing\n mapEventBus.emit(MapEvents.disablePan, { id: mapId });\n\n // Emit editing started event\n editShapeBus.emit(EditShapeEvents.editing, {\n shape,\n mapId,\n } as unknown as ShapeEditingEvent['payload']);\n\n notify();\n}\n\n/**\n * Save editing and create updated shape\n */\nfunction saveEditingInternal(\n mapId: UniqueId,\n state: EditingState,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): Shape | null {\n if (!(state.editingShape && state.featureBeingEdited)) {\n return null;\n }\n\n const originalShape = state.editingShape;\n const updatedFeature = state.featureBeingEdited;\n\n // Create updated shape with new geometry\n const updatedShape = {\n ...originalShape,\n feature: {\n ...updatedFeature,\n properties: {\n ...originalShape.feature.properties,\n ...updatedFeature.properties,\n },\n },\n lastUpdated: Date.now(),\n } as Shape;\n\n // Reset state\n setState({\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n });\n\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit shape updated event\n editShapeBus.emit(EditShapeEvents.updated, {\n shape: updatedShape,\n mapId,\n } as unknown as ShapeUpdatedEvent['payload']);\n\n notify();\n\n return updatedShape;\n}\n\n/**\n * Cancel the current editing operation\n */\nfunction cancelEditingInternal(\n mapId: UniqueId,\n state: EditingState,\n notify: () => void,\n setState: (updates: Partial<EditingState>) => void,\n): void {\n if (!state.editingShape) {\n return; // Nothing to cancel\n }\n\n const originalShape = state.editingShape;\n\n // Reset state\n setState({\n editingShape: null,\n editMode: 'view',\n featureBeingEdited: null,\n previousMode: null,\n });\n\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit canceled event\n editShapeBus.emit(EditShapeEvents.canceled, {\n shape: originalShape,\n mapId,\n } as unknown as ShapeEditCanceledEvent['payload']);\n\n notify();\n}\n\n/**\n * Edit shape store\n */\nexport const editStore = createMapStore<EditingState, EditShapeActions>({\n defaultState: { ...DEFAULT_EDITING_STATE },\n\n actions: (mapId, { get, set, notify }) => ({\n edit: (shape: Shape, options?: EditShapeOptions) => {\n startEditing(mapId, get(), shape, options, notify, set);\n },\n\n save: () => {\n saveEditingInternal(mapId, get(), notify, set);\n },\n\n cancel: () => {\n cancelEditingInternal(mapId, get(), notify, set);\n },\n }),\n\n // Note: EditShapeLayer is \"neutral\" regarding mode change authorization.\n // It doesn't auto-cancel or reject mode changes - those decisions are\n // left to UI components that can prompt the user.\n\n onCleanup: (mapId, state) => {\n // Cancel any active editing before cleanup\n if (state.editingShape) {\n // Return to default mode and cursor\n releaseModeAndCursor(mapId, EDIT_SHAPE_LAYER_ID);\n\n // Re-enable map panning\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n\n // Emit canceled event\n editShapeBus.emit(EditShapeEvents.canceled, {\n shape: state.editingShape,\n mapId,\n } as unknown as ShapeEditCanceledEvent['payload']);\n }\n },\n});\n\n// =============================================================================\n// Convenience exports\n// =============================================================================\n\n/**\n * Get the current editing state for a mapId\n * Returns null if no store instance exists\n *\n * @param mapId - The map instance ID.\n * @returns The current editing state, or null if no store instance exists.\n */\nexport function getEditingState(mapId: UniqueId): EditingState | null {\n if (!editStore.exists(mapId)) {\n return null;\n }\n return editStore.get(mapId);\n}\n\n/**\n * Hook for editing state\n * @param mapId - The map instance ID.\n * @returns The current editing state and edit actions.\n *\n * @example\n * ```typescript\n * const { state, edit, save, cancel } = useEditingState(mapId);\n */\nexport function useEditingState(\n mapId: UniqueId,\n): { state: EditingState } & EditShapeActions {\n return editStore.use(mapId);\n}\n\n/**\n * Manually clear editing state for a specific mapId.\n * @param mapId - The map instance ID.\n */\nexport function clearEditingState(mapId: UniqueId): void {\n editStore.clear(mapId);\n}\n\n/**\n * Update the feature currently being edited.\n *\n * Called internally by the layer during drag operations, and also available\n * to consumers via the `useEditShape` hook for form-driven updates.\n *\n * @param mapId - The map instance ID.\n * @param feature - The updated GeoJSON feature to store as the live editing state.\n *\n * @example\n * ```typescript\n * // From a form input handler\n * const newGeometry = circle(center, radius, { units: 'kilometers' }).geometry;\n * updateFeature(mapId, { ...currentFeature, geometry: newGeometry });\n * ```\n */\nexport function updateFeature(mapId: UniqueId, feature: Feature): void {\n editStore.set(mapId, { featureBeingEdited: feature });\n}\n\n/**\n * Cancel editing (called by the layer component on ESC)\n * @param mapId - The map instance ID.\n */\nexport function cancelEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).cancel();\n}\n\n/**\n * Save editing (called by the layer component on Enter)\n * @param mapId - The map instance ID.\n */\nexport function saveEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).save();\n}\n\n/**\n * Enables map panning while editing by switching to view mode and storing the\n * current edit mode for restoration.\n *\n * No-op if no shape is currently being edited.\n *\n * @param mapId - The map instance ID.\n *\n * @example\n * ```typescript\n * enableEditPanning(mapId);\n * ```\n */\nexport function enableEditPanning(mapId: UniqueId): void {\n const state = editStore.get(mapId);\n if (state?.previousMode !== null || !state?.editingShape) {\n return;\n }\n\n editStore.set(mapId, { previousMode: state.editMode, editMode: 'view' });\n mapEventBus.emit(MapEvents.enablePan, { id: mapId });\n requestCursorChange(mapId, 'grab', EDIT_SHAPE_LAYER_ID);\n}\n\n/**\n * Disables map panning and restores the previous edit mode.\n *\n * If no shape is being edited, clears the stored `previousMode` and returns.\n * Otherwise, restores the edit mode from `previousMode`, re-disables panning,\n * and sets the cursor back to the shape-appropriate edit cursor.\n *\n * @param mapId - The map instance ID.\n *\n * @example\n * ```typescript\n * disableEditPanning(mapId);\n * ```\n */\nexport function disableEditPanning(mapId: UniqueId): void {\n const state = editStore.get(mapId);\n\n if (!state?.editingShape) {\n editStore.set(mapId, { previousMode: null });\n return;\n }\n\n const previousMode = state?.previousMode;\n if (!previousMode) {\n editStore.set(mapId, { previousMode: null });\n return;\n }\n\n editStore.set(mapId, { editMode: previousMode, previousMode: null });\n mapEventBus.emit(MapEvents.disablePan, { id: mapId });\n requestCursorChange(\n mapId,\n EDIT_CURSOR_MAP[previousMode],\n EDIT_SHAPE_LAYER_ID,\n );\n}\n"],"mappings":"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"}
@@ -10,10 +10,10 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { DistanceUnitAbbreviation } from "../../../shared/units.js";
14
13
  import { Shape } from "../shared/types.js";
15
14
  import { UniqueId } from "@accelint/core";
16
15
  import { KeyOption } from "@accelint/hotkey-manager";
16
+ import { DistanceUnitSymbol } from "@accelint/constants/units";
17
17
  import { Feature } from "geojson";
18
18
  import { NonEmptyArray } from "@accelint/hotkey-manager/types/non-empty-array";
19
19
 
@@ -69,6 +69,8 @@ type UseEditShapeReturn = {
69
69
  save: () => void;
70
70
  /** Cancel editing and revert to original shape */
71
71
  cancel: () => void;
72
+ /** Update the feature being edited (e.g., from a form). No-op when not editing. */
73
+ updateFeature: (feature: Feature) => void;
72
74
  /** Whether currently in editing mode */
73
75
  isEditing: boolean;
74
76
  /** The shape currently being edited (null if not editing) */
@@ -87,7 +89,7 @@ type EditShapeLayerProps = {
87
89
  */
88
90
  mapId?: UniqueId;
89
91
  /** Distance unit for tooltip measurements (defaults to 'km') */
90
- unit?: DistanceUnitAbbreviation;
92
+ unit?: DistanceUnitSymbol;
91
93
  /** Configuration for hotkeys in EditShapesLayer */
92
94
  hotkeyConfig?: EditShapeHotkeyConfig;
93
95
  };
@@ -24,7 +24,7 @@ import { UniqueId } from "@accelint/core";
24
24
  *
25
25
  * @param mapId - Optional map instance ID. If not provided, will use the ID from `MapContext`.
26
26
  * @param options - Optional callbacks for onUpdate and onCancel events
27
- * @returns Editing state, edit/save/cancel functions, and convenience flags
27
+ * @returns Editing state, edit/save/cancel/updateFeature functions, and convenience flags
28
28
  * @throws Error if no `mapId` is provided and hook is used outside of `MapProvider`
29
29
  *
30
30
  * @example
@@ -14,7 +14,7 @@
14
14
  'use client';
15
15
 
16
16
  import { EditShapeEvents } from "./events.js";
17
- import { editStore } from "./store.js";
17
+ import { editStore, updateFeature } from "./store.js";
18
18
  import { MapContext } from "../../base-map/provider.js";
19
19
  import { useContext, useMemo } from "react";
20
20
  import { useBus } from "@accelint/bus/react";
@@ -29,7 +29,7 @@ import { useBus } from "@accelint/bus/react";
29
29
  *
30
30
  * @param mapId - Optional map instance ID. If not provided, will use the ID from `MapContext`.
31
31
  * @param options - Optional callbacks for onUpdate and onCancel events
32
- * @returns Editing state, edit/save/cancel functions, and convenience flags
32
+ * @returns Editing state, edit/save/cancel/updateFeature functions, and convenience flags
33
33
  * @throws Error if no `mapId` is provided and hook is used outside of `MapProvider`
34
34
  *
35
35
  * @example
@@ -99,13 +99,17 @@ function useEditShape(mapId, options) {
99
99
  edit,
100
100
  save,
101
101
  cancel,
102
+ updateFeature: (feature) => {
103
+ if (editingState?.editingShape) updateFeature(actualId, feature);
104
+ },
102
105
  isEditing: !!editingState?.editingShape,
103
106
  editingShape: editingState?.editingShape ?? null
104
107
  }), [
105
108
  editingState,
106
109
  edit,
107
110
  save,
108
- cancel
111
+ cancel,
112
+ actualId
109
113
  ]);
110
114
  }
111
115
 
@@ -1 +1 @@
1
- {"version":3,"file":"use-edit-shape.js","names":[],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/use-edit-shape.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 'client-only';\nimport { useBus } from '@accelint/bus/react';\nimport { useContext, useMemo } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport { EditShapeEvents } from './events';\nimport { editStore } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { EditShapeEvent } from './events';\nimport type { UseEditShapeOptions, UseEditShapeReturn } from './types';\n\n/**\n * Hook to access the shape editing state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to editing state changes,\n * providing concurrent-safe state updates. Uses a fan-out pattern where\n * a single bus listener per map instance notifies N React component subscribers.\n *\n * @param mapId - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @param options - Optional callbacks for onUpdate and onCancel events\n * @returns Editing state, edit/save/cancel functions, and convenience flags\n * @throws Error if no `mapId` is provided and hook is used outside of `MapProvider`\n *\n * @example\n * ```tsx\n * // Inside MapProvider (within BaseMap children) - uses context\n * function ShapeEditor() {\n * const { edit, save, cancel, isEditing, editingShape } = useEditShape(undefined, {\n * onUpdate: (shape) => {\n * console.log('Shape updated:', shape);\n * setShapes(prev => prev.map(s => s.id === shape.id ? shape : s));\n * },\n * onCancel: (shape) => {\n * console.log('Editing canceled:', shape.name);\n * },\n * });\n *\n * return (\n * <div>\n * {selectedShape && !isEditing && (\n * <button onClick={() => edit(selectedShape)}>\n * Edit Shape\n * </button>\n * )}\n * {isEditing && (\n * <>\n * <button onClick={save}>Save</button>\n * <button onClick={cancel}>Cancel</button>\n * </>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass mapId directly\n * function ExternalEditControl({ mapId, shape }: { mapId: UniqueId; shape: Shape }) {\n * const { edit, isEditing } = useEditShape(mapId);\n *\n * return (\n * <button\n * onClick={() => edit(shape)}\n * disabled={isEditing}\n * >\n * Edit\n * </button>\n * );\n * }\n * ```\n */\nexport function useEditShape(\n mapId?: UniqueId,\n options?: UseEditShapeOptions,\n): UseEditShapeReturn {\n const contextId = useContext(MapContext);\n const actualId = mapId ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useEditShape requires either a mapId parameter or to be used within a MapProvider',\n );\n }\n\n const { onUpdate, onCancel } = options ?? {};\n\n // Use the v2 store API directly\n const { state: editingState, edit, save, cancel } = editStore.use(actualId);\n\n // Listen for completion/cancellation events to trigger callbacks\n // useOn handles cleanup automatically and uses useEffectEvent for stable callbacks\n const { useOn } = useBus<EditShapeEvent>();\n\n useOn(EditShapeEvents.updated, (event) => {\n if (event.payload.mapId === actualId && onUpdate) {\n onUpdate(event.payload.shape);\n }\n });\n\n useOn(EditShapeEvents.canceled, (event) => {\n if (event.payload.mapId === actualId && onCancel) {\n onCancel(event.payload.shape);\n }\n });\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n editingState,\n edit,\n save,\n cancel,\n isEditing: !!editingState?.editingShape,\n editingShape: editingState?.editingShape ?? null,\n }),\n [editingState, edit, save, cancel],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,SAAgB,aACd,OACA,SACoB;CACpB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,SAAS;AAE1B,KAAI,CAAC,SACH,OAAM,IAAI,MACR,oFACD;CAGH,MAAM,EAAE,UAAU,aAAa,WAAW,EAAE;CAG5C,MAAM,EAAE,OAAO,cAAc,MAAM,MAAM,WAAW,UAAU,IAAI,SAAS;CAI3E,MAAM,EAAE,mBAAU,QAAwB;AAE1C,SAAM,gBAAgB,UAAU,UAAU;AACxC,MAAI,MAAM,QAAQ,UAAU,YAAY,SACtC,UAAS,MAAM,QAAQ,MAAM;GAE/B;AAEF,SAAM,gBAAgB,WAAW,UAAU;AACzC,MAAI,MAAM,QAAQ,UAAU,YAAY,SACtC,UAAS,MAAM,QAAQ,MAAM;GAE/B;AAGF,QAAO,eACE;EACL;EACA;EACA;EACA;EACA,WAAW,CAAC,CAAC,cAAc;EAC3B,cAAc,cAAc,gBAAgB;EAC7C,GACD;EAAC;EAAc;EAAM;EAAM;EAAO,CACnC"}
1
+ {"version":3,"file":"use-edit-shape.js","names":[],"sources":["../../../../src/deckgl/shapes/edit-shape-layer/use-edit-shape.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 'client-only';\nimport { useBus } from '@accelint/bus/react';\nimport { useContext, useMemo } from 'react';\nimport { MapContext } from '../../base-map/provider';\nimport { EditShapeEvents } from './events';\nimport { editStore, updateFeature as storeUpdateFeature } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type { EditShapeEvent } from './events';\nimport type { UseEditShapeOptions, UseEditShapeReturn } from './types';\n\n/**\n * Hook to access the shape editing state and actions.\n *\n * This hook uses `useSyncExternalStore` to subscribe to editing state changes,\n * providing concurrent-safe state updates. Uses a fan-out pattern where\n * a single bus listener per map instance notifies N React component subscribers.\n *\n * @param mapId - Optional map instance ID. If not provided, will use the ID from `MapContext`.\n * @param options - Optional callbacks for onUpdate and onCancel events\n * @returns Editing state, edit/save/cancel/updateFeature functions, and convenience flags\n * @throws Error if no `mapId` is provided and hook is used outside of `MapProvider`\n *\n * @example\n * ```tsx\n * // Inside MapProvider (within BaseMap children) - uses context\n * function ShapeEditor() {\n * const { edit, save, cancel, isEditing, editingShape } = useEditShape(undefined, {\n * onUpdate: (shape) => {\n * console.log('Shape updated:', shape);\n * setShapes(prev => prev.map(s => s.id === shape.id ? shape : s));\n * },\n * onCancel: (shape) => {\n * console.log('Editing canceled:', shape.name);\n * },\n * });\n *\n * return (\n * <div>\n * {selectedShape && !isEditing && (\n * <button onClick={() => edit(selectedShape)}>\n * Edit Shape\n * </button>\n * )}\n * {isEditing && (\n * <>\n * <button onClick={save}>Save</button>\n * <button onClick={cancel}>Cancel</button>\n * </>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * ```tsx\n * // Outside MapProvider - pass mapId directly\n * function ExternalEditControl({ mapId, shape }: { mapId: UniqueId; shape: Shape }) {\n * const { edit, isEditing } = useEditShape(mapId);\n *\n * return (\n * <button\n * onClick={() => edit(shape)}\n * disabled={isEditing}\n * >\n * Edit\n * </button>\n * );\n * }\n * ```\n */\nexport function useEditShape(\n mapId?: UniqueId,\n options?: UseEditShapeOptions,\n): UseEditShapeReturn {\n const contextId = useContext(MapContext);\n const actualId = mapId ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useEditShape requires either a mapId parameter or to be used within a MapProvider',\n );\n }\n\n const { onUpdate, onCancel } = options ?? {};\n\n // Use the v2 store API directly\n const { state: editingState, edit, save, cancel } = editStore.use(actualId);\n\n // Listen for completion/cancellation events to trigger callbacks\n // useOn handles cleanup automatically and uses useEffectEvent for stable callbacks\n const { useOn } = useBus<EditShapeEvent>();\n\n useOn(EditShapeEvents.updated, (event) => {\n if (event.payload.mapId === actualId && onUpdate) {\n onUpdate(event.payload.shape);\n }\n });\n\n useOn(EditShapeEvents.canceled, (event) => {\n if (event.payload.mapId === actualId && onCancel) {\n onCancel(event.payload.shape);\n }\n });\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n editingState,\n edit,\n save,\n cancel,\n updateFeature: (feature) => {\n if (editingState?.editingShape) {\n storeUpdateFeature(actualId, feature);\n }\n },\n isEditing: !!editingState?.editingShape,\n editingShape: editingState?.editingShape ?? null,\n }),\n [editingState, edit, save, cancel, actualId],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqFA,SAAgB,aACd,OACA,SACoB;CACpB,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,SAAS;AAE1B,KAAI,CAAC,SACH,OAAM,IAAI,MACR,oFACD;CAGH,MAAM,EAAE,UAAU,aAAa,WAAW,EAAE;CAG5C,MAAM,EAAE,OAAO,cAAc,MAAM,MAAM,WAAW,UAAU,IAAI,SAAS;CAI3E,MAAM,EAAE,mBAAU,QAAwB;AAE1C,SAAM,gBAAgB,UAAU,UAAU;AACxC,MAAI,MAAM,QAAQ,UAAU,YAAY,SACtC,UAAS,MAAM,QAAQ,MAAM;GAE/B;AAEF,SAAM,gBAAgB,WAAW,UAAU;AACzC,MAAI,MAAM,QAAQ,UAAU,YAAY,SACtC,UAAS,MAAM,QAAQ,MAAM;GAE/B;AAGF,QAAO,eACE;EACL;EACA;EACA;EACA;EACA,gBAAgB,YAAY;AAC1B,OAAI,cAAc,aAChB,eAAmB,UAAU,QAAQ;;EAGzC,WAAW,CAAC,CAAC,cAAc;EAC3B,cAAc,cAAc,gBAAgB;EAC7C,GACD;EAAC;EAAc;EAAM;EAAM;EAAQ;EAAS,CAC7C"}
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { CircleFeatureProperties, CircleProperties, CircleRadius, CircleShape, EllipseFeatureProperties, EllipseProperties, EllipseShape, LineStringShape, PointShape, PolygonShape, RectangleShape, Shape, ShapeFeature, ShapeFeatureProperties, ShapeFeatureType, ShapeId, StyleProperties, StyledFeature, StyledFeatureProperties, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape } from "./shared/types.js";
13
+ import { CircleFeatureProperties, CircleProperties, CircleRadius, CircleShape, EllipseFeatureProperties, EllipseProperties, EllipseShape, GeoPosition, LineStringShape, PointShape, PolygonShape, RectangleShape, Shape, ShapeFeature, ShapeFeatureProperties, ShapeFeatureType, ShapeId, StyleProperties, StyledFeature, StyledFeatureProperties, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape } from "./shared/types.js";
14
14
  import { CardinalLabelCoordinateAnchor, LabelHorizontalPosition, LabelPosition2d, LabelPositionOptions, LabelVerticalPosition } from "./display-shape-layer/utils/labels.js";
15
15
  import { DisplayShapeLayerProps, ShowLabelsMode } from "./display-shape-layer/types.js";
16
16
  import { DisplayShapeLayer } from "./display-shape-layer/index.js";
@@ -25,5 +25,6 @@ import { EditShapeLayer } from "./edit-shape-layer/index.js";
25
25
  import { useEditShape } from "./edit-shape-layer/use-edit-shape.js";
26
26
  import { BASE_FILL_OPACITY, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_STYLE_PROPERTIES, LINE_PATTERNS, LINE_WIDTHS, SHAPE_LAYER_IDS } from "./shared/constants.js";
27
27
  import { ShapeEventType, ShapeEvents } from "./shared/events.js";
28
+ import { DuplicateShapeOptions, duplicateShape } from "./shared/utils/duplicate-shape.js";
28
29
  import { getDashArray, getFillColor, getLineColor, getLineWidth, normalizeColor } from "./shared/utils/style-utils.js";
29
- export { BASE_FILL_OPACITY, type CardinalLabelCoordinateAnchor, type CircleFeatureProperties, type CircleProperties, type CircleRadius, type CircleShape, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_STYLE_PROPERTIES, DisplayShapeLayer, type DisplayShapeLayerProps, type DrawShapeEvent, type DrawShapeEventType, DrawShapeEvents, DrawShapeLayer, type DrawShapeLayerProps, type DrawShapeOptions, type DrawingState, type EditMode, type EditShapeEvent, type EditShapeEventType, EditShapeEvents, EditShapeLayer, type EditShapeLayerProps, type EditShapeOptions, type EditingState, type EllipseFeatureProperties, type EllipseProperties, type EllipseShape, LINE_PATTERNS, LINE_WIDTHS, type LabelHorizontalPosition, type LabelPosition2d, type LabelPositionOptions, type LabelVerticalPosition, type LineStringShape, type PointShape, type PolygonShape, type RectangleShape, SHAPE_LAYER_IDS, type Shape, type ShapeDrawCanceledEvent, type ShapeDrawingEvent, type ShapeDrawnEvent, type ShapeEditCanceledEvent, type ShapeEditingEvent, type ShapeEventType, ShapeEvents, type ShapeFeature, type ShapeFeatureProperties, ShapeFeatureType, type ShapeId, type ShapeUpdatedEvent, type ShowLabelsMode, type StyleProperties, type StyledFeature, type StyledFeatureProperties, type UseDrawShapeOptions, type UseDrawShapeReturn, type UseEditShapeOptions, type UseEditShapeReturn, type UseSelectShapeReturn, getDashArray, getFillColor, getLineColor, getLineWidth, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape, normalizeColor, useDrawShape, useEditShape, useSelectShape };
30
+ export { BASE_FILL_OPACITY, type CardinalLabelCoordinateAnchor, type CircleFeatureProperties, type CircleProperties, type CircleRadius, type CircleShape, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_STYLE_PROPERTIES, DisplayShapeLayer, type DisplayShapeLayerProps, type DrawShapeEvent, type DrawShapeEventType, DrawShapeEvents, DrawShapeLayer, type DrawShapeLayerProps, type DrawShapeOptions, type DrawingState, type DuplicateShapeOptions, type EditMode, type EditShapeEvent, type EditShapeEventType, EditShapeEvents, EditShapeLayer, type EditShapeLayerProps, type EditShapeOptions, type EditingState, type EllipseFeatureProperties, type EllipseProperties, type EllipseShape, type GeoPosition, LINE_PATTERNS, LINE_WIDTHS, type LabelHorizontalPosition, type LabelPosition2d, type LabelPositionOptions, type LabelVerticalPosition, type LineStringShape, type PointShape, type PolygonShape, type RectangleShape, SHAPE_LAYER_IDS, type Shape, type ShapeDrawCanceledEvent, type ShapeDrawingEvent, type ShapeDrawnEvent, type ShapeEditCanceledEvent, type ShapeEditingEvent, type ShapeEventType, ShapeEvents, type ShapeFeature, type ShapeFeatureProperties, ShapeFeatureType, type ShapeId, type ShapeUpdatedEvent, type ShowLabelsMode, type StyleProperties, type StyledFeature, type StyledFeatureProperties, type UseDrawShapeOptions, type UseDrawShapeReturn, type UseEditShapeOptions, type UseEditShapeReturn, type UseSelectShapeReturn, duplicateShape, getDashArray, getFillColor, getLineColor, getLineWidth, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape, normalizeColor, useDrawShape, useEditShape, useSelectShape };
@@ -23,5 +23,6 @@ import { DrawShapeLayer } from "./draw-shape-layer/index.js";
23
23
  import { useDrawShape } from "./draw-shape-layer/use-draw-shape.js";
24
24
  import { EditShapeLayer } from "./edit-shape-layer/index.js";
25
25
  import { useEditShape } from "./edit-shape-layer/use-edit-shape.js";
26
+ import { duplicateShape } from "./shared/utils/duplicate-shape.js";
26
27
 
27
- export { BASE_FILL_OPACITY, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_STYLE_PROPERTIES, DisplayShapeLayer, DrawShapeEvents, DrawShapeLayer, EditShapeEvents, EditShapeLayer, LINE_PATTERNS, LINE_WIDTHS, SHAPE_LAYER_IDS, ShapeEvents, ShapeFeatureType, getDashArray, getFillColor, getLineColor, getLineWidth, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape, normalizeColor, useDrawShape, useEditShape, useSelectShape };
28
+ export { BASE_FILL_OPACITY, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_STYLE_PROPERTIES, DisplayShapeLayer, DrawShapeEvents, DrawShapeLayer, EditShapeEvents, EditShapeLayer, LINE_PATTERNS, LINE_WIDTHS, SHAPE_LAYER_IDS, ShapeEvents, ShapeFeatureType, duplicateShape, getDashArray, getFillColor, getLineColor, getLineWidth, isCircleShape, isEllipseShape, isLineStringShape, isPointShape, isPolygonShape, isRectangleShape, normalizeColor, useDrawShape, useEditShape, useSelectShape };
@@ -309,5 +309,5 @@ const COMPLETION_EDIT_TYPES = new Set([
309
309
  ]);
310
310
 
311
311
  //#endregion
312
- export { BASE_FILL_OPACITY, COMPLETION_EDIT_TYPES, CONTINUOUS_EDIT_TYPES, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_LINE_WIDTH, DEFAULT_STYLE_PROPERTIES, DEFAULT_TENTATIVE_COLORS, EDITABLE_LAYER_SUBLAYER_PROPS, EMPTY_FEATURE_COLLECTION, HIGHLIGHT_WIDTH_INCREASE, HOVER_WIDTH_INCREASE, LINE_PATTERNS, LINE_WIDTHS, SHAPE_LAYER_IDS, formatCircleTooltip, formatDistanceTooltip, formatEllipseTooltip, formatRectangleTooltip };
312
+ export { BASE_FILL_OPACITY, COMPLETION_EDIT_TYPES, CONTINUOUS_EDIT_TYPES, DASH_ARRAYS, DEFAULT_COLORS, DEFAULT_EDIT_HANDLE_COLOR, DEFAULT_EDIT_HANDLE_OUTLINE_COLOR, DEFAULT_LINE_WIDTH, DEFAULT_STYLE_PROPERTIES, DEFAULT_TENTATIVE_COLORS, EDITABLE_LAYER_SUBLAYER_PROPS, EMPTY_FEATURE_COLLECTION, HIGHLIGHT_WIDTH_INCREASE, HOVER_WIDTH_INCREASE, LINE_PATTERNS, LINE_WIDTHS, SHAPE_LAYER_IDS, formatCircleTooltip, formatDistance, formatDistanceTooltip, formatEllipseTooltip, formatRectangleTooltip };
313
313
  //# sourceMappingURL=constants.js.map
@@ -10,9 +10,9 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
 
13
- import { DistanceUnit } from "../../../shared/units.js";
14
13
  import { UniqueId } from "@accelint/core";
15
14
  import { Color } from "@deck.gl/core";
15
+ import { DistanceUnit } from "@accelint/constants/units";
16
16
  import { Feature, Geometry, GeometryCollection, LineString, MultiLineString, MultiPoint, MultiPolygon, Point, Polygon } from "geojson";
17
17
 
18
18
  //#region src/deckgl/shapes/shared/types.d.ts
@@ -81,13 +81,20 @@ type StyleProperties = {
81
81
  /** Optional custom label coordinate anchor (position along geometry) */
82
82
  labelCoordinateAnchor?: 'center' | 'start' | 'middle' | 'end' | 'top' | 'right' | 'bottom' | 'left';
83
83
  };
84
+ /**
85
+ * Geographic position as [longitude, latitude] with optional elevation.
86
+ *
87
+ * Similar to GeoJSON's `Position` (`number[]`) but with stricter typing
88
+ * that guarantees at least two elements.
89
+ */
90
+ type GeoPosition = [number, number] | [number, number, number];
84
91
  /**
85
92
  * Circle-specific properties for precise rendering
86
93
  * Stored alongside the polygon approximation
87
94
  */
88
95
  type CircleProperties = {
89
96
  /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */
90
- center: [number, number, number?];
97
+ center: GeoPosition;
91
98
  /** Radius with value and units */
92
99
  radius: {
93
100
  /** Radius value */
@@ -102,7 +109,7 @@ type CircleProperties = {
102
109
  */
103
110
  type EllipseProperties = {
104
111
  /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */
105
- center: [number, number, number?];
112
+ center: GeoPosition;
106
113
  /** X semi-axis (horizontal radius) with value and units */
107
114
  xSemiAxis: {
108
115
  /** X semi-axis value */
@@ -434,5 +441,5 @@ declare function isLineGeometry(geometry: Geometry): geometry is LineString | Mu
434
441
  */
435
442
  declare function isPointGeometry(geometry: Geometry): geometry is Point | MultiPoint;
436
443
  //#endregion
437
- export { CircleFeatureProperties, CircleProperties, CircleRadius, CircleShape, EllipseFeatureProperties, EllipseProperties, EllipseShape, LinePattern, LineStringShape, LineWidth, PointShape, PolygonShape, RectangleShape, Shape, ShapeFeature, ShapeFeatureProperties, ShapeFeatureType, ShapeId, StyleProperties, StyledFeature, StyledFeatureProperties, isCircleShape, isEllipseShape, isGeometryCollectionType, isLineGeometry, isLineStringShape, isLineStringType, isMultiLineStringType, isMultiPointType, isMultiPolygonType, isPointGeometry, isPointShape, isPointType, isPolygonGeometry, isPolygonShape, isPolygonType, isRectangleShape };
444
+ export { CircleFeatureProperties, CircleProperties, CircleRadius, CircleShape, EllipseFeatureProperties, EllipseProperties, EllipseShape, GeoPosition, LinePattern, LineStringShape, LineWidth, PointShape, PolygonShape, RectangleShape, Shape, ShapeFeature, ShapeFeatureProperties, ShapeFeatureType, ShapeId, StyleProperties, StyledFeature, StyledFeatureProperties, isCircleShape, isEllipseShape, isGeometryCollectionType, isLineGeometry, isLineStringShape, isLineStringType, isMultiLineStringType, isMultiPointType, isMultiPolygonType, isPointGeometry, isPointShape, isPointType, isPolygonGeometry, isPolygonShape, isPolygonType, isRectangleShape };
438
445
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.js","names":[],"sources":["../../../../src/deckgl/shapes/shared/types.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 type { UniqueId } from '@accelint/core';\nimport type { Color } from '@deck.gl/core';\nimport type {\n Feature,\n Geometry,\n GeometryCollection,\n LineString,\n MultiLineString,\n MultiPoint,\n MultiPolygon,\n Point,\n Polygon,\n} from 'geojson';\nimport type { DistanceUnit } from '@/shared/units';\n\n/**\n * Supported shape types\n */\nexport const ShapeFeatureType = {\n Circle: 'Circle',\n Ellipse: 'Ellipse',\n Polygon: 'Polygon',\n Rectangle: 'Rectangle',\n LineString: 'LineString',\n Point: 'Point',\n} as const;\n\n/** Union of all supported shape feature type string literals. */\nexport type ShapeFeatureType =\n (typeof ShapeFeatureType)[keyof typeof ShapeFeatureType];\n\n/**\n * Shape ID type - uses UniqueId from core\n */\nexport type ShapeId = UniqueId;\n\n/**\n * Border/outline width options (in pixels).\n * Controls the width of shape outlines.\n */\nexport type LineWidth = 1 | 2 | 4 | 8;\n\n/**\n * Border/outline pattern options.\n * Controls how shape outlines are rendered.\n */\nexport type LinePattern = 'solid' | 'dashed' | 'dotted';\n\n/**\n * Style properties for rendering shapes\n */\nexport type StyleProperties = {\n /** Fill color as RGBA array [r, g, b, a] where each value is 0-255 */\n fillColor: Color;\n /** Border/outline color as RGBA array [r, g, b, a] where each value is 0-255 */\n lineColor: Color;\n /** Border/outline width in pixels */\n lineWidth: LineWidth;\n /** Border/outline pattern (solid, dashed, or dotted) */\n linePattern: LinePattern;\n /** Optional icon properties for Point geometries */\n icon?: {\n /** Icon atlas URL or data */\n atlas?: string;\n /** Icon mapping (name to position in atlas) */\n mapping?: Record<\n string,\n { x: number; y: number; width: number; height: number; mask?: boolean }\n >;\n /** Icon name to use from mapping */\n name?: string;\n /** Icon size in pixels */\n size?: number;\n };\n /** Optional custom label pixel offset [x, y] */\n labelOffset?: [number, number];\n /** Optional custom label vertical anchor */\n labelVerticalAnchor?: 'top' | 'middle' | 'bottom';\n /** Optional custom label horizontal anchor */\n labelHorizontalAnchor?: 'left' | 'center' | 'right';\n /** Optional custom label coordinate anchor (position along geometry) */\n labelCoordinateAnchor?:\n | 'center'\n | 'start'\n | 'middle'\n | 'end'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n};\n\n/**\n * Circle-specific properties for precise rendering\n * Stored alongside the polygon approximation\n */\nexport type CircleProperties = {\n /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */\n center: [number, number, number?];\n /** Radius with value and units */\n radius: {\n /** Radius value */\n value: number;\n /** Units for the radius measurement */\n units: DistanceUnit;\n };\n};\n\n/**\n * Ellipse-specific properties for precise rendering\n * Stored alongside the polygon approximation\n */\nexport type EllipseProperties = {\n /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */\n center: [number, number, number?];\n /** X semi-axis (horizontal radius) with value and units */\n xSemiAxis: {\n /** X semi-axis value */\n value: number;\n /** Units for the measurement */\n units: DistanceUnit;\n };\n /** Y semi-axis (vertical radius) with value and units */\n ySemiAxis: {\n /** Y semi-axis value */\n value: number;\n /** Units for the measurement */\n units: DistanceUnit;\n };\n /** Rotation angle in degrees */\n angle: number;\n};\n\n/**\n * Properties for styled features.\n *\n * Note: circleProperties and ellipseProperties are optional at the type level\n * but are guaranteed to be present for their respective shape types.\n * Use the type guards (isCircleShape, isEllipseShape) for type narrowing.\n */\nexport type StyledFeatureProperties = {\n /** Style properties for rendering */\n styleProperties: StyleProperties;\n /** Shape ID for correlation */\n shapeId?: ShapeId;\n /** Circle properties (present for Circle shapes) */\n circleProperties?: CircleProperties;\n /** Ellipse properties (present for Ellipse shapes) */\n ellipseProperties?: EllipseProperties;\n /** Minimum elevation in meters (optional) */\n minElevation?: number;\n /** Maximum elevation in meters (optional) */\n maxElevation?: number;\n};\n\n/**\n * Feature properties for Circle shapes (circleProperties required).\n * Used by CircleShape for better type narrowing.\n */\nexport type CircleFeatureProperties = StyledFeatureProperties & {\n /** Circle properties (required for Circle shapes) */\n circleProperties: CircleProperties;\n};\n\n/**\n * Feature properties for Ellipse shapes (ellipseProperties required).\n * Used by EllipseShape for better type narrowing.\n */\nexport type EllipseFeatureProperties = StyledFeatureProperties & {\n /** Ellipse properties (required for Ellipse shapes) */\n ellipseProperties: EllipseProperties;\n};\n\n/**\n * GeoJSON Feature with style properties\n */\nexport type StyledFeature = Feature & {\n properties: StyledFeatureProperties;\n};\n\n/**\n * Base shape properties shared by all shapes.\n */\ntype BaseShape = {\n /** Unique identifier */\n id: ShapeId;\n /** Full shape name used internally and in UI */\n name: string;\n /**\n * Optional short display label shown on the map\n * If not provided, the `name` property will be used instead\n * Useful for showing abbreviated text on the map (e.g., \"NYC\" vs \"New York City Office\")\n */\n label?: string;\n /** GeoJSON feature with geometry and style properties */\n feature: ShapeFeature;\n /** UTC timestamp (only set when saved) */\n lastUpdated?: number;\n /**\n * Whether the shape is locked for editing\n * Locked shapes cannot be modified due to data restrictions or user preference\n */\n locked?: boolean;\n};\n\n/**\n * Circle shape with required circleProperties\n */\nexport type CircleShape = BaseShape & {\n shape: typeof ShapeFeatureType.Circle;\n feature: StyledFeature & { properties: CircleFeatureProperties };\n};\n\n/**\n * Ellipse shape with required ellipseProperties\n */\nexport type EllipseShape = BaseShape & {\n shape: typeof ShapeFeatureType.Ellipse;\n feature: StyledFeature & { properties: EllipseFeatureProperties };\n};\n\n/**\n * Polygon shape\n */\nexport type PolygonShape = BaseShape & {\n shape: typeof ShapeFeatureType.Polygon;\n feature: StyledFeature;\n};\n\n/**\n * Rectangle shape\n */\nexport type RectangleShape = BaseShape & {\n shape: typeof ShapeFeatureType.Rectangle;\n feature: StyledFeature;\n};\n\n/**\n * LineString shape\n */\nexport type LineStringShape = BaseShape & {\n shape: typeof ShapeFeatureType.LineString;\n feature: StyledFeature;\n};\n\n/**\n * Point shape\n */\nexport type PointShape = BaseShape & {\n shape: typeof ShapeFeatureType.Point;\n feature: StyledFeature;\n};\n\n/**\n * Discriminated union of all shape types.\n *\n * Use this for type narrowing based on shape:\n * @example\n * ```typescript\n * function handleShape(shape: Shape) {\n * if (shape.shape === 'Circle') {\n * // TypeScript knows shape.feature.properties.circleProperties exists\n * const { center, radius } = shape.feature.properties.circleProperties;\n * }\n * }\n * ```\n */\nexport type Shape =\n | CircleShape\n | EllipseShape\n | PolygonShape\n | RectangleShape\n | LineStringShape\n | PointShape;\n\n/**\n * Alias for StyledFeature (shape feature)\n */\nexport type ShapeFeature = StyledFeature;\n\n/**\n * Alias for StyledFeature properties\n */\nexport type ShapeFeatureProperties = StyledFeature['properties'];\n\n/**\n * Circle radius type\n */\nexport type CircleRadius = CircleProperties['radius'];\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Type guard for Circle shapes.\n *\n * @param shape - The shape to test.\n * @returns True if shape is a CircleShape.\n *\n * @example\n * ```typescript\n * if (isCircleShape(shape)) {\n * // shape.feature.properties.circleProperties is available\n * const { center, radius } = shape.feature.properties.circleProperties;\n * }\n * ```\n */\nexport function isCircleShape(shape: Shape): shape is CircleShape {\n return shape.shape === ShapeFeatureType.Circle;\n}\n\n/**\n * Type guard for Ellipse shapes.\n *\n * @param shape - The shape to test.\n * @returns True if shape is an EllipseShape.\n *\n * @example\n * ```typescript\n * if (isEllipseShape(shape)) {\n * // shape.feature.properties.ellipseProperties is available\n * const { center, xSemiAxis, ySemiAxis } = shape.feature.properties.ellipseProperties;\n * }\n * ```\n */\nexport function isEllipseShape(shape: Shape): shape is EllipseShape {\n return shape.shape === ShapeFeatureType.Ellipse;\n}\n\n/**\n * Type guard for Polygon shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a PolygonShape\n * @example\n * ```typescript\n * if (isPolygonShape(shape)) {\n * // TypeScript narrows shape to PolygonShape\n * }\n * ```\n */\nexport function isPolygonShape(shape: Shape): shape is PolygonShape {\n return shape.shape === ShapeFeatureType.Polygon;\n}\n\n/**\n * Type guard for Rectangle shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a RectangleShape\n * @example\n * ```typescript\n * if (isRectangleShape(shape)) {\n * // TypeScript narrows shape to RectangleShape\n * }\n * ```\n */\nexport function isRectangleShape(shape: Shape): shape is RectangleShape {\n return shape.shape === ShapeFeatureType.Rectangle;\n}\n\n/**\n * Type guard for LineString shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a LineStringShape\n * @example\n * ```typescript\n * if (isLineStringShape(shape)) {\n * // TypeScript narrows shape to LineStringShape\n * }\n * ```\n */\nexport function isLineStringShape(shape: Shape): shape is LineStringShape {\n return shape.shape === ShapeFeatureType.LineString;\n}\n\n/**\n * Type guard for Point shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a PointShape\n * @example\n * ```typescript\n * if (isPointShape(shape)) {\n * // TypeScript narrows shape to PointShape\n * }\n * ```\n */\nexport function isPointShape(shape: Shape): shape is PointShape {\n return shape.shape === ShapeFeatureType.Point;\n}\n\n// =============================================================================\n// Geometry Type Predicates\n// =============================================================================\n// These narrow GeoJSON Geometry unions (e.g. Polygon, MultiPolygon),\n// distinct from the Shape type guards above which check shape.shape ('Circle', etc.).\n\n// --- Granular geometry predicates (single GeoJSON type) ---\n\n/**\n * Narrow a GeoJSON geometry to the Point type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Point.\n */\nexport function isPointType(geometry: Geometry): geometry is Point {\n return geometry.type === 'Point';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiPoint type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiPoint.\n */\nexport function isMultiPointType(geometry: Geometry): geometry is MultiPoint {\n return geometry.type === 'MultiPoint';\n}\n\n/**\n * Narrow a GeoJSON geometry to the LineString type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a LineString.\n */\nexport function isLineStringType(geometry: Geometry): geometry is LineString {\n return geometry.type === 'LineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiLineString type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiLineString.\n */\nexport function isMultiLineStringType(\n geometry: Geometry,\n): geometry is MultiLineString {\n return geometry.type === 'MultiLineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to the Polygon type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Polygon.\n */\nexport function isPolygonType(geometry: Geometry): geometry is Polygon {\n return geometry.type === 'Polygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiPolygon type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiPolygon.\n */\nexport function isMultiPolygonType(\n geometry: Geometry,\n): geometry is MultiPolygon {\n return geometry.type === 'MultiPolygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to the GeometryCollection type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a GeometryCollection.\n */\nexport function isGeometryCollectionType(\n geometry: Geometry,\n): geometry is GeometryCollection {\n return geometry.type === 'GeometryCollection';\n}\n\n// --- Composite geometry predicates (multiple GeoJSON types) ---\n\n/**\n * Narrow a GeoJSON geometry to polygon-like types (Polygon or MultiPolygon).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Polygon or MultiPolygon.\n *\n * @example\n * ```typescript\n * if (isPolygonGeometry(feature.geometry)) {\n * // geometry narrowed to Polygon | MultiPolygon\n * }\n * ```\n */\nexport function isPolygonGeometry(\n geometry: Geometry,\n): geometry is Polygon | MultiPolygon {\n return geometry.type === 'Polygon' || geometry.type === 'MultiPolygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to line-like types (LineString or MultiLineString).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a LineString or MultiLineString.\n *\n * @example\n * ```typescript\n * if (isLineGeometry(feature.geometry)) {\n * // geometry narrowed to LineString | MultiLineString\n * }\n * ```\n */\nexport function isLineGeometry(\n geometry: Geometry,\n): geometry is LineString | MultiLineString {\n return geometry.type === 'LineString' || geometry.type === 'MultiLineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to point-like types (Point or MultiPoint).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Point or MultiPoint.\n *\n * @example\n * ```typescript\n * if (isPointGeometry(feature.geometry)) {\n * // geometry narrowed to Point | MultiPoint\n * }\n * ```\n */\nexport function isPointGeometry(\n geometry: Geometry,\n): geometry is Point | MultiPoint {\n return geometry.type === 'Point' || geometry.type === 'MultiPoint';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAa,mBAAmB;CAC9B,QAAQ;CACR,SAAS;CACT,SAAS;CACT,WAAW;CACX,YAAY;CACZ,OAAO;CACR;;;;;;;;;;;;;;;AA2RD,SAAgB,cAAc,OAAoC;AAChE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;;;AAiB1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,iBAAiB,OAAuC;AACtE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,kBAAkB,OAAwC;AACxE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,aAAa,OAAmC;AAC9D,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;AAiB1C,SAAgB,YAAY,UAAuC;AACjE,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,iBAAiB,UAA4C;AAC3E,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,iBAAiB,UAA4C;AAC3E,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,sBACd,UAC6B;AAC7B,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,cAAc,UAAyC;AACrE,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,mBACd,UAC0B;AAC1B,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,yBACd,UACgC;AAChC,QAAO,SAAS,SAAS;;;;;;;;;;;;;;;AAkB3B,SAAgB,kBACd,UACoC;AACpC,QAAO,SAAS,SAAS,aAAa,SAAS,SAAS;;;;;;;;;;;;;;;AAgB1D,SAAgB,eACd,UAC0C;AAC1C,QAAO,SAAS,SAAS,gBAAgB,SAAS,SAAS;;;;;;;;;;;;;;;AAgB7D,SAAgB,gBACd,UACgC;AAChC,QAAO,SAAS,SAAS,WAAW,SAAS,SAAS"}
1
+ {"version":3,"file":"types.js","names":[],"sources":["../../../../src/deckgl/shapes/shared/types.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 type { DistanceUnit } from '@accelint/constants/units';\nimport type { UniqueId } from '@accelint/core';\nimport type { Color } from '@deck.gl/core';\nimport type {\n Feature,\n Geometry,\n GeometryCollection,\n LineString,\n MultiLineString,\n MultiPoint,\n MultiPolygon,\n Point,\n Polygon,\n} from 'geojson';\n\n/**\n * Supported shape types\n */\nexport const ShapeFeatureType = {\n Circle: 'Circle',\n Ellipse: 'Ellipse',\n Polygon: 'Polygon',\n Rectangle: 'Rectangle',\n LineString: 'LineString',\n Point: 'Point',\n} as const;\n\n/** Union of all supported shape feature type string literals. */\nexport type ShapeFeatureType =\n (typeof ShapeFeatureType)[keyof typeof ShapeFeatureType];\n\n/**\n * Shape ID type - uses UniqueId from core\n */\nexport type ShapeId = UniqueId;\n\n/**\n * Border/outline width options (in pixels).\n * Controls the width of shape outlines.\n */\nexport type LineWidth = 1 | 2 | 4 | 8;\n\n/**\n * Border/outline pattern options.\n * Controls how shape outlines are rendered.\n */\nexport type LinePattern = 'solid' | 'dashed' | 'dotted';\n\n/**\n * Style properties for rendering shapes\n */\nexport type StyleProperties = {\n /** Fill color as RGBA array [r, g, b, a] where each value is 0-255 */\n fillColor: Color;\n /** Border/outline color as RGBA array [r, g, b, a] where each value is 0-255 */\n lineColor: Color;\n /** Border/outline width in pixels */\n lineWidth: LineWidth;\n /** Border/outline pattern (solid, dashed, or dotted) */\n linePattern: LinePattern;\n /** Optional icon properties for Point geometries */\n icon?: {\n /** Icon atlas URL or data */\n atlas?: string;\n /** Icon mapping (name to position in atlas) */\n mapping?: Record<\n string,\n { x: number; y: number; width: number; height: number; mask?: boolean }\n >;\n /** Icon name to use from mapping */\n name?: string;\n /** Icon size in pixels */\n size?: number;\n };\n /** Optional custom label pixel offset [x, y] */\n labelOffset?: [number, number];\n /** Optional custom label vertical anchor */\n labelVerticalAnchor?: 'top' | 'middle' | 'bottom';\n /** Optional custom label horizontal anchor */\n labelHorizontalAnchor?: 'left' | 'center' | 'right';\n /** Optional custom label coordinate anchor (position along geometry) */\n labelCoordinateAnchor?:\n | 'center'\n | 'start'\n | 'middle'\n | 'end'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n};\n\n/**\n * Geographic position as [longitude, latitude] with optional elevation.\n *\n * Similar to GeoJSON's `Position` (`number[]`) but with stricter typing\n * that guarantees at least two elements.\n */\nexport type GeoPosition = [number, number] | [number, number, number];\n\n/**\n * Circle-specific properties for precise rendering\n * Stored alongside the polygon approximation\n */\nexport type CircleProperties = {\n /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */\n center: GeoPosition;\n /** Radius with value and units */\n radius: {\n /** Radius value */\n value: number;\n /** Units for the radius measurement */\n units: DistanceUnit;\n };\n};\n\n/**\n * Ellipse-specific properties for precise rendering\n * Stored alongside the polygon approximation\n */\nexport type EllipseProperties = {\n /** Center point as [longitude, latitude] or [longitude, latitude, elevation] */\n center: GeoPosition;\n /** X semi-axis (horizontal radius) with value and units */\n xSemiAxis: {\n /** X semi-axis value */\n value: number;\n /** Units for the measurement */\n units: DistanceUnit;\n };\n /** Y semi-axis (vertical radius) with value and units */\n ySemiAxis: {\n /** Y semi-axis value */\n value: number;\n /** Units for the measurement */\n units: DistanceUnit;\n };\n /** Rotation angle in degrees */\n angle: number;\n};\n\n/**\n * Properties for styled features.\n *\n * Note: circleProperties and ellipseProperties are optional at the type level\n * but are guaranteed to be present for their respective shape types.\n * Use the type guards (isCircleShape, isEllipseShape) for type narrowing.\n */\nexport type StyledFeatureProperties = {\n /** Style properties for rendering */\n styleProperties: StyleProperties;\n /** Shape ID for correlation */\n shapeId?: ShapeId;\n /** Circle properties (present for Circle shapes) */\n circleProperties?: CircleProperties;\n /** Ellipse properties (present for Ellipse shapes) */\n ellipseProperties?: EllipseProperties;\n /** Minimum elevation in meters (optional) */\n minElevation?: number;\n /** Maximum elevation in meters (optional) */\n maxElevation?: number;\n};\n\n/**\n * Feature properties for Circle shapes (circleProperties required).\n * Used by CircleShape for better type narrowing.\n */\nexport type CircleFeatureProperties = StyledFeatureProperties & {\n /** Circle properties (required for Circle shapes) */\n circleProperties: CircleProperties;\n};\n\n/**\n * Feature properties for Ellipse shapes (ellipseProperties required).\n * Used by EllipseShape for better type narrowing.\n */\nexport type EllipseFeatureProperties = StyledFeatureProperties & {\n /** Ellipse properties (required for Ellipse shapes) */\n ellipseProperties: EllipseProperties;\n};\n\n/**\n * GeoJSON Feature with style properties\n */\nexport type StyledFeature = Feature & {\n properties: StyledFeatureProperties;\n};\n\n/**\n * Base shape properties shared by all shapes.\n */\ntype BaseShape = {\n /** Unique identifier */\n id: ShapeId;\n /** Full shape name used internally and in UI */\n name: string;\n /**\n * Optional short display label shown on the map\n * If not provided, the `name` property will be used instead\n * Useful for showing abbreviated text on the map (e.g., \"NYC\" vs \"New York City Office\")\n */\n label?: string;\n /** GeoJSON feature with geometry and style properties */\n feature: ShapeFeature;\n /** UTC timestamp (only set when saved) */\n lastUpdated?: number;\n /**\n * Whether the shape is locked for editing\n * Locked shapes cannot be modified due to data restrictions or user preference\n */\n locked?: boolean;\n};\n\n/**\n * Circle shape with required circleProperties\n */\nexport type CircleShape = BaseShape & {\n shape: typeof ShapeFeatureType.Circle;\n feature: StyledFeature & { properties: CircleFeatureProperties };\n};\n\n/**\n * Ellipse shape with required ellipseProperties\n */\nexport type EllipseShape = BaseShape & {\n shape: typeof ShapeFeatureType.Ellipse;\n feature: StyledFeature & { properties: EllipseFeatureProperties };\n};\n\n/**\n * Polygon shape\n */\nexport type PolygonShape = BaseShape & {\n shape: typeof ShapeFeatureType.Polygon;\n feature: StyledFeature;\n};\n\n/**\n * Rectangle shape\n */\nexport type RectangleShape = BaseShape & {\n shape: typeof ShapeFeatureType.Rectangle;\n feature: StyledFeature;\n};\n\n/**\n * LineString shape\n */\nexport type LineStringShape = BaseShape & {\n shape: typeof ShapeFeatureType.LineString;\n feature: StyledFeature;\n};\n\n/**\n * Point shape\n */\nexport type PointShape = BaseShape & {\n shape: typeof ShapeFeatureType.Point;\n feature: StyledFeature;\n};\n\n/**\n * Discriminated union of all shape types.\n *\n * Use this for type narrowing based on shape:\n * @example\n * ```typescript\n * function handleShape(shape: Shape) {\n * if (shape.shape === 'Circle') {\n * // TypeScript knows shape.feature.properties.circleProperties exists\n * const { center, radius } = shape.feature.properties.circleProperties;\n * }\n * }\n * ```\n */\nexport type Shape =\n | CircleShape\n | EllipseShape\n | PolygonShape\n | RectangleShape\n | LineStringShape\n | PointShape;\n\n/**\n * Alias for StyledFeature (shape feature)\n */\nexport type ShapeFeature = StyledFeature;\n\n/**\n * Alias for StyledFeature properties\n */\nexport type ShapeFeatureProperties = StyledFeature['properties'];\n\n/**\n * Circle radius type\n */\nexport type CircleRadius = CircleProperties['radius'];\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Type guard for Circle shapes.\n *\n * @param shape - The shape to test.\n * @returns True if shape is a CircleShape.\n *\n * @example\n * ```typescript\n * if (isCircleShape(shape)) {\n * // shape.feature.properties.circleProperties is available\n * const { center, radius } = shape.feature.properties.circleProperties;\n * }\n * ```\n */\nexport function isCircleShape(shape: Shape): shape is CircleShape {\n return shape.shape === ShapeFeatureType.Circle;\n}\n\n/**\n * Type guard for Ellipse shapes.\n *\n * @param shape - The shape to test.\n * @returns True if shape is an EllipseShape.\n *\n * @example\n * ```typescript\n * if (isEllipseShape(shape)) {\n * // shape.feature.properties.ellipseProperties is available\n * const { center, xSemiAxis, ySemiAxis } = shape.feature.properties.ellipseProperties;\n * }\n * ```\n */\nexport function isEllipseShape(shape: Shape): shape is EllipseShape {\n return shape.shape === ShapeFeatureType.Ellipse;\n}\n\n/**\n * Type guard for Polygon shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a PolygonShape\n * @example\n * ```typescript\n * if (isPolygonShape(shape)) {\n * // TypeScript narrows shape to PolygonShape\n * }\n * ```\n */\nexport function isPolygonShape(shape: Shape): shape is PolygonShape {\n return shape.shape === ShapeFeatureType.Polygon;\n}\n\n/**\n * Type guard for Rectangle shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a RectangleShape\n * @example\n * ```typescript\n * if (isRectangleShape(shape)) {\n * // TypeScript narrows shape to RectangleShape\n * }\n * ```\n */\nexport function isRectangleShape(shape: Shape): shape is RectangleShape {\n return shape.shape === ShapeFeatureType.Rectangle;\n}\n\n/**\n * Type guard for LineString shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a LineStringShape\n * @example\n * ```typescript\n * if (isLineStringShape(shape)) {\n * // TypeScript narrows shape to LineStringShape\n * }\n * ```\n */\nexport function isLineStringShape(shape: Shape): shape is LineStringShape {\n return shape.shape === ShapeFeatureType.LineString;\n}\n\n/**\n * Type guard for Point shapes.\n *\n * @param shape - The shape to test\n * @returns True if shape is a PointShape\n * @example\n * ```typescript\n * if (isPointShape(shape)) {\n * // TypeScript narrows shape to PointShape\n * }\n * ```\n */\nexport function isPointShape(shape: Shape): shape is PointShape {\n return shape.shape === ShapeFeatureType.Point;\n}\n\n// =============================================================================\n// Geometry Type Predicates\n// =============================================================================\n// These narrow GeoJSON Geometry unions (e.g. Polygon, MultiPolygon),\n// distinct from the Shape type guards above which check shape.shape ('Circle', etc.).\n\n// --- Granular geometry predicates (single GeoJSON type) ---\n\n/**\n * Narrow a GeoJSON geometry to the Point type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Point.\n */\nexport function isPointType(geometry: Geometry): geometry is Point {\n return geometry.type === 'Point';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiPoint type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiPoint.\n */\nexport function isMultiPointType(geometry: Geometry): geometry is MultiPoint {\n return geometry.type === 'MultiPoint';\n}\n\n/**\n * Narrow a GeoJSON geometry to the LineString type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a LineString.\n */\nexport function isLineStringType(geometry: Geometry): geometry is LineString {\n return geometry.type === 'LineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiLineString type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiLineString.\n */\nexport function isMultiLineStringType(\n geometry: Geometry,\n): geometry is MultiLineString {\n return geometry.type === 'MultiLineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to the Polygon type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Polygon.\n */\nexport function isPolygonType(geometry: Geometry): geometry is Polygon {\n return geometry.type === 'Polygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to the MultiPolygon type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a MultiPolygon.\n */\nexport function isMultiPolygonType(\n geometry: Geometry,\n): geometry is MultiPolygon {\n return geometry.type === 'MultiPolygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to the GeometryCollection type.\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a GeometryCollection.\n */\nexport function isGeometryCollectionType(\n geometry: Geometry,\n): geometry is GeometryCollection {\n return geometry.type === 'GeometryCollection';\n}\n\n// --- Composite geometry predicates (multiple GeoJSON types) ---\n\n/**\n * Narrow a GeoJSON geometry to polygon-like types (Polygon or MultiPolygon).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Polygon or MultiPolygon.\n *\n * @example\n * ```typescript\n * if (isPolygonGeometry(feature.geometry)) {\n * // geometry narrowed to Polygon | MultiPolygon\n * }\n * ```\n */\nexport function isPolygonGeometry(\n geometry: Geometry,\n): geometry is Polygon | MultiPolygon {\n return geometry.type === 'Polygon' || geometry.type === 'MultiPolygon';\n}\n\n/**\n * Narrow a GeoJSON geometry to line-like types (LineString or MultiLineString).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a LineString or MultiLineString.\n *\n * @example\n * ```typescript\n * if (isLineGeometry(feature.geometry)) {\n * // geometry narrowed to LineString | MultiLineString\n * }\n * ```\n */\nexport function isLineGeometry(\n geometry: Geometry,\n): geometry is LineString | MultiLineString {\n return geometry.type === 'LineString' || geometry.type === 'MultiLineString';\n}\n\n/**\n * Narrow a GeoJSON geometry to point-like types (Point or MultiPoint).\n *\n * @param geometry - The GeoJSON geometry to test.\n * @returns True if the geometry is a Point or MultiPoint.\n *\n * @example\n * ```typescript\n * if (isPointGeometry(feature.geometry)) {\n * // geometry narrowed to Point | MultiPoint\n * }\n * ```\n */\nexport function isPointGeometry(\n geometry: Geometry,\n): geometry is Point | MultiPoint {\n return geometry.type === 'Point' || geometry.type === 'MultiPoint';\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAgCA,MAAa,mBAAmB;CAC9B,QAAQ;CACR,SAAS;CACT,SAAS;CACT,WAAW;CACX,YAAY;CACZ,OAAO;CACR;;;;;;;;;;;;;;;AAmSD,SAAgB,cAAc,OAAoC;AAChE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;;;AAiB1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,iBAAiB,OAAuC;AACtE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,kBAAkB,OAAwC;AACxE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;;AAe1C,SAAgB,aAAa,OAAmC;AAC9D,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;AAiB1C,SAAgB,YAAY,UAAuC;AACjE,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,iBAAiB,UAA4C;AAC3E,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,iBAAiB,UAA4C;AAC3E,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,sBACd,UAC6B;AAC7B,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,cAAc,UAAyC;AACrE,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,mBACd,UAC0B;AAC1B,QAAO,SAAS,SAAS;;;;;;;;AAS3B,SAAgB,yBACd,UACgC;AAChC,QAAO,SAAS,SAAS;;;;;;;;;;;;;;;AAkB3B,SAAgB,kBACd,UACoC;AACpC,QAAO,SAAS,SAAS,aAAa,SAAS,SAAS;;;;;;;;;;;;;;;AAgB1D,SAAgB,eACd,UAC0C;AAC1C,QAAO,SAAS,SAAS,gBAAgB,SAAS,SAAS;;;;;;;;;;;;;;;AAgB7D,SAAgB,gBACd,UACgC;AAChC,QAAO,SAAS,SAAS,WAAW,SAAS,SAAS"}
@@ -0,0 +1,56 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+ import { Shape } from "../types.js";
14
+
15
+ //#region src/deckgl/shapes/shared/utils/duplicate-shape.d.ts
16
+ /**
17
+ * Options for duplicating a shape.
18
+ */
19
+ type DuplicateShapeOptions = {
20
+ /** Custom name for the clone. Defaults to "{original name} (copy)". */
21
+ name?: string;
22
+ /** Coordinate offset [longitude, latitude] applied to all geometry coordinates. No offset by default. */
23
+ offset?: [number, number];
24
+ };
25
+ /**
26
+ * Duplicate a shape, producing a new Shape with a unique ID and optionally
27
+ * offset coordinates.
28
+ *
29
+ * The clone preserves the original's geometry, style properties, and any
30
+ * shape-specific properties (circle radius, ellipse axes). A new UUID is
31
+ * generated for both the shape ID and the feature's `shapeId` property.
32
+ *
33
+ * @param shape - The source shape to clone.
34
+ * @param options - Optional name override and coordinate offset.
35
+ * @returns A new Shape with a unique ID.
36
+ *
37
+ * @example No offset (clone appears on top of original)
38
+ * ```typescript
39
+ * const clone = duplicateShape(selectedShape);
40
+ * setShapes(prev => [...prev, clone]);
41
+ * ```
42
+ *
43
+ * @example With offset (clone appears nearby)
44
+ * ```typescript
45
+ * const clone = duplicateShape(selectedShape, { offset: [0.05, 0.05] });
46
+ * ```
47
+ *
48
+ * @example With custom name
49
+ * ```typescript
50
+ * const clone = duplicateShape(selectedShape, { name: 'Backup Zone' });
51
+ * ```
52
+ */
53
+ declare function duplicateShape(shape: Shape, options?: DuplicateShapeOptions): Shape;
54
+ //#endregion
55
+ export { DuplicateShapeOptions, duplicateShape };
56
+ //# sourceMappingURL=duplicate-shape.d.ts.map
@@ -0,0 +1,131 @@
1
+ /*
2
+ * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { isCircleShape, isEllipseShape } from "../types.js";
15
+ import { uuid } from "@accelint/core";
16
+
17
+ //#region src/deckgl/shapes/shared/utils/duplicate-shape.ts
18
+ /**
19
+ * Shift a single coordinate position by the given offset.
20
+ *
21
+ * Preserves elevation (third element) if present.
22
+ *
23
+ * @param position - The [lng, lat] or [lng, lat, elevation] coordinate.
24
+ * @param offset - The [lng, lat] offset to apply.
25
+ * @returns A new position array with the offset applied.
26
+ */
27
+ function offsetPosition(position, offset) {
28
+ const [lng, lat, elevation] = position;
29
+ const shifted = [lng + offset[0], lat + offset[1]];
30
+ if (elevation != null) shifted.push(elevation);
31
+ return shifted;
32
+ }
33
+ /**
34
+ * Shift all coordinates in a GeoJSON geometry by the given offset.
35
+ *
36
+ * Handles all standard GeoJSON geometry types: Point, MultiPoint, LineString,
37
+ * MultiLineString, Polygon, MultiPolygon, and GeometryCollection.
38
+ *
39
+ * @param geometry - The GeoJSON geometry to offset.
40
+ * @param offset - The [longitude, latitude] offset to apply.
41
+ * @returns A new geometry object with all coordinates shifted.
42
+ */
43
+ function offsetGeometry(geometry, offset) {
44
+ switch (geometry.type) {
45
+ case "Point": return {
46
+ type: "Point",
47
+ coordinates: offsetPosition(geometry.coordinates, offset)
48
+ };
49
+ case "MultiPoint":
50
+ case "LineString": return {
51
+ type: geometry.type,
52
+ coordinates: geometry.coordinates.map((pos) => offsetPosition(pos, offset))
53
+ };
54
+ case "MultiLineString":
55
+ case "Polygon": return {
56
+ type: geometry.type,
57
+ coordinates: geometry.coordinates.map((ring) => ring.map((pos) => offsetPosition(pos, offset)))
58
+ };
59
+ case "MultiPolygon": return {
60
+ type: "MultiPolygon",
61
+ coordinates: geometry.coordinates.map((polygon) => polygon.map((ring) => ring.map((pos) => offsetPosition(pos, offset))))
62
+ };
63
+ case "GeometryCollection": return {
64
+ type: "GeometryCollection",
65
+ geometries: geometry.geometries.map((g) => offsetGeometry(g, offset))
66
+ };
67
+ }
68
+ }
69
+ /**
70
+ * Duplicate a shape, producing a new Shape with a unique ID and optionally
71
+ * offset coordinates.
72
+ *
73
+ * The clone preserves the original's geometry, style properties, and any
74
+ * shape-specific properties (circle radius, ellipse axes). A new UUID is
75
+ * generated for both the shape ID and the feature's `shapeId` property.
76
+ *
77
+ * @param shape - The source shape to clone.
78
+ * @param options - Optional name override and coordinate offset.
79
+ * @returns A new Shape with a unique ID.
80
+ *
81
+ * @example No offset (clone appears on top of original)
82
+ * ```typescript
83
+ * const clone = duplicateShape(selectedShape);
84
+ * setShapes(prev => [...prev, clone]);
85
+ * ```
86
+ *
87
+ * @example With offset (clone appears nearby)
88
+ * ```typescript
89
+ * const clone = duplicateShape(selectedShape, { offset: [0.05, 0.05] });
90
+ * ```
91
+ *
92
+ * @example With custom name
93
+ * ```typescript
94
+ * const clone = duplicateShape(selectedShape, { name: 'Backup Zone' });
95
+ * ```
96
+ */
97
+ function duplicateShape(shape, options) {
98
+ const id = uuid();
99
+ const name = options?.name ?? `${shape.name} (copy)`;
100
+ const offset = options?.offset;
101
+ const geometry = offset ? offsetGeometry(shape.feature.geometry, offset) : structuredClone(shape.feature.geometry);
102
+ const properties = {
103
+ ...structuredClone(shape.feature.properties),
104
+ shapeId: id
105
+ };
106
+ if (offset && isCircleShape(shape) && properties.circleProperties) properties.circleProperties = {
107
+ ...properties.circleProperties,
108
+ center: offsetPosition(properties.circleProperties.center, offset)
109
+ };
110
+ if (offset && isEllipseShape(shape) && properties.ellipseProperties) properties.ellipseProperties = {
111
+ ...properties.ellipseProperties,
112
+ center: offsetPosition(properties.ellipseProperties.center, offset)
113
+ };
114
+ return {
115
+ id,
116
+ name,
117
+ shape: shape.shape,
118
+ label: name,
119
+ feature: {
120
+ type: "Feature",
121
+ geometry,
122
+ properties
123
+ },
124
+ lastUpdated: Date.now(),
125
+ locked: false
126
+ };
127
+ }
128
+
129
+ //#endregion
130
+ export { duplicateShape };
131
+ //# sourceMappingURL=duplicate-shape.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"duplicate-shape.js","names":["shifted: Position"],"sources":["../../../../../src/deckgl/shapes/shared/utils/duplicate-shape.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { uuid } from '@accelint/core';\nimport { isCircleShape, isEllipseShape } from '../types';\nimport type { Geometry, Position } from 'geojson';\nimport type { GeoPosition, Shape } from '../types';\n\n/**\n * Options for duplicating a shape.\n */\nexport type DuplicateShapeOptions = {\n /** Custom name for the clone. Defaults to \"{original name} (copy)\". */\n name?: string;\n /** Coordinate offset [longitude, latitude] applied to all geometry coordinates. No offset by default. */\n offset?: [number, number];\n};\n\n/**\n * Shift a single coordinate position by the given offset.\n *\n * Preserves elevation (third element) if present.\n *\n * @param position - The [lng, lat] or [lng, lat, elevation] coordinate.\n * @param offset - The [lng, lat] offset to apply.\n * @returns A new position array with the offset applied.\n */\nfunction offsetPosition(\n position: Position,\n offset: [number, number],\n): Position {\n // GeoJSON positions always have at least [lng, lat]; cast for safe indexing\n const [lng, lat, elevation] = position as GeoPosition;\n const shifted: Position = [lng + offset[0], lat + offset[1]];\n\n if (elevation != null) {\n shifted.push(elevation);\n }\n\n return shifted;\n}\n\n/**\n * Shift all coordinates in a GeoJSON geometry by the given offset.\n *\n * Handles all standard GeoJSON geometry types: Point, MultiPoint, LineString,\n * MultiLineString, Polygon, MultiPolygon, and GeometryCollection.\n *\n * @param geometry - The GeoJSON geometry to offset.\n * @param offset - The [longitude, latitude] offset to apply.\n * @returns A new geometry object with all coordinates shifted.\n */\nfunction offsetGeometry(\n geometry: Geometry,\n offset: [number, number],\n): Geometry {\n switch (geometry.type) {\n case 'Point':\n return {\n type: 'Point',\n coordinates: offsetPosition(geometry.coordinates, offset),\n };\n\n case 'MultiPoint':\n case 'LineString':\n return {\n type: geometry.type,\n coordinates: geometry.coordinates.map((pos) =>\n offsetPosition(pos, offset),\n ),\n };\n\n case 'MultiLineString':\n case 'Polygon':\n return {\n type: geometry.type,\n coordinates: geometry.coordinates.map((ring) =>\n ring.map((pos) => offsetPosition(pos, offset)),\n ),\n };\n\n case 'MultiPolygon':\n return {\n type: 'MultiPolygon',\n coordinates: geometry.coordinates.map((polygon) =>\n polygon.map((ring) => ring.map((pos) => offsetPosition(pos, offset))),\n ),\n };\n\n case 'GeometryCollection':\n return {\n type: 'GeometryCollection',\n geometries: geometry.geometries.map((g) => offsetGeometry(g, offset)),\n };\n }\n}\n\n/**\n * Duplicate a shape, producing a new Shape with a unique ID and optionally\n * offset coordinates.\n *\n * The clone preserves the original's geometry, style properties, and any\n * shape-specific properties (circle radius, ellipse axes). A new UUID is\n * generated for both the shape ID and the feature's `shapeId` property.\n *\n * @param shape - The source shape to clone.\n * @param options - Optional name override and coordinate offset.\n * @returns A new Shape with a unique ID.\n *\n * @example No offset (clone appears on top of original)\n * ```typescript\n * const clone = duplicateShape(selectedShape);\n * setShapes(prev => [...prev, clone]);\n * ```\n *\n * @example With offset (clone appears nearby)\n * ```typescript\n * const clone = duplicateShape(selectedShape, { offset: [0.05, 0.05] });\n * ```\n *\n * @example With custom name\n * ```typescript\n * const clone = duplicateShape(selectedShape, { name: 'Backup Zone' });\n * ```\n */\nexport function duplicateShape(\n shape: Shape,\n options?: DuplicateShapeOptions,\n): Shape {\n const id = uuid();\n const name = options?.name ?? `${shape.name} (copy)`;\n const offset = options?.offset;\n\n const geometry = offset\n ? offsetGeometry(shape.feature.geometry, offset)\n : structuredClone(shape.feature.geometry);\n\n // Build properties, offsetting shape-specific centers when needed\n const properties = {\n ...structuredClone(shape.feature.properties),\n shapeId: id,\n };\n\n if (offset && isCircleShape(shape) && properties.circleProperties) {\n properties.circleProperties = {\n ...properties.circleProperties,\n center: offsetPosition(\n properties.circleProperties.center,\n offset,\n ) as GeoPosition,\n };\n }\n\n if (offset && isEllipseShape(shape) && properties.ellipseProperties) {\n properties.ellipseProperties = {\n ...properties.ellipseProperties,\n center: offsetPosition(\n properties.ellipseProperties.center,\n offset,\n ) as GeoPosition,\n };\n }\n\n return {\n id,\n name,\n shape: shape.shape,\n label: name,\n feature: {\n type: 'Feature',\n geometry,\n properties,\n },\n lastUpdated: Date.now(),\n locked: false,\n } as Shape;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAoCA,SAAS,eACP,UACA,QACU;CAEV,MAAM,CAAC,KAAK,KAAK,aAAa;CAC9B,MAAMA,UAAoB,CAAC,MAAM,OAAO,IAAI,MAAM,OAAO,GAAG;AAE5D,KAAI,aAAa,KACf,SAAQ,KAAK,UAAU;AAGzB,QAAO;;;;;;;;;;;;AAaT,SAAS,eACP,UACA,QACU;AACV,SAAQ,SAAS,MAAjB;EACE,KAAK,QACH,QAAO;GACL,MAAM;GACN,aAAa,eAAe,SAAS,aAAa,OAAO;GAC1D;EAEH,KAAK;EACL,KAAK,aACH,QAAO;GACL,MAAM,SAAS;GACf,aAAa,SAAS,YAAY,KAAK,QACrC,eAAe,KAAK,OAAO,CAC5B;GACF;EAEH,KAAK;EACL,KAAK,UACH,QAAO;GACL,MAAM,SAAS;GACf,aAAa,SAAS,YAAY,KAAK,SACrC,KAAK,KAAK,QAAQ,eAAe,KAAK,OAAO,CAAC,CAC/C;GACF;EAEH,KAAK,eACH,QAAO;GACL,MAAM;GACN,aAAa,SAAS,YAAY,KAAK,YACrC,QAAQ,KAAK,SAAS,KAAK,KAAK,QAAQ,eAAe,KAAK,OAAO,CAAC,CAAC,CACtE;GACF;EAEH,KAAK,qBACH,QAAO;GACL,MAAM;GACN,YAAY,SAAS,WAAW,KAAK,MAAM,eAAe,GAAG,OAAO,CAAC;GACtE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgCP,SAAgB,eACd,OACA,SACO;CACP,MAAM,KAAK,MAAM;CACjB,MAAM,OAAO,SAAS,QAAQ,GAAG,MAAM,KAAK;CAC5C,MAAM,SAAS,SAAS;CAExB,MAAM,WAAW,SACb,eAAe,MAAM,QAAQ,UAAU,OAAO,GAC9C,gBAAgB,MAAM,QAAQ,SAAS;CAG3C,MAAM,aAAa;EACjB,GAAG,gBAAgB,MAAM,QAAQ,WAAW;EAC5C,SAAS;EACV;AAED,KAAI,UAAU,cAAc,MAAM,IAAI,WAAW,iBAC/C,YAAW,mBAAmB;EAC5B,GAAG,WAAW;EACd,QAAQ,eACN,WAAW,iBAAiB,QAC5B,OACD;EACF;AAGH,KAAI,UAAU,eAAe,MAAM,IAAI,WAAW,kBAChD,YAAW,oBAAoB;EAC7B,GAAG,WAAW;EACd,QAAQ,eACN,WAAW,kBAAkB,QAC7B,OACD;EACF;AAGH,QAAO;EACL;EACA;EACA,OAAO,MAAM;EACb,OAAO;EACP,SAAS;GACP,MAAM;GACN;GACA;GACD;EACD,aAAa,KAAK,KAAK;EACvB,QAAQ;EACT"}
@@ -1 +1 @@
1
- {"version":3,"file":"geometry-measurements.js","names":[],"sources":["../../../../../src/deckgl/shapes/shared/utils/geometry-measurements.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { distance } from '@turf/turf';\nimport type { DistanceUnit } from '@/shared/units';\n\n/**\n * Circle measurement result containing radius, diameter, and area.\n */\nexport type CircleMeasurements = {\n /** Radius in the specified units. */\n radius: number;\n /** Diameter (radius * 2) in the specified units. */\n diameter: number;\n /** Area (π * radius²) in the specified units squared. */\n area: number;\n};\n\n/**\n * Ellipse measurement result containing semi-axes and area.\n */\nexport type EllipseMeasurements = {\n /** X semi-axis (horizontal radius) in the specified units. */\n xSemiAxis: number;\n /** Y semi-axis (vertical radius) in the specified units. */\n ySemiAxis: number;\n /** Major axis (full length of longer axis) in the specified units. */\n majorAxis: number;\n /** Minor axis (full length of shorter axis) in the specified units. */\n minorAxis: number;\n /** Area (π * xSemiAxis * ySemiAxis) in the specified units squared. */\n area: number;\n};\n\n/**\n * Rectangle measurement result containing width, height, and area.\n */\nexport type RectangleMeasurements = {\n /** Width in the specified units. */\n width: number;\n /** Height in the specified units. */\n height: number;\n /** Area (width * height) in the specified units squared. */\n area: number;\n};\n\n/**\n * Compute circle measurements from center and edge point.\n *\n * @param center - Center point as [longitude, latitude].\n * @param edgePoint - Point on the circle's edge as [longitude, latitude].\n * @param units - Distance units for the measurements.\n * @returns Circle measurements including radius, diameter, and area.\n *\n * @example\n * ```typescript\n * const { radius, diameter, area } = computeCircleMeasurements(\n * [-122.4, 37.8],\n * [-122.3, 37.8],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeCircleMeasurements(\n center: [number, number],\n edgePoint: [number, number],\n units: DistanceUnit,\n): CircleMeasurements {\n const radius = distance(center, edgePoint, { units });\n const diameter = radius * 2;\n const area = Math.PI * radius ** 2;\n\n return { radius, diameter, area };\n}\n\n/**\n * Compute ellipse measurements from semi-axis lengths.\n *\n * @param xSemiAxis - X semi-axis (horizontal radius) in the specified units.\n * @param ySemiAxis - Y semi-axis (vertical radius) in the specified units.\n * @returns Ellipse measurements including axes and area.\n *\n * @example\n * ```typescript\n * const { majorAxis, minorAxis, area } = computeEllipseMeasurementsFromAxes(100, 50);\n * ```\n */\nexport function computeEllipseMeasurementsFromAxes(\n xSemiAxis: number,\n ySemiAxis: number,\n): EllipseMeasurements {\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n const area = Math.PI * xSemiAxis * ySemiAxis;\n\n return { xSemiAxis, ySemiAxis, majorAxis, minorAxis, area };\n}\n\n/**\n * Compute rectangle measurements from adjacent corner points.\n *\n * Uses geodesic distance calculations for accurate measurements on Earth's surface.\n * Corners should be provided in order: corner0 -> corner1 -> corner2 where:\n * - corner0 to corner1 defines one edge (width)\n * - corner1 to corner2 defines the adjacent edge (height)\n *\n * @param corner0 - First corner as [longitude, latitude].\n * @param corner1 - Adjacent corner as [longitude, latitude].\n * @param corner2 - Corner adjacent to corner1 as [longitude, latitude].\n * @param units - Distance units for the measurements.\n * @returns Rectangle measurements including width, height, and area.\n *\n * @example\n * ```typescript\n * const coords = polygon.geometry.coordinates[0];\n * const { width, height, area } = computeRectangleMeasurementsFromCorners(\n * coords[0] as [number, number],\n * coords[1] as [number, number],\n * coords[2] as [number, number],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeRectangleMeasurementsFromCorners(\n corner0: [number, number],\n corner1: [number, number],\n corner2: [number, number],\n units: DistanceUnit,\n): RectangleMeasurements {\n const options = { units };\n const width = distance(corner0, corner1, options);\n const height = distance(corner1, corner2, options);\n const area = width * height;\n\n return { width, height, area };\n}\n\n/**\n * Compute ellipse measurements from polygon coordinates.\n *\n * For an ellipse approximated as a polygon, calculates the major and minor axes\n * by measuring distances between opposite points on the perimeter.\n *\n * @param coordinates - Polygon ring coordinates as [[lon, lat], ...].\n * @param units - Distance units for the measurements.\n * @returns Ellipse measurements including axes and area.\n *\n * @example\n * ```typescript\n * const coords = polygon.geometry.coordinates[0];\n * const { majorAxis, minorAxis, area } = computeEllipseMeasurementsFromPolygon(\n * coords as [number, number][],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeEllipseMeasurementsFromPolygon(\n coordinates: [number, number][],\n units: DistanceUnit,\n): EllipseMeasurements {\n // Determine effective point count without allocating a new array\n const len = coordinates.length;\n const isClosed =\n len > 0 &&\n coordinates[0]?.[0] === coordinates[len - 1]?.[0] &&\n coordinates[0]?.[1] === coordinates[len - 1]?.[1];\n const pointCount = isClosed ? len - 1 : len;\n\n if (pointCount < 4) {\n return { xSemiAxis: 0, ySemiAxis: 0, majorAxis: 0, minorAxis: 0, area: 0 };\n }\n\n // For an ellipse polygon, opposite points are at index i and i + n/2\n const halfLen = Math.floor(pointCount / 2);\n const options = { units };\n let maxDist = 0;\n let minDist = Number.POSITIVE_INFINITY;\n\n // Sample several diameter measurements\n for (let i = 0; i < halfLen; i++) {\n const p1 = coordinates[i];\n const p2 = coordinates[i + halfLen];\n if (!(p1 && p2)) {\n continue;\n }\n\n const dist = distance(p1, p2, options);\n\n if (dist > maxDist) {\n maxDist = dist;\n }\n if (dist < minDist) {\n minDist = dist;\n }\n }\n\n const majorAxis = maxDist;\n const minorAxis = minDist === Number.POSITIVE_INFINITY ? maxDist : minDist;\n\n // Ellipse area = π × a × b (where a and b are semi-axes)\n const semiMajor = majorAxis / 2;\n const semiMinor = minorAxis / 2;\n const area = Math.PI * semiMajor * semiMinor;\n\n return {\n xSemiAxis: semiMajor,\n ySemiAxis: semiMinor,\n majorAxis,\n minorAxis,\n area,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,0BACd,QACA,WACA,OACoB;CACpB,MAAM,SAAS,SAAS,QAAQ,WAAW,EAAE,OAAO,CAAC;AAIrD,QAAO;EAAE;EAAQ,UAHA,SAAS;EAGC,MAFd,KAAK,KAAK,UAAU;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDnC,SAAgB,wCACd,SACA,SACA,SACA,OACuB;CACvB,MAAM,UAAU,EAAE,OAAO;CACzB,MAAM,QAAQ,SAAS,SAAS,SAAS,QAAQ;CACjD,MAAM,SAAS,SAAS,SAAS,SAAS,QAAQ;AAGlD,QAAO;EAAE;EAAO;EAAQ,MAFX,QAAQ;EAES;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,sCACd,aACA,OACqB;CAErB,MAAM,MAAM,YAAY;CAKxB,MAAM,aAHJ,MAAM,KACN,YAAY,KAAK,OAAO,YAAY,MAAM,KAAK,MAC/C,YAAY,KAAK,OAAO,YAAY,MAAM,KAAK,KACnB,MAAM,IAAI;AAExC,KAAI,aAAa,EACf,QAAO;EAAE,WAAW;EAAG,WAAW;EAAG,WAAW;EAAG,WAAW;EAAG,MAAM;EAAG;CAI5E,MAAM,UAAU,KAAK,MAAM,aAAa,EAAE;CAC1C,MAAM,UAAU,EAAE,OAAO;CACzB,IAAI,UAAU;CACd,IAAI,UAAU,OAAO;AAGrB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;EAChC,MAAM,KAAK,YAAY;EACvB,MAAM,KAAK,YAAY,IAAI;AAC3B,MAAI,EAAE,MAAM,IACV;EAGF,MAAM,OAAO,SAAS,IAAI,IAAI,QAAQ;AAEtC,MAAI,OAAO,QACT,WAAU;AAEZ,MAAI,OAAO,QACT,WAAU;;CAId,MAAM,YAAY;CAClB,MAAM,YAAY,YAAY,OAAO,oBAAoB,UAAU;CAGnE,MAAM,YAAY,YAAY;CAC9B,MAAM,YAAY,YAAY;AAG9B,QAAO;EACL,WAAW;EACX,WAAW;EACX;EACA;EACA,MAPW,KAAK,KAAK,YAAY;EAQlC"}
1
+ {"version":3,"file":"geometry-measurements.js","names":[],"sources":["../../../../../src/deckgl/shapes/shared/utils/geometry-measurements.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport { distance } from '@turf/turf';\nimport type { DistanceUnit } from '@accelint/constants/units';\n\n/**\n * Circle measurement result containing radius, diameter, and area.\n */\nexport type CircleMeasurements = {\n /** Radius in the specified units. */\n radius: number;\n /** Diameter (radius * 2) in the specified units. */\n diameter: number;\n /** Area (π * radius²) in the specified units squared. */\n area: number;\n};\n\n/**\n * Ellipse measurement result containing semi-axes and area.\n */\nexport type EllipseMeasurements = {\n /** X semi-axis (horizontal radius) in the specified units. */\n xSemiAxis: number;\n /** Y semi-axis (vertical radius) in the specified units. */\n ySemiAxis: number;\n /** Major axis (full length of longer axis) in the specified units. */\n majorAxis: number;\n /** Minor axis (full length of shorter axis) in the specified units. */\n minorAxis: number;\n /** Area (π * xSemiAxis * ySemiAxis) in the specified units squared. */\n area: number;\n};\n\n/**\n * Rectangle measurement result containing width, height, and area.\n */\nexport type RectangleMeasurements = {\n /** Width in the specified units. */\n width: number;\n /** Height in the specified units. */\n height: number;\n /** Area (width * height) in the specified units squared. */\n area: number;\n};\n\n/**\n * Compute circle measurements from center and edge point.\n *\n * @param center - Center point as [longitude, latitude].\n * @param edgePoint - Point on the circle's edge as [longitude, latitude].\n * @param units - Distance units for the measurements.\n * @returns Circle measurements including radius, diameter, and area.\n *\n * @example\n * ```typescript\n * const { radius, diameter, area } = computeCircleMeasurements(\n * [-122.4, 37.8],\n * [-122.3, 37.8],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeCircleMeasurements(\n center: [number, number],\n edgePoint: [number, number],\n units: DistanceUnit,\n): CircleMeasurements {\n const radius = distance(center, edgePoint, { units });\n const diameter = radius * 2;\n const area = Math.PI * radius ** 2;\n\n return { radius, diameter, area };\n}\n\n/**\n * Compute ellipse measurements from semi-axis lengths.\n *\n * @param xSemiAxis - X semi-axis (horizontal radius) in the specified units.\n * @param ySemiAxis - Y semi-axis (vertical radius) in the specified units.\n * @returns Ellipse measurements including axes and area.\n *\n * @example\n * ```typescript\n * const { majorAxis, minorAxis, area } = computeEllipseMeasurementsFromAxes(100, 50);\n * ```\n */\nexport function computeEllipseMeasurementsFromAxes(\n xSemiAxis: number,\n ySemiAxis: number,\n): EllipseMeasurements {\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n const area = Math.PI * xSemiAxis * ySemiAxis;\n\n return { xSemiAxis, ySemiAxis, majorAxis, minorAxis, area };\n}\n\n/**\n * Compute rectangle measurements from adjacent corner points.\n *\n * Uses geodesic distance calculations for accurate measurements on Earth's surface.\n * Corners should be provided in order: corner0 -> corner1 -> corner2 where:\n * - corner0 to corner1 defines one edge (width)\n * - corner1 to corner2 defines the adjacent edge (height)\n *\n * @param corner0 - First corner as [longitude, latitude].\n * @param corner1 - Adjacent corner as [longitude, latitude].\n * @param corner2 - Corner adjacent to corner1 as [longitude, latitude].\n * @param units - Distance units for the measurements.\n * @returns Rectangle measurements including width, height, and area.\n *\n * @example\n * ```typescript\n * const coords = polygon.geometry.coordinates[0];\n * const { width, height, area } = computeRectangleMeasurementsFromCorners(\n * coords[0] as [number, number],\n * coords[1] as [number, number],\n * coords[2] as [number, number],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeRectangleMeasurementsFromCorners(\n corner0: [number, number],\n corner1: [number, number],\n corner2: [number, number],\n units: DistanceUnit,\n): RectangleMeasurements {\n const options = { units };\n const width = distance(corner0, corner1, options);\n const height = distance(corner1, corner2, options);\n const area = width * height;\n\n return { width, height, area };\n}\n\n/**\n * Compute ellipse measurements from polygon coordinates.\n *\n * For an ellipse approximated as a polygon, calculates the major and minor axes\n * by measuring distances between opposite points on the perimeter.\n *\n * @param coordinates - Polygon ring coordinates as [[lon, lat], ...].\n * @param units - Distance units for the measurements.\n * @returns Ellipse measurements including axes and area.\n *\n * @example\n * ```typescript\n * const coords = polygon.geometry.coordinates[0];\n * const { majorAxis, minorAxis, area } = computeEllipseMeasurementsFromPolygon(\n * coords as [number, number][],\n * 'kilometers'\n * );\n * ```\n */\nexport function computeEllipseMeasurementsFromPolygon(\n coordinates: [number, number][],\n units: DistanceUnit,\n): EllipseMeasurements {\n // Determine effective point count without allocating a new array\n const len = coordinates.length;\n const isClosed =\n len > 0 &&\n coordinates[0]?.[0] === coordinates[len - 1]?.[0] &&\n coordinates[0]?.[1] === coordinates[len - 1]?.[1];\n const pointCount = isClosed ? len - 1 : len;\n\n if (pointCount < 4) {\n return { xSemiAxis: 0, ySemiAxis: 0, majorAxis: 0, minorAxis: 0, area: 0 };\n }\n\n // For an ellipse polygon, opposite points are at index i and i + n/2\n const halfLen = Math.floor(pointCount / 2);\n const options = { units };\n let maxDist = 0;\n let minDist = Number.POSITIVE_INFINITY;\n\n // Sample several diameter measurements\n for (let i = 0; i < halfLen; i++) {\n const p1 = coordinates[i];\n const p2 = coordinates[i + halfLen];\n if (!(p1 && p2)) {\n continue;\n }\n\n const dist = distance(p1, p2, options);\n\n if (dist > maxDist) {\n maxDist = dist;\n }\n if (dist < minDist) {\n minDist = dist;\n }\n }\n\n const majorAxis = maxDist;\n const minorAxis = minDist === Number.POSITIVE_INFINITY ? maxDist : minDist;\n\n // Ellipse area = π × a × b (where a and b are semi-axes)\n const semiMajor = majorAxis / 2;\n const semiMinor = minorAxis / 2;\n const area = Math.PI * semiMajor * semiMinor;\n\n return {\n xSemiAxis: semiMajor,\n ySemiAxis: semiMinor,\n majorAxis,\n minorAxis,\n area,\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwEA,SAAgB,0BACd,QACA,WACA,OACoB;CACpB,MAAM,SAAS,SAAS,QAAQ,WAAW,EAAE,OAAO,CAAC;AAIrD,QAAO;EAAE;EAAQ,UAHA,SAAS;EAGC,MAFd,KAAK,KAAK,UAAU;EAEA;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmDnC,SAAgB,wCACd,SACA,SACA,SACA,OACuB;CACvB,MAAM,UAAU,EAAE,OAAO;CACzB,MAAM,QAAQ,SAAS,SAAS,SAAS,QAAQ;CACjD,MAAM,SAAS,SAAS,SAAS,SAAS,QAAQ;AAGlD,QAAO;EAAE;EAAO;EAAQ,MAFX,QAAQ;EAES;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,sCACd,aACA,OACqB;CAErB,MAAM,MAAM,YAAY;CAKxB,MAAM,aAHJ,MAAM,KACN,YAAY,KAAK,OAAO,YAAY,MAAM,KAAK,MAC/C,YAAY,KAAK,OAAO,YAAY,MAAM,KAAK,KACnB,MAAM,IAAI;AAExC,KAAI,aAAa,EACf,QAAO;EAAE,WAAW;EAAG,WAAW;EAAG,WAAW;EAAG,WAAW;EAAG,MAAM;EAAG;CAI5E,MAAM,UAAU,KAAK,MAAM,aAAa,EAAE;CAC1C,MAAM,UAAU,EAAE,OAAO;CACzB,IAAI,UAAU;CACd,IAAI,UAAU,OAAO;AAGrB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;EAChC,MAAM,KAAK,YAAY;EACvB,MAAM,KAAK,YAAY,IAAI;AAC3B,MAAI,EAAE,MAAM,IACV;EAGF,MAAM,OAAO,SAAS,IAAI,IAAI,QAAQ;AAEtC,MAAI,OAAO,QACT,WAAU;AAEZ,MAAI,OAAO,QACT,WAAU;;CAId,MAAM,YAAY;CAClB,MAAM,YAAY,YAAY,OAAO,oBAAoB,UAAU;CAGnE,MAAM,YAAY,YAAY;CAC9B,MAAM,YAAY,YAAY;AAG9B,QAAO;EACL,WAAW;EACX,WAAW;EACX;EACA;EACA,MAPW,KAAK,KAAK,YAAY;EAQlC"}