@accelint/map-toolkit 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/catalog-info.yaml +3 -3
- package/dist/camera/events.d.ts +45 -0
- package/dist/camera/events.js +45 -0
- package/dist/camera/events.js.map +1 -1
- package/dist/camera/store.d.ts +47 -0
- package/dist/camera/store.js +81 -0
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +81 -0
- package/dist/cursor-coordinates/constants.d.ts +8 -0
- package/dist/cursor-coordinates/constants.js +22 -0
- package/dist/cursor-coordinates/constants.js.map +1 -0
- package/dist/cursor-coordinates/store.d.ts +1 -0
- package/dist/cursor-coordinates/store.js +1 -0
- package/dist/cursor-coordinates/store.js.map +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +5 -0
- package/dist/cursor-coordinates/use-cursor-coordinates.js +21 -6
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.d.ts +12 -0
- package/dist/deckgl/base-map/constants.js +12 -0
- package/dist/deckgl/base-map/constants.js.map +1 -1
- package/dist/deckgl/base-map/controls.d.ts +10 -0
- package/dist/deckgl/base-map/controls.js +5 -0
- package/dist/deckgl/base-map/controls.js.map +1 -1
- package/dist/deckgl/base-map/events.d.ts +30 -0
- package/dist/deckgl/base-map/events.js +30 -0
- package/dist/deckgl/base-map/events.js.map +1 -1
- package/dist/deckgl/base-map/index.d.ts +2 -2
- package/dist/deckgl/base-map/index.js +30 -0
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/saved-viewports/index.d.ts +75 -0
- package/dist/deckgl/saved-viewports/index.js +58 -0
- package/dist/deckgl/saved-viewports/index.js.map +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +51 -0
- package/dist/deckgl/saved-viewports/storage.js +64 -0
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +18 -6
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +7 -0
- package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +61 -4
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +22 -8
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +75 -4
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +30 -0
- package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +36 -0
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +32 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +37 -8
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +43 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +44 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +46 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +37 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +50 -2
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +137 -16
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/index.js +14 -0
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +56 -8
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +26 -4
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +28 -3
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +24 -0
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +33 -4
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +21 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +35 -11
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +12 -0
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +3 -3
- package/dist/deckgl/shapes/shared/types.js +2 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +3 -3
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +18 -0
- package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +79 -1
- package/dist/deckgl/symbol-layer/index.js +72 -1
- package/dist/deckgl/symbol-layer/index.js.map +1 -1
- package/dist/deckgl/text-layer/character-sets.d.ts +30 -0
- package/dist/deckgl/text-layer/character-sets.js +26 -0
- package/dist/deckgl/text-layer/character-sets.js.map +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +29 -0
- package/dist/deckgl/text-layer/default-settings.js +28 -0
- package/dist/deckgl/text-layer/default-settings.js.map +1 -1
- package/dist/deckgl/text-layer/index.d.ts +65 -0
- package/dist/deckgl/text-layer/index.js +56 -0
- package/dist/deckgl/text-layer/index.js.map +1 -1
- package/dist/map-cursor/events.d.ts +19 -0
- package/dist/map-cursor/events.js +19 -0
- package/dist/map-cursor/events.js.map +1 -1
- package/dist/map-cursor/store.d.ts +34 -2
- package/dist/map-cursor/store.js +44 -3
- package/dist/map-cursor/store.js.map +1 -1
- package/dist/map-mode/store.d.ts +43 -4
- package/dist/map-mode/store.js +55 -5
- package/dist/map-mode/store.js.map +1 -1
- package/dist/shared/create-map-store.d.ts +14 -0
- package/dist/shared/create-map-store.js +26 -2
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/shared/units.d.ts +24 -0
- package/dist/shared/units.js +24 -0
- package/dist/shared/units.js.map +1 -1
- package/dist/viewport/store.d.ts +1 -0
- package/dist/viewport/store.js +4 -0
- package/dist/viewport/store.js.map +1 -1
- package/package.json +7 -7
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scale-mode-with-free-transform.js","names":["origin: Position"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type ModeProps,\n ScaleMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport type { Position } from 'geojson';\n\ntype ScaleFactors = {\n scaleX: number;\n scaleY: number;\n};\n\ntype ScaleContext = {\n origin: Position;\n scaleFactors: ScaleFactors;\n geometry: FeatureCollection;\n};\n\n/**\n * Extends ScaleMode to support non-uniform (free) scaling.\n *\n * ## Features\n * - Default: Free scaling - can stretch/squish in any direction\n * - With modeConfig.lockScaling = true: Uniform scaling (maintains aspect ratio)\n *\n * ## How Non-Uniform Scaling Works\n *\n * Non-uniform scaling applies separate X and Y scale factors based on cursor\n * movement relative to the opposite corner (the \"origin\" or anchor point).\n *\n * ```\n * Origin (opposite corner) Drag Handle (start)\n * ●───────────────────────────────────●\n * │ │\n * │ startDelta = start - origin │\n * │ currentDelta = current - origin\n * │ │\n * │ scaleX = currentDeltaX / startDeltaX\n * │ scaleY = currentDeltaY / startDeltaY\n * │ │\n * ●───────────────────────────────────● Cursor (current)\n * ```\n *\n * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`\n *\n * ## Why We Override Parent's Uniform Scaling\n *\n * The parent ScaleMode calculates uniform scale factors in screen coordinates,\n * which can distort rotated shapes. We calculate our own uniform factor using\n * vector projection to ensure the corner follows the cursor's projection onto\n * the original drag line, preserving shape orientation.\n *\n * ## Minimum Scale Clamping\n *\n * All scale factors are clamped to a minimum of 0.01 to prevent:\n * - Shape inversion (negative scale flipping the shape inside-out)\n * - Shape collapse (scale of 0 making the shape a point/line)\n */\nexport class ScaleModeWithFreeTransform extends ScaleMode {\n /**\n * Override handleDragging to support non-uniform scaling.\n * When lockScaling is false (default), applies separate X/Y scale factors.\n * When lockScaling is true, applies uniform scaling (same factor for X and Y).\n *\n * Note: We don't use parent's handleDragging for uniform scaling because it\n * calculates scale factors in screen coordinates which distorts rotated shapes.\n * Instead, we calculate our own uniform scale factor based on distance ratios.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isScaling) {\n return;\n }\n\n props.onUpdateCursor(self._cursor);\n\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n // Calculate based on distance from origin to preserve aspect ratio\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaling',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final scaled geometry.\n * Uses the same uniform/non-uniform scaling logic as handleDragging.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isScaling) {\n this.emitFinalScaledGeometry(event, props, self);\n this.resetScaleState(props, self);\n }\n }\n\n /**\n * Get the scale context (origin, scale factors, geometry) for the current drag.\n * Returns null if required data is not available.\n */\n private getScaleContext(\n event: DraggingEvent | StopDraggingEvent,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ): ScaleContext | null {\n if (!self._selectedEditHandle) {\n return null;\n }\n\n const oppositeHandle = self._getOppositeScaleHandle(\n self._selectedEditHandle,\n );\n if (!oppositeHandle) {\n return null;\n }\n\n const geometry = self._geometryBeingScaled;\n if (!geometry) {\n return null;\n }\n\n const origin: Position = oppositeHandle.geometry.coordinates;\n const scaleFactors = this.calculateScaleFactors(event, origin);\n\n return { origin, scaleFactors, geometry };\n }\n\n /**\n * Calculate separate X and Y scale factors based on cursor movement.\n *\n * ## Math Explanation\n *\n * For each axis, we compute: `scale = currentDelta / startDelta`\n *\n * Where:\n * - `startDelta` = distance from origin to where drag started (the handle position)\n * - `currentDelta` = distance from origin to current cursor position\n *\n * Example: If the handle started 100px from origin and cursor is now 150px from origin,\n * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).\n *\n * ## Edge Cases\n * - If `startDelta` is near zero (handle very close to origin), we return scale=1\n * to avoid division by zero and prevent erratic behavior\n * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion\n */\n private calculateScaleFactors(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): ScaleFactors {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Calculate deltas from the anchor point (origin) to drag positions\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Epsilon for near-zero checks to avoid division by zero\n const epsilon = 0.0000001;\n // Minimum scale to prevent shape from collapsing or inverting\n const minScale = 0.01;\n\n // Scale = ratio of (current distance from origin) / (original distance from origin)\n // If original distance is near-zero, default to scale=1 (no change)\n const rawScaleX =\n Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;\n const rawScaleY =\n Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;\n\n // Clamp to prevent negative values (which would invert/flip the shape)\n const scaleX = Math.max(rawScaleX, minScale);\n const scaleY = Math.max(rawScaleY, minScale);\n\n return { scaleX, scaleY };\n }\n\n /**\n * Calculate a uniform scale factor for aspect-ratio-preserving scaling.\n *\n * ## Why Use Projection Instead of Simple Distance Ratio?\n *\n * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`\n *\n * But this fails for diagonal movements - if you drag a corner handle and move\n * perpendicular to the diagonal, the simple distance changes even though you\n * don't want the shape to scale.\n *\n * ## Vector Projection Math\n *\n * Instead, we project the cursor position onto the line defined by\n * (origin → start drag point). This way, only movement along the original\n * drag direction affects the scale.\n *\n * ```\n * Origin Start (drag handle)\n * ●──────────────────●\n * \\\n * \\ Cursor moved diagonally\n * ●\n * /\n * / Projected point (what we measure from)\n * ●──────────────────●\n * Origin Projection\n * ```\n *\n * The projection formula uses the dot product:\n * ```\n * projectedDist = (current · start) / |start|\n * scale = projectedDist / |start|\n * ```\n *\n * This equals: `scale = (current · start) / |start|²`\n */\n private calculateUniformScaleFactor(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): number {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Vector from origin to start drag point (defines the scaling direction)\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Vector from origin to current cursor position\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Distance from origin to start point (|start|)\n const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);\n\n if (startDist < 0.0000001) {\n return 1;\n }\n\n // Dot product: current · start = |current| × |start| × cos(θ)\n // This gives us the component of 'current' that lies along 'start'\n const dotProduct =\n currentDeltaX * startDeltaX + currentDeltaY * startDeltaY;\n\n // Project current point onto start vector: projectedDist = (current · start) / |start|\n const projectedDist = dotProduct / startDist;\n\n // Scale factor = projectedDist / |start| = (current · start) / |start|²\n // Clamp to minScale to prevent negative values (shape inversion)\n const minScale = 0.01;\n const rawScale = projectedDist / startDist;\n return Math.max(rawScale, minScale);\n }\n\n /**\n * Emit the final scaled geometry when dragging stops.\n */\n private emitFinalScaledGeometry(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaled',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n }\n\n /**\n * Reset the scale state after dragging stops.\n */\n private resetScaleState(\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n props.onUpdateCursor(null);\n self._geometryBeingScaled = null;\n self._selectedEditHandle = null;\n self._cursor = null;\n self._isScaling = false;\n }\n\n /**\n * Apply non-uniform scaling to geometry.\n * Transforms each coordinate by scaling X and Y independently around the origin.\n */\n private applyNonUniformScale(\n geometry: FeatureCollection,\n scaleX: number,\n scaleY: number,\n origin: Position,\n ): FeatureCollection {\n const scaledFeatures = geometry.features.map((feature) => {\n const scaledGeometry = this.scaleGeometry(\n feature.geometry,\n scaleX,\n scaleY,\n origin,\n );\n return {\n ...feature,\n geometry: scaledGeometry,\n };\n });\n\n return {\n type: 'FeatureCollection',\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON feature type variance\n features: scaledFeatures as any,\n };\n }\n\n /**\n * Scale a geometry's coordinates non-uniformly around an origin point.\n *\n * ## Coordinate Transformation\n *\n * Each coordinate is transformed using: `new = origin + (old - origin) × scale`\n *\n * This is equivalent to:\n * 1. Translate so origin is at (0,0): `temp = old - origin`\n * 2. Scale: `temp = temp × scale`\n * 3. Translate back: `new = temp + origin`\n *\n * The origin (opposite corner from the drag handle) stays fixed while\n * all other points move proportionally.\n */\n private scaleGeometry(\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - includes Point, LineString, Polygon, Multi* variants\n geometry: any,\n scaleX: number,\n scaleY: number,\n origin: Position,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - return type varies by input\n ): any {\n // Transform a single coordinate around the origin point\n const scaleCoord = (coord: Position): Position => {\n return [\n (origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX,\n (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY,\n ];\n };\n\n switch (geometry.type) {\n case 'Point':\n return {\n ...geometry,\n coordinates: scaleCoord(geometry.coordinates),\n };\n case 'LineString':\n case 'MultiPoint':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map(scaleCoord),\n };\n case 'Polygon':\n case 'MultiLineString':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring: Position[]) =>\n ring.map(scaleCoord),\n ),\n };\n case 'MultiPolygon':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon: Position[][]) =>\n polygon.map((ring: Position[]) => ring.map(scaleCoord)),\n ),\n };\n default:\n return geometry;\n }\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scale-mode-with-free-transform.js","names":["origin: Position"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type DraggingEvent,\n type FeatureCollection,\n type ModeProps,\n ScaleMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport type { Position } from 'geojson';\n\ntype ScaleFactors = {\n scaleX: number;\n scaleY: number;\n};\n\ntype ScaleContext = {\n origin: Position;\n scaleFactors: ScaleFactors;\n geometry: FeatureCollection;\n};\n\n/**\n * Extends ScaleMode to support non-uniform (free) scaling.\n *\n * ## Features\n * - **Default**: Free scaling - can stretch/squish in any direction\n * - **With Shift**: Uniform scaling (maintains aspect ratio)\n *\n * ## How Non-Uniform Scaling Works\n *\n * Non-uniform scaling applies separate X and Y scale factors based on cursor\n * movement relative to the opposite corner (the \"origin\" or anchor point).\n *\n * ```\n * Origin (opposite corner) Drag Handle (start)\n * ●───────────────────────────────────●\n * │ │\n * │ startDelta = start - origin │\n * │ currentDelta = current - origin\n * │ │\n * │ scaleX = currentDeltaX / startDeltaX\n * │ scaleY = currentDeltaY / startDeltaY\n * │ │\n * ●───────────────────────────────────● Cursor (current)\n * ```\n *\n * Each coordinate is transformed: `newCoord = origin + (oldCoord - origin) × scale`\n *\n * ## Why We Override Parent's Uniform Scaling\n *\n * The parent ScaleMode calculates uniform scale factors in screen coordinates,\n * which can distort rotated shapes. We calculate our own uniform factor using\n * vector projection to ensure the corner follows the cursor's projection onto\n * the original drag line, preserving shape orientation.\n *\n * ## Minimum Scale Clamping\n *\n * All scale factors are clamped to a minimum of 0.01 to prevent:\n * - Shape inversion (negative scale flipping the shape inside-out)\n * - Shape collapse (scale of 0 making the shape a point/line)\n *\n * @example\n * ```typescript\n * import { ScaleModeWithFreeTransform } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * const mode = new ScaleModeWithFreeTransform();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: featureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * modeConfig: {\n * lockScaling: false, // Default: free scaling (stretch/squish)\n * // lockScaling: true, // Hold Shift: uniform scaling (maintain aspect ratio)\n * },\n * });\n * ```\n */\nexport class ScaleModeWithFreeTransform extends ScaleMode {\n /**\n * Override handleDragging to support non-uniform scaling.\n * When lockScaling is false (default), applies separate X/Y scale factors.\n * When lockScaling is true, applies uniform scaling (same factor for X and Y).\n *\n * Note: We don't use parent's handleDragging for uniform scaling because it\n * calculates scale factors in screen coordinates which distorts rotated shapes.\n * Instead, we calculate our own uniform scale factor based on distance ratios.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isScaling) {\n return;\n }\n\n props.onUpdateCursor(self._cursor);\n\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n // Calculate based on distance from origin to preserve aspect ratio\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaling',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final scaled geometry.\n * Uses the same uniform/non-uniform scaling logic as handleDragging.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isScaling) {\n this.emitFinalScaledGeometry(event, props, self);\n this.resetScaleState(props, self);\n }\n }\n\n /**\n * Get the scale context (origin, scale factors, geometry) for the current drag.\n * Returns null if required data is not available.\n */\n private getScaleContext(\n event: DraggingEvent | StopDraggingEvent,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ): ScaleContext | null {\n if (!self._selectedEditHandle) {\n return null;\n }\n\n const oppositeHandle = self._getOppositeScaleHandle(\n self._selectedEditHandle,\n );\n if (!oppositeHandle) {\n return null;\n }\n\n const geometry = self._geometryBeingScaled;\n if (!geometry) {\n return null;\n }\n\n const origin: Position = oppositeHandle.geometry.coordinates;\n const scaleFactors = this.calculateScaleFactors(event, origin);\n\n return { origin, scaleFactors, geometry };\n }\n\n /**\n * Calculate separate X and Y scale factors based on cursor movement.\n *\n * ## Math Explanation\n *\n * For each axis, we compute: `scale = currentDelta / startDelta`\n *\n * Where:\n * - `startDelta` = distance from origin to where drag started (the handle position)\n * - `currentDelta` = distance from origin to current cursor position\n *\n * Example: If the handle started 100px from origin and cursor is now 150px from origin,\n * scale = 150/100 = 1.5 (shape grows to 150% of original size along that axis).\n *\n * ## Edge Cases\n * - If `startDelta` is near zero (handle very close to origin), we return scale=1\n * to avoid division by zero and prevent erratic behavior\n * - Negative scale values are clamped to 0.01 minimum to prevent shape inversion\n */\n private calculateScaleFactors(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): ScaleFactors {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Calculate deltas from the anchor point (origin) to drag positions\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Epsilon for near-zero checks to avoid division by zero\n const epsilon = 0.0000001;\n // Minimum scale to prevent shape from collapsing or inverting\n const minScale = 0.01;\n\n // Scale = ratio of (current distance from origin) / (original distance from origin)\n // If original distance is near-zero, default to scale=1 (no change)\n const rawScaleX =\n Math.abs(startDeltaX) > epsilon ? currentDeltaX / startDeltaX : 1;\n const rawScaleY =\n Math.abs(startDeltaY) > epsilon ? currentDeltaY / startDeltaY : 1;\n\n // Clamp to prevent negative values (which would invert/flip the shape)\n const scaleX = Math.max(rawScaleX, minScale);\n const scaleY = Math.max(rawScaleY, minScale);\n\n return { scaleX, scaleY };\n }\n\n /**\n * Calculate a uniform scale factor for aspect-ratio-preserving scaling.\n *\n * ## Why Use Projection Instead of Simple Distance Ratio?\n *\n * A naive approach would be: `scale = distance(origin, cursor) / distance(origin, start)`\n *\n * But this fails for diagonal movements - if you drag a corner handle and move\n * perpendicular to the diagonal, the simple distance changes even though you\n * don't want the shape to scale.\n *\n * ## Vector Projection Math\n *\n * Instead, we project the cursor position onto the line defined by\n * (origin → start drag point). This way, only movement along the original\n * drag direction affects the scale.\n *\n * ```\n * Origin Start (drag handle)\n * ●──────────────────●\n * \\\n * \\ Cursor moved diagonally\n * ●\n * /\n * / Projected point (what we measure from)\n * ●──────────────────●\n * Origin Projection\n * ```\n *\n * The projection formula uses the dot product:\n * ```\n * projectedDist = (current · start) / |start|\n * scale = projectedDist / |start|\n * ```\n *\n * This equals: `scale = (current · start) / |start|²`\n */\n private calculateUniformScaleFactor(\n event: DraggingEvent | StopDraggingEvent,\n origin: Position,\n ): number {\n const startDragPoint = event.pointerDownMapCoords;\n const currentPoint = event.mapCoords;\n\n // Vector from origin to start drag point (defines the scaling direction)\n const startDeltaX = (startDragPoint[0] ?? 0) - (origin[0] ?? 0);\n const startDeltaY = (startDragPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Vector from origin to current cursor position\n const currentDeltaX = (currentPoint[0] ?? 0) - (origin[0] ?? 0);\n const currentDeltaY = (currentPoint[1] ?? 0) - (origin[1] ?? 0);\n\n // Distance from origin to start point (|start|)\n const startDist = Math.sqrt(startDeltaX ** 2 + startDeltaY ** 2);\n\n if (startDist < 0.0000001) {\n return 1;\n }\n\n // Dot product: current · start = |current| × |start| × cos(θ)\n // This gives us the component of 'current' that lies along 'start'\n const dotProduct =\n currentDeltaX * startDeltaX + currentDeltaY * startDeltaY;\n\n // Project current point onto start vector: projectedDist = (current · start) / |start|\n const projectedDist = dotProduct / startDist;\n\n // Scale factor = projectedDist / |start| = (current · start) / |start|²\n // Clamp to minScale to prevent negative values (shape inversion)\n const minScale = 0.01;\n const rawScale = projectedDist / startDist;\n return Math.max(rawScale, minScale);\n }\n\n /**\n * Emit the final scaled geometry when dragging stops.\n */\n private emitFinalScaledGeometry(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n const scaleContext = this.getScaleContext(event, self);\n if (!scaleContext) {\n return;\n }\n\n const lockScaling = props.modeConfig?.lockScaling ?? false;\n const { origin, scaleFactors, geometry } = scaleContext;\n\n // For uniform scaling, use a single scale factor for both axes\n const finalScaleX = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleX;\n const finalScaleY = lockScaling\n ? this.calculateUniformScaleFactor(event, origin)\n : scaleFactors.scaleY;\n\n const scaledFeatures = this.applyNonUniformScale(\n geometry,\n finalScaleX,\n finalScaleY,\n origin,\n );\n\n const updatedData = self._getUpdatedData(props, scaledFeatures);\n\n props.onEdit({\n updatedData,\n editType: 'scaled',\n editContext: {\n featureIndexes: props.selectedIndexes,\n },\n });\n }\n\n /**\n * Reset the scale state after dragging stops.\n */\n private resetScaleState(\n props: ModeProps<FeatureCollection>,\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n self: any,\n ) {\n props.onUpdateCursor(null);\n self._geometryBeingScaled = null;\n self._selectedEditHandle = null;\n self._cursor = null;\n self._isScaling = false;\n }\n\n /**\n * Apply non-uniform scaling to geometry.\n * Transforms each coordinate by scaling X and Y independently around the origin.\n */\n private applyNonUniformScale(\n geometry: FeatureCollection,\n scaleX: number,\n scaleY: number,\n origin: Position,\n ): FeatureCollection {\n const scaledFeatures = geometry.features.map((feature) => {\n const scaledGeometry = this.scaleGeometry(\n feature.geometry,\n scaleX,\n scaleY,\n origin,\n );\n return {\n ...feature,\n geometry: scaledGeometry,\n };\n });\n\n return {\n type: 'FeatureCollection',\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON feature type variance\n features: scaledFeatures as any,\n };\n }\n\n /**\n * Scale a geometry's coordinates non-uniformly around an origin point.\n *\n * ## Coordinate Transformation\n *\n * Each coordinate is transformed using: `new = origin + (old - origin) × scale`\n *\n * This is equivalent to:\n * 1. Translate so origin is at (0,0): `temp = old - origin`\n * 2. Scale: `temp = temp × scale`\n * 3. Translate back: `new = temp + origin`\n *\n * The origin (opposite corner from the drag handle) stays fixed while\n * all other points move proportionally.\n */\n private scaleGeometry(\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - includes Point, LineString, Polygon, Multi* variants\n geometry: any,\n scaleX: number,\n scaleY: number,\n origin: Position,\n // biome-ignore lint/suspicious/noExplicitAny: GeoJSON geometry types are complex - return type varies by input\n ): any {\n // Transform a single coordinate around the origin point\n const scaleCoord = (coord: Position): Position => {\n return [\n (origin[0] ?? 0) + ((coord[0] ?? 0) - (origin[0] ?? 0)) * scaleX,\n (origin[1] ?? 0) + ((coord[1] ?? 0) - (origin[1] ?? 0)) * scaleY,\n ];\n };\n\n switch (geometry.type) {\n case 'Point':\n return {\n ...geometry,\n coordinates: scaleCoord(geometry.coordinates),\n };\n case 'LineString':\n case 'MultiPoint':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map(scaleCoord),\n };\n case 'Polygon':\n case 'MultiLineString':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((ring: Position[]) =>\n ring.map(scaleCoord),\n ),\n };\n case 'MultiPolygon':\n return {\n ...geometry,\n coordinates: geometry.coordinates.map((polygon: Position[][]) =>\n polygon.map((ring: Position[]) => ring.map(scaleCoord)),\n ),\n };\n default:\n return geometry;\n }\n }\n}\n"],"mappings":"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"}
|
|
@@ -24,6 +24,7 @@ import { featureCollection } from "@turf/helpers";
|
|
|
24
24
|
* Use this mode for shapes where individual vertices can be dragged to reshape
|
|
25
25
|
* the geometry. This provides the most flexibility for freeform shape editing.
|
|
26
26
|
*
|
|
27
|
+
* ## Capabilities
|
|
27
28
|
* This composite mode provides:
|
|
28
29
|
* - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry
|
|
29
30
|
* - **Translation** (TranslateMode): Drag the shape to move it
|
|
@@ -34,20 +35,43 @@ import { featureCollection } from "@turf/helpers";
|
|
|
34
35
|
* - Default: Free rotation
|
|
35
36
|
* - With Shift: Snap to 45° intervals
|
|
36
37
|
*
|
|
37
|
-
* Priority
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
38
|
+
* ## Handle Priority Logic
|
|
39
|
+
* When drag starts, modes are evaluated in this priority order:
|
|
40
|
+
* 1. If hovering over a vertex (edit handle) → vertex editing takes priority
|
|
41
|
+
* 2. If hovering over a scale handle → scaling takes priority
|
|
42
|
+
* 3. If hovering over the rotate handle → rotation takes priority
|
|
43
|
+
* 4. Otherwise → dragging the shape translates it
|
|
42
44
|
*
|
|
43
|
-
*
|
|
45
|
+
* ## Visual Behavior
|
|
46
|
+
* The guides from all modes are combined, showing both vertex handles (white circles
|
|
47
|
+
* on existing points) and transform handles (corner/rotation handles on bounding box).
|
|
44
48
|
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* Note: This mode does not show tooltips during editing because arbitrary polygons
|
|
49
|
-
* don't have meaningful dimensions to display. Use BoundingTransformMode for shapes
|
|
49
|
+
* ## Tooltips
|
|
50
|
+
* This mode does not show live measurement tooltips during editing because arbitrary
|
|
51
|
+
* polygons don't have well-defined dimensions. Use BoundingTransformMode for shapes
|
|
50
52
|
* like rectangles and ellipses where dimension tooltips are useful.
|
|
53
|
+
*
|
|
54
|
+
* ## Rectangle Special Handling
|
|
55
|
+
* For rectangles, vertex handles are hidden to preserve rotation and right angles.
|
|
56
|
+
* Only scale/rotate/translate handles are shown. Consider using BoundingTransformMode
|
|
57
|
+
* directly for rectangles if vertex editing should never be available.
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```typescript
|
|
61
|
+
* import { VertexTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode';
|
|
62
|
+
* import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';
|
|
63
|
+
*
|
|
64
|
+
* // Used internally by EditShapeLayer for polygons and lines
|
|
65
|
+
* const mode = new VertexTransformMode();
|
|
66
|
+
*
|
|
67
|
+
* const layer = new EditableGeoJsonLayer({
|
|
68
|
+
* mode,
|
|
69
|
+
* data: polygonFeatureCollection,
|
|
70
|
+
* selectedFeatureIndexes: [0],
|
|
71
|
+
* onEdit: handleEdit,
|
|
72
|
+
* // ... other props
|
|
73
|
+
* });
|
|
74
|
+
* ```
|
|
51
75
|
*/
|
|
52
76
|
var VertexTransformMode = class extends BaseTransformMode {
|
|
53
77
|
modifyMode;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vertex-transform-mode.js","names":["guidesToFilterOut: string[]"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n ModifyMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\n\n/**\n * Transform mode for shapes that support vertex editing (polygons and lines).\n *\n * Use this mode for shapes where individual vertices can be dragged to reshape\n * the geometry. This provides the most flexibility for freeform shape editing.\n *\n * This composite mode provides:\n * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry\n * - **Translation** (TranslateMode): Drag the shape to move it\n * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize\n * - Default: Non-uniform scaling (can stretch/squish)\n * - With Shift: Uniform scaling (maintains aspect ratio)\n * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate\n * - Default: Free rotation\n * - With Shift: Snap to 45° intervals\n *\n * Priority
|
|
1
|
+
{"version":3,"file":"vertex-transform-mode.js","names":["guidesToFilterOut: string[]"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n type FeatureCollection,\n type GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n ModifyMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\n\n/**\n * Transform mode for shapes that support vertex editing (polygons and lines).\n *\n * Use this mode for shapes where individual vertices can be dragged to reshape\n * the geometry. This provides the most flexibility for freeform shape editing.\n *\n * ## Capabilities\n * This composite mode provides:\n * - **Vertex editing** (ModifyMode): Drag vertices to reshape the geometry\n * - **Translation** (TranslateMode): Drag the shape to move it\n * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize\n * - Default: Non-uniform scaling (can stretch/squish)\n * - With Shift: Uniform scaling (maintains aspect ratio)\n * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate\n * - Default: Free rotation\n * - With Shift: Snap to 45° intervals\n *\n * ## Handle Priority Logic\n * When drag starts, modes are evaluated in this priority order:\n * 1. If hovering over a vertex (edit handle) → vertex editing takes priority\n * 2. If hovering over a scale handle → scaling takes priority\n * 3. If hovering over the rotate handle → rotation takes priority\n * 4. Otherwise → dragging the shape translates it\n *\n * ## Visual Behavior\n * The guides from all modes are combined, showing both vertex handles (white circles\n * on existing points) and transform handles (corner/rotation handles on bounding box).\n *\n * ## Tooltips\n * This mode does not show live measurement tooltips during editing because arbitrary\n * polygons don't have well-defined dimensions. Use BoundingTransformMode for shapes\n * like rectangles and ellipses where dimension tooltips are useful.\n *\n * ## Rectangle Special Handling\n * For rectangles, vertex handles are hidden to preserve rotation and right angles.\n * Only scale/rotate/translate handles are shown. Consider using BoundingTransformMode\n * directly for rectangles if vertex editing should never be available.\n *\n * @example\n * ```typescript\n * import { VertexTransformMode } from '@accelint/map-toolkit/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode';\n * import { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\n *\n * // Used internally by EditShapeLayer for polygons and lines\n * const mode = new VertexTransformMode();\n *\n * const layer = new EditableGeoJsonLayer({\n * mode,\n * data: polygonFeatureCollection,\n * selectedFeatureIndexes: [0],\n * onEdit: handleEdit,\n * // ... other props\n * });\n * ```\n */\nexport class VertexTransformMode extends BaseTransformMode {\n private modifyMode: ModifyMode;\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const modifyMode = new ModifyMode();\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order matters: first mode to handle the event wins\n // We put modify first so vertex handles take priority over translate\n super([modifyMode, scaleMode, rotateMode, translateMode]);\n\n this.modifyMode = modifyMode;\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Vertex handle: existing point on polygon/line\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'existing',\n ),\n mode: this.modifyMode,\n // No shift config - vertex editing doesn't have modifiers\n },\n {\n // Scale handle: corner handles on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide && pick.object?.properties?.editHandleType === 'scale',\n ),\n mode: this.scaleMode,\n shiftConfig: { configKey: 'lockScaling' },\n },\n {\n // Rotate handle: top handle on bounding box\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.editHandleType === 'rotate',\n ),\n mode: this.rotateMode,\n shiftConfig: { configKey: 'snapRotation' },\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides and handle rectangles.\n *\n * Both ScaleMode and RotateMode render the same bounding box envelope.\n * We keep scale's envelope and filter rotate's duplicate.\n * We also hide scale handles while rotating to avoid visual clutter.\n *\n * For rectangles, we hide vertex handles because vertex editing would distort\n * the shape or force axis-alignment. Rectangles should use scale handles only.\n */\n override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n // Get guides from all modes (base class handles pick filtering)\n const allGuides = super.getGuides(props);\n\n // Check if we're editing a rectangle - rectangles have shape: 'Rectangle' property\n const isRectangle =\n props.data.features[0]?.properties?.shape === 'Rectangle';\n\n // biome-ignore lint/suspicious/noExplicitAny: Guide properties vary by mode, safely accessing with optional chaining\n const filteredGuides = allGuides.features.filter((guide: any) => {\n const properties = guide.properties || {};\n const editHandleType = properties.editHandleType;\n const guideType = properties.guideType;\n const mode = properties.mode;\n\n // Both scale and rotate modes have the same enveloping box as a guide - only need one\n const guidesToFilterOut: string[] = [mode as string];\n\n // Do not render scaling edit handles if rotating\n if (this.rotateMode.getIsRotating()) {\n guidesToFilterOut.push(editHandleType as string);\n }\n\n // For rectangles, hide ModifyMode vertex handles (editHandleType: 'existing')\n // Rectangles should only use scale handles for resizing to preserve rotation\n // Vertex editing would either distort the shape or force axis-alignment\n if (\n isRectangle &&\n guideType === 'editHandle' &&\n editHandleType === 'existing'\n ) {\n return false;\n }\n\n return !guidesToFilterOut.includes('scale');\n });\n\n // biome-ignore lint/suspicious/noExplicitAny: turf types mismatch with editable-layers GeoJSON types\n return featureCollection(filteredGuides as any) as any;\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgFA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,YAAY;EACnC,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAI3C,QAAM;GAAC;GAAY;GAAW;GAAY;GAAc,CAAC;AAEzD,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO;GACL;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,WAC/C;IACH,MAAM,KAAK;IAEZ;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,eAAe;IAC1C;GACD;IAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;IACH,MAAM,KAAK;IACX,aAAa,EAAE,WAAW,gBAAgB;IAC3C;GACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;;;;;;;;CAad,AAAS,UACP,OACwB;EAExB,MAAM,YAAY,MAAM,UAAU,MAAM;EAGxC,MAAM,cACJ,MAAM,KAAK,SAAS,IAAI,YAAY,UAAU;AAgChD,SAAO,kBA7BgB,UAAU,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAClC,MAAM,YAAY,WAAW;GAI7B,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAMlD,OACE,eACA,cAAc,gBACd,mBAAmB,WAEnB,QAAO;AAGT,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C"}
|
|
@@ -29,7 +29,7 @@ import { getLogger } from "@accelint/logger";
|
|
|
29
29
|
* Manages editing state for shape modification.
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
|
-
* ```
|
|
32
|
+
* ```typescript
|
|
33
33
|
* import { editStore } from '@accelint/map-toolkit/deckgl/shapes';
|
|
34
34
|
*
|
|
35
35
|
* function EditControls({ mapId }) {
|
|
@@ -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 * ```tsx\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 { getLogger } from '@accelint/logger';\nimport { createMapStore } from '@/shared/create-map-store';\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 = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[EditShapeLayer]',\n pretty: true,\n});\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};\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 */\nfunction getEditModeForShape(shape: Shape): EditMode {\n if (isPointShape(shape)) {\n return '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 });\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 });\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 */\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 */\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 */\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 */\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 */\nexport function cancelEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).cancel();\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 { getLogger } from '@accelint/logger';\nimport { createMapStore } from '@/shared/create-map-store';\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 = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[EditShapeLayer]',\n pretty: true,\n});\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};\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 */\nfunction getEditModeForShape(shape: Shape): EditMode {\n if (isPointShape(shape)) {\n return '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 });\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 });\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 */\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 */\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 */\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 */\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 */\nexport function cancelEditingFromLayer(mapId: UniqueId): void {\n editStore.actions(mapId).cancel();\n}\n"],"mappings":"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"}
|
|
@@ -29,6 +29,18 @@ import { useEmit } from "@accelint/bus/react";
|
|
|
29
29
|
*
|
|
30
30
|
* @param mapId - The map instance ID
|
|
31
31
|
* @param isActive - Whether the hook should be active (e.g., when editing/drawing)
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
* ```typescript
|
|
35
|
+
* function EditShapeLayer({ mapId }) {
|
|
36
|
+
* const isEditing = editStore.use(mapId).state?.editingShape != null;
|
|
37
|
+
*
|
|
38
|
+
* // Disable zoom when editing to allow Shift modifiers to work
|
|
39
|
+
* useShiftZoomDisable(mapId, isEditing);
|
|
40
|
+
*
|
|
41
|
+
* // ... rest of component
|
|
42
|
+
* }
|
|
43
|
+
* ```
|
|
32
44
|
*/
|
|
33
45
|
function useShiftZoomDisable(mapId, isActive) {
|
|
34
46
|
const emitDisableZoom = useEmit(MapEvents.disableZoom);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-shift-zoom-disable.js","names":[],"sources":["../../../../../src/deckgl/shapes/shared/hooks/use-shift-zoom-disable.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 { useEmit } from '@accelint/bus/react';\nimport { useEffect, useRef } from 'react';\nimport { MapEvents } from '@/deckgl/base-map/events';\nimport type { UniqueId } from '@accelint/core';\nimport type {\n MapDisableZoomEvent,\n MapEnableZoomEvent,\n} from '@/deckgl/base-map/types';\n\n/**\n * Hook to disable map zoom while Shift key is held during shape operations.\n *\n * This prevents MapLibre's boxZoom (Shift+drag) from interfering with\n * Shift modifier constraints like:\n * - Shift for uniform scaling during edit\n * - Shift for rotation snap during edit\n * - Shift for square constraint during rectangle drawing\n *\n * @param mapId - The map instance ID\n * @param isActive - Whether the hook should be active (e.g., when editing/drawing)\n */\nexport function useShiftZoomDisable(mapId: UniqueId, isActive: boolean): void {\n const emitDisableZoom = useEmit<MapDisableZoomEvent>(MapEvents.disableZoom);\n const emitEnableZoom = useEmit<MapEnableZoomEvent>(MapEvents.enableZoom);\n const isZoomDisabledRef = useRef(false);\n\n useEffect(() => {\n if (!isActive) {\n return;\n }\n\n const disableZoom = () => {\n if (!isZoomDisabledRef.current) {\n isZoomDisabledRef.current = true;\n emitDisableZoom({ id: mapId });\n }\n };\n\n const enableZoom = () => {\n if (isZoomDisabledRef.current) {\n isZoomDisabledRef.current = false;\n emitEnableZoom({ id: mapId });\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Shift') {\n disableZoom();\n }\n };\n\n const handleKeyUp = (event: KeyboardEvent) => {\n if (event.key === 'Shift') {\n enableZoom();\n }\n };\n\n // Also catch Shift state on mousedown to handle edge cases where\n // keydown might have been missed (e.g., focus issues)\n const handleMouseDown = (event: MouseEvent) => {\n if (event.shiftKey) {\n disableZoom();\n }\n };\n\n // Re-enable zoom if the window loses focus while Shift is held\n const handleBlur = () => {\n enableZoom();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('keyup', handleKeyUp);\n document.addEventListener('mousedown', handleMouseDown, { capture: true });\n window.addEventListener('blur', handleBlur);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('keyup', handleKeyUp);\n document.removeEventListener('mousedown', handleMouseDown, {\n capture: true,\n });\n window.removeEventListener('blur', handleBlur);\n\n // Ensure zoom is re-enabled when unmounting\n enableZoom();\n };\n }, [isActive, mapId, emitDisableZoom, emitEnableZoom]);\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"use-shift-zoom-disable.js","names":[],"sources":["../../../../../src/deckgl/shapes/shared/hooks/use-shift-zoom-disable.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 { useEmit } from '@accelint/bus/react';\nimport { useEffect, useRef } from 'react';\nimport { MapEvents } from '@/deckgl/base-map/events';\nimport type { UniqueId } from '@accelint/core';\nimport type {\n MapDisableZoomEvent,\n MapEnableZoomEvent,\n} from '@/deckgl/base-map/types';\n\n/**\n * Hook to disable map zoom while Shift key is held during shape operations.\n *\n * This prevents MapLibre's boxZoom (Shift+drag) from interfering with\n * Shift modifier constraints like:\n * - Shift for uniform scaling during edit\n * - Shift for rotation snap during edit\n * - Shift for square constraint during rectangle drawing\n *\n * @param mapId - The map instance ID\n * @param isActive - Whether the hook should be active (e.g., when editing/drawing)\n *\n * @example\n * ```typescript\n * function EditShapeLayer({ mapId }) {\n * const isEditing = editStore.use(mapId).state?.editingShape != null;\n *\n * // Disable zoom when editing to allow Shift modifiers to work\n * useShiftZoomDisable(mapId, isEditing);\n *\n * // ... rest of component\n * }\n * ```\n */\nexport function useShiftZoomDisable(mapId: UniqueId, isActive: boolean): void {\n const emitDisableZoom = useEmit<MapDisableZoomEvent>(MapEvents.disableZoom);\n const emitEnableZoom = useEmit<MapEnableZoomEvent>(MapEvents.enableZoom);\n const isZoomDisabledRef = useRef(false);\n\n useEffect(() => {\n if (!isActive) {\n return;\n }\n\n const disableZoom = () => {\n if (!isZoomDisabledRef.current) {\n isZoomDisabledRef.current = true;\n emitDisableZoom({ id: mapId });\n }\n };\n\n const enableZoom = () => {\n if (isZoomDisabledRef.current) {\n isZoomDisabledRef.current = false;\n emitEnableZoom({ id: mapId });\n }\n };\n\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === 'Shift') {\n disableZoom();\n }\n };\n\n const handleKeyUp = (event: KeyboardEvent) => {\n if (event.key === 'Shift') {\n enableZoom();\n }\n };\n\n // Also catch Shift state on mousedown to handle edge cases where\n // keydown might have been missed (e.g., focus issues)\n const handleMouseDown = (event: MouseEvent) => {\n if (event.shiftKey) {\n disableZoom();\n }\n };\n\n // Re-enable zoom if the window loses focus while Shift is held\n const handleBlur = () => {\n enableZoom();\n };\n\n document.addEventListener('keydown', handleKeyDown);\n document.addEventListener('keyup', handleKeyUp);\n document.addEventListener('mousedown', handleMouseDown, { capture: true });\n window.addEventListener('blur', handleBlur);\n\n return () => {\n document.removeEventListener('keydown', handleKeyDown);\n document.removeEventListener('keyup', handleKeyUp);\n document.removeEventListener('mousedown', handleMouseDown, {\n capture: true,\n });\n window.removeEventListener('blur', handleBlur);\n\n // Ensure zoom is re-enabled when unmounting\n enableZoom();\n };\n }, [isActive, mapId, emitDisableZoom, emitEnableZoom]);\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA+CA,SAAgB,oBAAoB,OAAiB,UAAyB;CAC5E,MAAM,kBAAkB,QAA6B,UAAU,YAAY;CAC3E,MAAM,iBAAiB,QAA4B,UAAU,WAAW;CACxE,MAAM,oBAAoB,OAAO,MAAM;AAEvC,iBAAgB;AACd,MAAI,CAAC,SACH;EAGF,MAAM,oBAAoB;AACxB,OAAI,CAAC,kBAAkB,SAAS;AAC9B,sBAAkB,UAAU;AAC5B,oBAAgB,EAAE,IAAI,OAAO,CAAC;;;EAIlC,MAAM,mBAAmB;AACvB,OAAI,kBAAkB,SAAS;AAC7B,sBAAkB,UAAU;AAC5B,mBAAe,EAAE,IAAI,OAAO,CAAC;;;EAIjC,MAAM,iBAAiB,UAAyB;AAC9C,OAAI,MAAM,QAAQ,QAChB,cAAa;;EAIjB,MAAM,eAAe,UAAyB;AAC5C,OAAI,MAAM,QAAQ,QAChB,aAAY;;EAMhB,MAAM,mBAAmB,UAAsB;AAC7C,OAAI,MAAM,SACR,cAAa;;EAKjB,MAAM,mBAAmB;AACvB,eAAY;;AAGd,WAAS,iBAAiB,WAAW,cAAc;AACnD,WAAS,iBAAiB,SAAS,YAAY;AAC/C,WAAS,iBAAiB,aAAa,iBAAiB,EAAE,SAAS,MAAM,CAAC;AAC1E,SAAO,iBAAiB,QAAQ,WAAW;AAE3C,eAAa;AACX,YAAS,oBAAoB,WAAW,cAAc;AACtD,YAAS,oBAAoB,SAAS,YAAY;AAClD,YAAS,oBAAoB,aAAa,iBAAiB,EACzD,SAAS,MACV,CAAC;AACF,UAAO,oBAAoB,QAAQ,WAAW;AAG9C,eAAY;;IAEb;EAAC;EAAU;EAAO;EAAiB;EAAe,CAAC"}
|
|
@@ -237,7 +237,7 @@ interface PointShape extends BaseShape {
|
|
|
237
237
|
*
|
|
238
238
|
* Use this for type narrowing based on shape:
|
|
239
239
|
* @example
|
|
240
|
-
* ```
|
|
240
|
+
* ```typescript
|
|
241
241
|
* function handleShape(shape: Shape) {
|
|
242
242
|
* if (shape.shape === 'Circle') {
|
|
243
243
|
* // TypeScript knows shape.feature.properties.circleProperties exists
|
|
@@ -276,7 +276,7 @@ type Subscription = (onStoreChange: () => void) => () => void;
|
|
|
276
276
|
* Type guard for Circle shapes.
|
|
277
277
|
*
|
|
278
278
|
* @example
|
|
279
|
-
* ```
|
|
279
|
+
* ```typescript
|
|
280
280
|
* if (isCircleShape(shape)) {
|
|
281
281
|
* // shape.feature.properties.circleProperties is available
|
|
282
282
|
* const { center, radius } = shape.feature.properties.circleProperties;
|
|
@@ -288,7 +288,7 @@ declare function isCircleShape(shape: Shape): shape is CircleShape;
|
|
|
288
288
|
* Type guard for Ellipse shapes.
|
|
289
289
|
*
|
|
290
290
|
* @example
|
|
291
|
-
* ```
|
|
291
|
+
* ```typescript
|
|
292
292
|
* if (isEllipseShape(shape)) {
|
|
293
293
|
* // shape.feature.properties.ellipseProperties is available
|
|
294
294
|
* const { center, xSemiAxis, ySemiAxis } = shape.feature.properties.ellipseProperties;
|
|
@@ -29,7 +29,7 @@ const ShapeFeatureType = {
|
|
|
29
29
|
* Type guard for Circle shapes.
|
|
30
30
|
*
|
|
31
31
|
* @example
|
|
32
|
-
* ```
|
|
32
|
+
* ```typescript
|
|
33
33
|
* if (isCircleShape(shape)) {
|
|
34
34
|
* // shape.feature.properties.circleProperties is available
|
|
35
35
|
* const { center, radius } = shape.feature.properties.circleProperties;
|
|
@@ -43,7 +43,7 @@ function isCircleShape(shape) {
|
|
|
43
43
|
* Type guard for Ellipse shapes.
|
|
44
44
|
*
|
|
45
45
|
* @example
|
|
46
|
-
* ```
|
|
46
|
+
* ```typescript
|
|
47
47
|
* if (isEllipseShape(shape)) {
|
|
48
48
|
* // shape.feature.properties.ellipseProperties is available
|
|
49
49
|
* const { center, xSemiAxis, ySemiAxis } = shape.feature.properties.ellipseProperties;
|
|
@@ -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 { Feature, LineString, Point, Polygon } 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\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 interface 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 interface CircleProperties {\n /** Center point as [longitude, latitude] */\n center: [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 interface EllipseProperties {\n /** Center point as [longitude, latitude] */\n center: [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 * Custom geometry types supported\n */\nexport type CustomGeometry = Point | LineString | Polygon;\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 interface 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}\n\n/**\n * Feature properties for Circle shapes (circleProperties required).\n * Used by CircleShape for better type narrowing.\n */\nexport interface CircleFeatureProperties extends 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 interface EllipseFeatureProperties extends StyledFeatureProperties {\n /** Ellipse properties (required for Ellipse shapes) */\n ellipseProperties: EllipseProperties;\n}\n\n/**\n * GeoJSON Feature with style properties\n */\nexport interface StyledFeature extends Feature {\n properties: StyledFeatureProperties;\n}\n\n/**\n * Base shape properties shared by all shapes\n */\ninterface 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 interface CircleShape extends BaseShape {\n shape: typeof ShapeFeatureType.Circle;\n feature: StyledFeature & { properties: CircleFeatureProperties };\n}\n\n/**\n * Ellipse shape with required ellipseProperties\n */\nexport interface EllipseShape extends BaseShape {\n shape: typeof ShapeFeatureType.Ellipse;\n feature: StyledFeature & { properties: EllipseFeatureProperties };\n}\n\n/**\n * Polygon shape\n */\nexport interface PolygonShape extends BaseShape {\n shape: typeof ShapeFeatureType.Polygon;\n feature: StyledFeature;\n}\n\n/**\n * Rectangle shape\n */\nexport interface RectangleShape extends BaseShape {\n shape: typeof ShapeFeatureType.Rectangle;\n feature: StyledFeature;\n}\n\n/**\n * LineString shape\n */\nexport interface LineStringShape extends BaseShape {\n shape: typeof ShapeFeatureType.LineString;\n feature: StyledFeature;\n}\n\n/**\n * Point shape\n */\nexport interface PointShape extends 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 * ```ts\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 ShapeFeatureType values\n */\nexport type ShapeFeatureTypeValues = ShapeFeatureType;\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 * Coordinate as [longitude, latitude]\n */\nexport type Coordinate = [number, number];\n\n/**\n * Function type for subscription (useSyncExternalStore pattern).\n * Used by draw-shape-layer and edit-shape-layer stores.\n */\nexport type Subscription = (onStoreChange: () => void) => () => void;\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Type guard for Circle shapes.\n *\n * @example\n * ```ts\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 * @example\n * ```ts\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 */\nexport function isPolygonShape(shape: Shape): shape is PolygonShape {\n return shape.shape === ShapeFeatureType.Polygon;\n}\n\n/**\n * Type guard for Rectangle shapes.\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 */\nexport function isLineStringShape(shape: Shape): shape is LineStringShape {\n return shape.shape === ShapeFeatureType.LineString;\n}\n\n/**\n * Type guard for Point shapes.\n */\nexport function isPointShape(shape: Shape): shape is PointShape {\n return shape.shape === ShapeFeatureType.Point;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,MAAa,mBAAmB;CAC9B,QAAQ;CACR,SAAS;CACT,SAAS;CACT,WAAW;CACX,YAAY;CACZ,OAAO;CACR;;;;;;;;;;;;AAwSD,SAAgB,cAAc,OAAoC;AAChE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;AAc1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,iBAAiB,OAAuC;AACtE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,kBAAkB,OAAwC;AACxE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,aAAa,OAAmC;AAC9D,QAAO,MAAM,UAAU,iBAAiB"}
|
|
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 { Feature, LineString, Point, Polygon } 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\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 interface 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 interface CircleProperties {\n /** Center point as [longitude, latitude] */\n center: [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 interface EllipseProperties {\n /** Center point as [longitude, latitude] */\n center: [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 * Custom geometry types supported\n */\nexport type CustomGeometry = Point | LineString | Polygon;\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 interface 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}\n\n/**\n * Feature properties for Circle shapes (circleProperties required).\n * Used by CircleShape for better type narrowing.\n */\nexport interface CircleFeatureProperties extends 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 interface EllipseFeatureProperties extends StyledFeatureProperties {\n /** Ellipse properties (required for Ellipse shapes) */\n ellipseProperties: EllipseProperties;\n}\n\n/**\n * GeoJSON Feature with style properties\n */\nexport interface StyledFeature extends Feature {\n properties: StyledFeatureProperties;\n}\n\n/**\n * Base shape properties shared by all shapes\n */\ninterface 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 interface CircleShape extends BaseShape {\n shape: typeof ShapeFeatureType.Circle;\n feature: StyledFeature & { properties: CircleFeatureProperties };\n}\n\n/**\n * Ellipse shape with required ellipseProperties\n */\nexport interface EllipseShape extends BaseShape {\n shape: typeof ShapeFeatureType.Ellipse;\n feature: StyledFeature & { properties: EllipseFeatureProperties };\n}\n\n/**\n * Polygon shape\n */\nexport interface PolygonShape extends BaseShape {\n shape: typeof ShapeFeatureType.Polygon;\n feature: StyledFeature;\n}\n\n/**\n * Rectangle shape\n */\nexport interface RectangleShape extends BaseShape {\n shape: typeof ShapeFeatureType.Rectangle;\n feature: StyledFeature;\n}\n\n/**\n * LineString shape\n */\nexport interface LineStringShape extends BaseShape {\n shape: typeof ShapeFeatureType.LineString;\n feature: StyledFeature;\n}\n\n/**\n * Point shape\n */\nexport interface PointShape extends 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 ShapeFeatureType values\n */\nexport type ShapeFeatureTypeValues = ShapeFeatureType;\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 * Coordinate as [longitude, latitude]\n */\nexport type Coordinate = [number, number];\n\n/**\n * Function type for subscription (useSyncExternalStore pattern).\n * Used by draw-shape-layer and edit-shape-layer stores.\n */\nexport type Subscription = (onStoreChange: () => void) => () => void;\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Type guard for Circle shapes.\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 * @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 */\nexport function isPolygonShape(shape: Shape): shape is PolygonShape {\n return shape.shape === ShapeFeatureType.Polygon;\n}\n\n/**\n * Type guard for Rectangle shapes.\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 */\nexport function isLineStringShape(shape: Shape): shape is LineStringShape {\n return shape.shape === ShapeFeatureType.LineString;\n}\n\n/**\n * Type guard for Point shapes.\n */\nexport function isPointShape(shape: Shape): shape is PointShape {\n return shape.shape === ShapeFeatureType.Point;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;AAsBA,MAAa,mBAAmB;CAC9B,QAAQ;CACR,SAAS;CACT,SAAS;CACT,WAAW;CACX,YAAY;CACZ,OAAO;CACR;;;;;;;;;;;;AAwSD,SAAgB,cAAc,OAAoC;AAChE,QAAO,MAAM,UAAU,iBAAiB;;;;;;;;;;;;;AAc1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,eAAe,OAAqC;AAClE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,iBAAiB,OAAuC;AACtE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,kBAAkB,OAAwC;AACxE,QAAO,MAAM,UAAU,iBAAiB;;;;;AAM1C,SAAgB,aAAa,OAAmC;AAC9D,QAAO,MAAM,UAAU,iBAAiB"}
|
|
@@ -25,7 +25,7 @@ import { distance, point } from "@turf/turf";
|
|
|
25
25
|
* @returns Circle measurements including radius, diameter, and area
|
|
26
26
|
*
|
|
27
27
|
* @example
|
|
28
|
-
* ```
|
|
28
|
+
* ```typescript
|
|
29
29
|
* const { radius, diameter, area } = computeCircleMeasurements(
|
|
30
30
|
* [-122.4, 37.8],
|
|
31
31
|
* [-122.3, 37.8],
|
|
@@ -56,7 +56,7 @@ function computeCircleMeasurements(center, edgePoint, units) {
|
|
|
56
56
|
* @returns Rectangle measurements including width, height, and area
|
|
57
57
|
*
|
|
58
58
|
* @example
|
|
59
|
-
* ```
|
|
59
|
+
* ```typescript
|
|
60
60
|
* const coords = polygon.geometry.coordinates[0];
|
|
61
61
|
* const { width, height, area } = computeRectangleMeasurementsFromCorners(
|
|
62
62
|
* coords[0] as [number, number],
|
|
@@ -86,7 +86,7 @@ function computeRectangleMeasurementsFromCorners(corner0, corner1, corner2, unit
|
|
|
86
86
|
* @returns Ellipse measurements including axes and area
|
|
87
87
|
*
|
|
88
88
|
* @example
|
|
89
|
-
* ```
|
|
89
|
+
* ```typescript
|
|
90
90
|
* const coords = polygon.geometry.coordinates[0];
|
|
91
91
|
* const { majorAxis, minorAxis, area } = computeEllipseMeasurementsFromPolygon(
|
|
92
92
|
* coords as [number, number][],
|
|
@@ -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\n'use client';\n\nimport { distance, point } from '@turf/turf';\nimport type { DistanceUnit } from '@/shared/units';\n\n/**\n * Circle measurement result containing radius, diameter, and area.\n */\nexport interface 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 interface 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 interface 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 * ```
|
|
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\n'use client';\n\nimport { distance, point } from '@turf/turf';\nimport type { DistanceUnit } from '@/shared/units';\n\n/**\n * Circle measurement result containing radius, diameter, and area.\n */\nexport interface 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 interface 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 interface 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 center and 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 width = distance(point(corner0), point(corner1), { units });\n const height = distance(point(corner1), point(corner2), { units });\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): { majorAxis: number; minorAxis: number; area: number } {\n // Remove the closing point if it duplicates the first\n const points =\n coordinates[0]?.[0] === coordinates[coordinates.length - 1]?.[0] &&\n coordinates[0]?.[1] === coordinates[coordinates.length - 1]?.[1]\n ? coordinates.slice(0, -1)\n : coordinates;\n\n if (points.length < 4) {\n return { 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(points.length / 2);\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 = points[i];\n const p2 = points[i + halfLen];\n if (!(p1 && p2)) {\n continue;\n }\n\n const dist = distance(\n [p1[0] as number, p1[1] as number],\n [p2[0] as number, p2[1] as number],\n { units },\n );\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 ellipseArea = Math.PI * semiMajor * semiMinor;\n\n return { majorAxis, minorAxis, area: ellipseArea };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0EA,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,QAAQ,SAAS,MAAM,QAAQ,EAAE,MAAM,QAAQ,EAAE,EAAE,OAAO,CAAC;CACjE,MAAM,SAAS,SAAS,MAAM,QAAQ,EAAE,MAAM,QAAQ,EAAE,EAAE,OAAO,CAAC;AAGlE,QAAO;EAAE;EAAO;EAAQ,MAFX,QAAQ;EAES;;;;;;;;;;;;;;;;;;;;;AAsBhC,SAAgB,sCACd,aACA,OACwD;CAExD,MAAM,SACJ,YAAY,KAAK,OAAO,YAAY,YAAY,SAAS,KAAK,MAC9D,YAAY,KAAK,OAAO,YAAY,YAAY,SAAS,KAAK,KAC1D,YAAY,MAAM,GAAG,GAAG,GACxB;AAEN,KAAI,OAAO,SAAS,EAClB,QAAO;EAAE,WAAW;EAAG,WAAW;EAAG,MAAM;EAAG;CAIhD,MAAM,UAAU,KAAK,MAAM,OAAO,SAAS,EAAE;CAC7C,IAAI,UAAU;CACd,IAAI,UAAU,OAAO;AAGrB,MAAK,IAAI,IAAI,GAAG,IAAI,SAAS,KAAK;EAChC,MAAM,KAAK,OAAO;EAClB,MAAM,KAAK,OAAO,IAAI;AACtB,MAAI,EAAE,MAAM,IACV;EAGF,MAAM,OAAO,SACX,CAAC,GAAG,IAAc,GAAG,GAAa,EAClC,CAAC,GAAG,IAAc,GAAG,GAAa,EAClC,EAAE,OAAO,CACV;AAED,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;EAAE;EAAW;EAAW,MAFX,KAAK,KAAK,YAAY;EAEQ"}
|
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* @returns Object containing filtered picks and whether filtering occurred
|
|
29
29
|
*
|
|
30
30
|
* @example
|
|
31
|
-
* ```
|
|
31
|
+
* ```typescript
|
|
32
32
|
* const picks = props.lastPointerMoveEvent?.picks;
|
|
33
33
|
* if (picks && picks.length > 0) {
|
|
34
34
|
* const { filteredPicks, didFilter } = filterGeometryAwarePicks(picks);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pick-filtering.js","names":["filteredPicks: T[]"],"sources":["../../../../../src/deckgl/shapes/shared/utils/pick-filtering.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 * Result of filtering picks for geometry-aware processing.\n * Generic to support both deck.gl/core PickingInfo and editable-layers Pick types.\n */\nexport interface FilteredPicksResult<T> {\n /** The filtered picks array (only valid geometry picks) */\n filteredPicks: T[];\n /** Whether any picks were removed during filtering */\n didFilter: boolean;\n}\n\n/**\n * Filters picks to only include valid geometry elements.\n *\n * This prevents TypeError from sublayer elements that don't have geometry\n * when modes try to access pick.object.geometry.type. Only picks that are\n * either guide features (pick.isGuide) or have valid geometry are included.\n *\n * Uses a single-pass algorithm for efficiency - filters and tracks changes\n * in one loop iteration.\n *\n * @param picks - The picks array from a pointer event\n * @returns Object containing filtered picks and whether filtering occurred\n *\n * @example\n * ```
|
|
1
|
+
{"version":3,"file":"pick-filtering.js","names":["filteredPicks: T[]"],"sources":["../../../../../src/deckgl/shapes/shared/utils/pick-filtering.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 * Result of filtering picks for geometry-aware processing.\n * Generic to support both deck.gl/core PickingInfo and editable-layers Pick types.\n */\nexport interface FilteredPicksResult<T> {\n /** The filtered picks array (only valid geometry picks) */\n filteredPicks: T[];\n /** Whether any picks were removed during filtering */\n didFilter: boolean;\n}\n\n/**\n * Filters picks to only include valid geometry elements.\n *\n * This prevents TypeError from sublayer elements that don't have geometry\n * when modes try to access pick.object.geometry.type. Only picks that are\n * either guide features (pick.isGuide) or have valid geometry are included.\n *\n * Uses a single-pass algorithm for efficiency - filters and tracks changes\n * in one loop iteration.\n *\n * @param picks - The picks array from a pointer event\n * @returns Object containing filtered picks and whether filtering occurred\n *\n * @example\n * ```typescript\n * const picks = props.lastPointerMoveEvent?.picks;\n * if (picks && picks.length > 0) {\n * const { filteredPicks, didFilter } = filterGeometryAwarePicks(picks);\n * if (didFilter) {\n * // Use filteredPicks in modified props\n * }\n * }\n * ```\n */\nexport function filterGeometryAwarePicks<T>(\n picks: T[],\n): FilteredPicksResult<T> {\n const filteredPicks: T[] = [];\n let didFilter = false;\n\n for (const pick of picks) {\n // Keep picks that are guides or have valid geometry\n // biome-ignore lint/suspicious/noExplicitAny: deck.gl picks have dynamic object structure\n const pickObj = pick as any;\n if (pickObj.isGuide || pickObj.object?.geometry?.type !== undefined) {\n filteredPicks.push(pick);\n } else {\n didFilter = true;\n }\n }\n\n return { filteredPicks, didFilter };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,yBACd,OACwB;CACxB,MAAMA,gBAAqB,EAAE;CAC7B,IAAI,YAAY;AAEhB,MAAK,MAAM,QAAQ,OAAO;EAGxB,MAAM,UAAU;AAChB,MAAI,QAAQ,WAAW,QAAQ,QAAQ,UAAU,SAAS,OACxD,eAAc,KAAK,KAAK;MAExB,aAAY;;AAIhB,QAAO;EAAE;EAAe;EAAW"}
|
|
@@ -17,6 +17,24 @@ declare global {
|
|
|
17
17
|
namespace React {
|
|
18
18
|
namespace JSX {
|
|
19
19
|
interface IntrinsicElements {
|
|
20
|
+
/**
|
|
21
|
+
* A Deck.gl Fiber layer for rendering MIL-STD-2525 and APP-6 military symbols.
|
|
22
|
+
*
|
|
23
|
+
* Automatically generates symbol icons from SIDC codes with support for
|
|
24
|
+
* multiple symbology standards and customizable rendering options.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```tsx
|
|
28
|
+
* <symbolLayer
|
|
29
|
+
* id="military-units"
|
|
30
|
+
* data={units}
|
|
31
|
+
* getSidc={d => d.sidc}
|
|
32
|
+
* getPosition={d => d.position}
|
|
33
|
+
* getSize={32}
|
|
34
|
+
* defaultSymbolOptions={{ colorMode: 'Dark' }}
|
|
35
|
+
* />
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
20
38
|
symbolLayer: SymbolLayerProps;
|
|
21
39
|
}
|
|
22
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fiber.js","names":[],"sources":["../../../src/deckgl/symbol-layer/fiber.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 { extend } from '@deckgl-fiber-renderer/dom';\nimport { SymbolLayer, type SymbolLayerProps } from './index';\n\nextend({ SymbolLayer });\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n symbolLayer: SymbolLayerProps;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,OAAO,EAAE,aAAa,CAAC"}
|
|
1
|
+
{"version":3,"file":"fiber.js","names":[],"sources":["../../../src/deckgl/symbol-layer/fiber.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 { extend } from '@deckgl-fiber-renderer/dom';\nimport { SymbolLayer, type SymbolLayerProps } from './index';\n\nextend({ SymbolLayer });\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n /**\n * A Deck.gl Fiber layer for rendering MIL-STD-2525 and APP-6 military symbols.\n *\n * Automatically generates symbol icons from SIDC codes with support for\n * multiple symbology standards and customizable rendering options.\n *\n * @example\n * ```tsx\n * <symbolLayer\n * id=\"military-units\"\n * data={units}\n * getSidc={d => d.sidc}\n * getPosition={d => d.position}\n * getSize={32}\n * defaultSymbolOptions={{ colorMode: 'Dark' }}\n * />\n * ```\n */\n symbolLayer: SymbolLayerProps;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,OAAO,EAAE,aAAa,CAAC"}
|