@accelint/map-toolkit 0.6.0 → 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (183) hide show
  1. package/CHANGELOG.md +68 -0
  2. package/catalog-info.yaml +5 -4
  3. package/dist/camera/index.d.ts +2 -2
  4. package/dist/camera/index.js +2 -2
  5. package/dist/camera/store.d.ts +120 -0
  6. package/dist/camera/store.js +279 -0
  7. package/dist/camera/store.js.map +1 -0
  8. package/dist/deckgl/base-map/constants.d.ts +1 -6
  9. package/dist/deckgl/base-map/constants.js +1 -6
  10. package/dist/deckgl/base-map/constants.js.map +1 -1
  11. package/dist/deckgl/base-map/controls.js +2 -0
  12. package/dist/deckgl/base-map/controls.js.map +1 -1
  13. package/dist/deckgl/base-map/index.d.ts +2 -2
  14. package/dist/deckgl/base-map/index.js +10 -11
  15. package/dist/deckgl/base-map/index.js.map +1 -1
  16. package/dist/deckgl/base-map/provider.js +1 -1
  17. package/dist/deckgl/index.d.ts +4 -4
  18. package/dist/deckgl/index.js +4 -4
  19. package/dist/deckgl/saved-viewports/storage.js +10 -2
  20. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  21. package/dist/deckgl/shapes/display-shape-layer/constants.js +5 -8
  22. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  23. package/dist/deckgl/shapes/display-shape-layer/index.d.ts +18 -14
  24. package/dist/deckgl/shapes/display-shape-layer/index.js +63 -30
  25. package/dist/deckgl/shapes/display-shape-layer/index.js.map +1 -1
  26. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js +2 -16
  27. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.js.map +1 -1
  28. package/dist/deckgl/shapes/display-shape-layer/store.js +58 -272
  29. package/dist/deckgl/shapes/display-shape-layer/store.js.map +1 -1
  30. package/dist/deckgl/shapes/display-shape-layer/types.d.ts +22 -11
  31. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.d.ts → use-select-shape.d.ts} +9 -9
  32. package/dist/deckgl/shapes/display-shape-layer/{use-shape-selection.js → use-select-shape.js} +12 -12
  33. package/dist/deckgl/shapes/display-shape-layer/use-select-shape.js.map +1 -0
  34. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +5 -66
  35. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  36. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +2 -65
  37. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +3 -121
  38. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  39. package/dist/deckgl/shapes/draw-shape-layer/constants.js +46 -0
  40. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -0
  41. package/dist/deckgl/shapes/draw-shape-layer/events.d.ts +92 -0
  42. package/dist/deckgl/shapes/draw-shape-layer/events.js +56 -0
  43. package/dist/deckgl/shapes/draw-shape-layer/events.js.map +1 -0
  44. package/dist/deckgl/shapes/draw-shape-layer/fiber.d.ts +11 -0
  45. package/dist/{maplibre/constants.js → deckgl/shapes/draw-shape-layer/fiber.js} +6 -12
  46. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -0
  47. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +53 -0
  48. package/dist/deckgl/shapes/draw-shape-layer/index.js +95 -0
  49. package/dist/deckgl/shapes/draw-shape-layer/index.js.map +1 -0
  50. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +51 -0
  51. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -0
  52. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +73 -0
  53. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -0
  54. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +87 -0
  55. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -0
  56. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +88 -0
  57. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -0
  58. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +77 -0
  59. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -0
  60. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +64 -0
  61. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -0
  62. package/dist/deckgl/shapes/draw-shape-layer/store.js +175 -0
  63. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -0
  64. package/dist/deckgl/shapes/draw-shape-layer/types.d.ts +86 -0
  65. package/dist/{viewport/constants.js → deckgl/shapes/draw-shape-layer/types.js} +1 -12
  66. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.d.ts +82 -0
  67. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js +112 -0
  68. package/dist/deckgl/shapes/draw-shape-layer/use-draw-shape.js.map +1 -0
  69. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +147 -0
  70. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -0
  71. package/dist/deckgl/shapes/edit-shape-layer/constants.js +41 -0
  72. package/dist/deckgl/shapes/edit-shape-layer/constants.js.map +1 -0
  73. package/dist/deckgl/shapes/edit-shape-layer/events.d.ts +92 -0
  74. package/dist/deckgl/shapes/edit-shape-layer/events.js +56 -0
  75. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -0
  76. package/dist/deckgl/shapes/edit-shape-layer/fiber.d.ts +13 -0
  77. package/dist/deckgl/shapes/edit-shape-layer/fiber.js +14 -0
  78. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +63 -0
  79. package/dist/deckgl/shapes/edit-shape-layer/index.js +162 -0
  80. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -0
  81. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +154 -0
  82. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -0
  83. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +147 -0
  84. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -0
  85. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +87 -0
  86. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -0
  87. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +61 -0
  88. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -0
  89. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +109 -0
  90. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -0
  91. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +289 -0
  92. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -0
  93. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +121 -0
  94. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -0
  95. package/dist/deckgl/shapes/edit-shape-layer/store.js +194 -0
  96. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -0
  97. package/dist/deckgl/shapes/edit-shape-layer/types.d.ts +93 -0
  98. package/dist/deckgl/shapes/edit-shape-layer/types.js +14 -0
  99. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.d.ts +82 -0
  100. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js +114 -0
  101. package/dist/deckgl/shapes/edit-shape-layer/use-edit-shape.js.map +1 -0
  102. package/dist/deckgl/shapes/index.d.ts +15 -6
  103. package/dist/deckgl/shapes/index.js +12 -5
  104. package/dist/deckgl/shapes/shared/constants.d.ts +27 -32
  105. package/dist/deckgl/shapes/shared/constants.js +189 -25
  106. package/dist/deckgl/shapes/shared/constants.js.map +1 -1
  107. package/dist/deckgl/shapes/shared/events.d.ts +1 -20
  108. package/dist/deckgl/shapes/shared/events.js +1 -31
  109. package/dist/deckgl/shapes/shared/events.js.map +1 -1
  110. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +84 -0
  111. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -0
  112. package/dist/deckgl/shapes/shared/types.d.ts +187 -28
  113. package/dist/deckgl/shapes/shared/types.js +55 -1
  114. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  115. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +128 -0
  116. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -0
  117. package/dist/deckgl/shapes/shared/utils/layer-config.js +50 -0
  118. package/dist/deckgl/shapes/shared/utils/layer-config.js.map +1 -0
  119. package/dist/deckgl/shapes/shared/utils/mode-utils.js +113 -0
  120. package/dist/deckgl/shapes/shared/utils/mode-utils.js.map +1 -0
  121. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +57 -0
  122. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -0
  123. package/dist/deckgl/shapes/shared/utils/style-utils.d.ts +64 -0
  124. package/dist/deckgl/shapes/shared/utils/style-utils.js +101 -0
  125. package/dist/deckgl/shapes/shared/utils/style-utils.js.map +1 -0
  126. package/dist/deckgl/text-layer/default-settings.js +4 -24
  127. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  128. package/dist/deckgl/text-settings.d.ts +77 -0
  129. package/dist/deckgl/text-settings.js +83 -0
  130. package/dist/deckgl/text-settings.js.map +1 -0
  131. package/dist/map-cursor/index.d.ts +2 -2
  132. package/dist/map-cursor/index.js +2 -2
  133. package/dist/map-cursor/store.d.ts +32 -61
  134. package/dist/map-cursor/store.js +165 -294
  135. package/dist/map-cursor/store.js.map +1 -1
  136. package/dist/map-cursor/use-map-cursor.d.ts +5 -2
  137. package/dist/map-cursor/use-map-cursor.js +33 -15
  138. package/dist/map-cursor/use-map-cursor.js.map +1 -1
  139. package/dist/map-mode/index.d.ts +2 -2
  140. package/dist/map-mode/index.js +2 -2
  141. package/dist/map-mode/store.d.ts +36 -37
  142. package/dist/map-mode/store.js +131 -237
  143. package/dist/map-mode/store.js.map +1 -1
  144. package/dist/map-mode/use-map-mode.js +6 -5
  145. package/dist/map-mode/use-map-mode.js.map +1 -1
  146. package/dist/maplibre/index.d.ts +2 -2
  147. package/dist/maplibre/index.js +2 -2
  148. package/dist/shared/constants.d.ts +19 -0
  149. package/dist/shared/constants.js +33 -0
  150. package/dist/shared/constants.js.map +1 -0
  151. package/dist/shared/create-map-store.d.ts +202 -0
  152. package/dist/shared/create-map-store.js +223 -0
  153. package/dist/shared/create-map-store.js.map +1 -0
  154. package/dist/shared/units.d.ts +39 -0
  155. package/dist/shared/units.js +49 -0
  156. package/dist/shared/units.js.map +1 -0
  157. package/dist/viewport/index.d.ts +3 -3
  158. package/dist/viewport/index.js +3 -3
  159. package/dist/viewport/store.d.ts +69 -0
  160. package/dist/viewport/store.js +125 -0
  161. package/dist/viewport/store.js.map +1 -0
  162. package/dist/viewport/types.d.ts +2 -2
  163. package/dist/viewport/utils.js +2 -2
  164. package/dist/viewport/utils.js.map +1 -1
  165. package/dist/viewport/viewport-size.d.ts +2 -2
  166. package/dist/viewport/viewport-size.js +2 -2
  167. package/dist/viewport/viewport-size.js.map +1 -1
  168. package/package.json +36 -18
  169. package/dist/camera/use-camera-state.d.ts +0 -153
  170. package/dist/camera/use-camera-state.js +0 -418
  171. package/dist/camera/use-camera-state.js.map +0 -1
  172. package/dist/deckgl/shapes/display-shape-layer/constants.d.ts +0 -44
  173. package/dist/deckgl/shapes/display-shape-layer/shape-label-layer.d.ts +0 -66
  174. package/dist/deckgl/shapes/display-shape-layer/store.d.ts +0 -87
  175. package/dist/deckgl/shapes/display-shape-layer/use-shape-selection.js.map +0 -1
  176. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.d.ts +0 -61
  177. package/dist/maplibre/constants.d.ts +0 -13
  178. package/dist/maplibre/constants.js.map +0 -1
  179. package/dist/viewport/constants.d.ts +0 -11
  180. package/dist/viewport/constants.js.map +0 -1
  181. package/dist/viewport/use-viewport-state.d.ts +0 -100
  182. package/dist/viewport/use-viewport-state.js +0 -222
  183. package/dist/viewport/use-viewport-state.js.map +0 -1
@@ -0,0 +1,147 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { formatEllipseTooltip, formatRectangleTooltip } from "../../shared/constants.js";
15
+ import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
16
+ import { computeEllipseMeasurementsFromPolygon, computeRectangleMeasurementsFromCorners } from "../../shared/utils/geometry-measurements.js";
17
+ import { BaseTransformMode } from "./base-transform-mode.js";
18
+ import { RotateModeWithSnap } from "./rotate-mode-with-snap.js";
19
+ import { ScaleModeWithFreeTransform } from "./scale-mode-with-free-transform.js";
20
+ import { TranslateMode } from "@deck.gl-community/editable-layers";
21
+ import { featureCollection } from "@turf/helpers";
22
+
23
+ //#region src/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.ts
24
+ /**
25
+ * Transform mode for shapes that use bounding box manipulation (no vertex editing).
26
+ *
27
+ * Use this mode for shapes like ellipses and rectangles where individual vertex
28
+ * editing is not meaningful or desired. Instead, shapes are manipulated via their
29
+ * bounding box handles.
30
+ *
31
+ * This composite mode provides:
32
+ * - **Translation** (TranslateMode): Drag the shape body to move it
33
+ * - **Scaling** (ScaleModeWithFreeTransform): Drag corner handles to resize
34
+ * - Default: Non-uniform scaling (can stretch/squish)
35
+ * - With Shift: Uniform scaling (maintains aspect ratio)
36
+ * - **Rotation** (RotateModeWithSnap): Drag top handle to rotate
37
+ * - Default: Free rotation
38
+ * - With Shift: Snap to 45° intervals
39
+ * - **Live tooltip**: Shows dimensions and area during scaling
40
+ *
41
+ * Unlike VertexTransformMode, this mode does NOT include vertex editing handles.
42
+ *
43
+ * Priority logic:
44
+ * - If hovering over a scale handle, scaling takes priority
45
+ * - If hovering over the rotate handle, rotation takes priority
46
+ * - Otherwise, dragging the shape body translates it
47
+ */
48
+ var BoundingTransformMode = class extends BaseTransformMode {
49
+ translateMode;
50
+ scaleMode;
51
+ rotateMode;
52
+ constructor() {
53
+ const translateMode = new TranslateMode();
54
+ const scaleMode = new ScaleModeWithFreeTransform();
55
+ const rotateMode = new RotateModeWithSnap();
56
+ super([
57
+ scaleMode,
58
+ rotateMode,
59
+ translateMode
60
+ ]);
61
+ this.translateMode = translateMode;
62
+ this.scaleMode = scaleMode;
63
+ this.rotateMode = rotateMode;
64
+ }
65
+ getHandleMatchers() {
66
+ return [{
67
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.editHandleType === "scale"),
68
+ mode: this.scaleMode,
69
+ shiftConfig: { configKey: "lockScaling" }
70
+ }, {
71
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.editHandleType === "rotate"),
72
+ mode: this.rotateMode,
73
+ shiftConfig: { configKey: "snapRotation" }
74
+ }];
75
+ }
76
+ getDefaultMode() {
77
+ return this.translateMode;
78
+ }
79
+ /**
80
+ * Update tooltip with shape dimensions during scaling.
81
+ */
82
+ onDragging(event, props) {
83
+ if (this.activeDragMode !== this.scaleMode) return;
84
+ this.updateShapeTooltip(event, props);
85
+ }
86
+ /**
87
+ * Override getGuides to filter duplicate envelope guides.
88
+ *
89
+ * Both ScaleMode and RotateMode render the same bounding box envelope.
90
+ * We keep scale's envelope and filter rotate's duplicate.
91
+ * We also hide scale handles while rotating to avoid visual clutter.
92
+ */
93
+ getGuides(props) {
94
+ return featureCollection(super.getGuides(props).features.filter((guide) => {
95
+ const properties = guide.properties || {};
96
+ const editHandleType = properties.editHandleType;
97
+ const guidesToFilterOut = [properties.mode];
98
+ if (this.rotateMode.getIsRotating()) guidesToFilterOut.push(editHandleType);
99
+ return !guidesToFilterOut.includes("scale");
100
+ }));
101
+ }
102
+ /**
103
+ * Update the tooltip with shape dimensions and area.
104
+ * Called during scaling to show live measurements.
105
+ * Handles both rectangles (5 points) and ellipses (many points).
106
+ */
107
+ updateShapeTooltip(event, props) {
108
+ const { mapCoords } = event;
109
+ const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
110
+ const selectedIndex = props.selectedIndexes?.[0];
111
+ if (selectedIndex === void 0) {
112
+ this.tooltip = null;
113
+ return;
114
+ }
115
+ const feature = props.data.features[selectedIndex];
116
+ if (!feature || feature.geometry.type !== "Polygon") {
117
+ this.tooltip = null;
118
+ return;
119
+ }
120
+ const coordinates = feature.geometry.coordinates[0];
121
+ if (!coordinates || coordinates.length < 4) {
122
+ this.tooltip = null;
123
+ return;
124
+ }
125
+ const isRectangle = feature.properties?.shape === "Rectangle";
126
+ let text;
127
+ const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);
128
+ if (isRectangle) {
129
+ const corner0 = coordinates[0];
130
+ const corner1 = coordinates[1];
131
+ const corner2 = coordinates[2];
132
+ const { width, height, area } = computeRectangleMeasurementsFromCorners(corner0, corner1, corner2, distanceUnits);
133
+ text = formatRectangleTooltip(width, height, area, unitAbbrev);
134
+ } else {
135
+ const { majorAxis, minorAxis, area: ellipseArea } = computeEllipseMeasurementsFromPolygon(coordinates, distanceUnits);
136
+ text = formatEllipseTooltip(majorAxis, minorAxis, ellipseArea, unitAbbrev);
137
+ }
138
+ this.tooltip = {
139
+ position: mapCoords,
140
+ text
141
+ };
142
+ }
143
+ };
144
+
145
+ //#endregion
146
+ export { BoundingTransformMode };
147
+ //# sourceMappingURL=bounding-transform-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bounding-transform-mode.js","names":["guidesToFilterOut: string[]","text: string"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2025 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 GeoJsonEditMode,\n type GuideFeatureCollection,\n type ModeProps,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { featureCollection } from '@turf/helpers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatEllipseTooltip,\n formatRectangleTooltip,\n} from '../../shared/constants';\nimport {\n computeEllipseMeasurementsFromPolygon,\n computeRectangleMeasurementsFromCorners,\n} from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport { RotateModeWithSnap } from './rotate-mode-with-snap';\nimport { ScaleModeWithFreeTransform } from './scale-mode-with-free-transform';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for shapes that use bounding box manipulation (no vertex editing).\n *\n * Use this mode for shapes like ellipses and rectangles where individual vertex\n * editing is not meaningful or desired. Instead, shapes are manipulated via their\n * bounding box handles.\n *\n * This composite mode provides:\n * - **Translation** (TranslateMode): Drag the shape body 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 * - **Live tooltip**: Shows dimensions and area during scaling\n *\n * Unlike VertexTransformMode, this mode does NOT include vertex editing handles.\n *\n * Priority logic:\n * - If hovering over a scale handle, scaling takes priority\n * - If hovering over the rotate handle, rotation takes priority\n * - Otherwise, dragging the shape body translates it\n */\nexport class BoundingTransformMode extends BaseTransformMode {\n private translateMode: TranslateMode;\n private scaleMode: ScaleModeWithFreeTransform;\n private rotateMode: RotateModeWithSnap;\n\n constructor() {\n const translateMode = new TranslateMode();\n const scaleMode = new ScaleModeWithFreeTransform();\n const rotateMode = new RotateModeWithSnap();\n\n // Order: scale and rotate first so their handles take priority over translate\n super([scaleMode, rotateMode, translateMode]);\n\n this.translateMode = translateMode;\n this.scaleMode = scaleMode;\n this.rotateMode = rotateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\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 * Update tooltip with shape dimensions during scaling.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when scaling\n if (this.activeDragMode !== this.scaleMode) {\n return;\n }\n\n this.updateShapeTooltip(event, props);\n }\n\n /**\n * Override getGuides to filter duplicate envelope guides.\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 override getGuides(\n props: ModeProps<FeatureCollection>,\n ): GuideFeatureCollection {\n const allGuides = super.getGuides(props);\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 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 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 /**\n * Update the tooltip with shape dimensions and area.\n * Called during scaling to show live measurements.\n * Handles both rectangles (5 points) and ellipses (many points).\n */\n private updateShapeTooltip(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 4) {\n this.tooltip = null;\n return;\n }\n\n // Check if this is a rectangle (has shape: 'Rectangle' property)\n const isRectangle = feature.properties?.shape === 'Rectangle';\n\n let text: string;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n if (isRectangle) {\n // Rectangle: calculate width and height from corners\n const corner0 = coordinates[0] as [number, number];\n const corner1 = coordinates[1] as [number, number];\n const corner2 = coordinates[2] as [number, number];\n\n const { width, height, area } = computeRectangleMeasurementsFromCorners(\n corner0,\n corner1,\n corner2,\n distanceUnits,\n );\n\n text = formatRectangleTooltip(width, height, area, unitAbbrev);\n } else {\n // Ellipse: calculate major/minor axes using consolidated utility\n const {\n majorAxis,\n minorAxis,\n area: ellipseArea,\n } = computeEllipseMeasurementsFromPolygon(\n coordinates as [number, number][],\n distanceUnits,\n );\n\n text = formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n );\n }\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8DA,IAAa,wBAAb,cAA2C,kBAAkB;CAC3D,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,gBAAgB,IAAI,eAAe;EACzC,MAAM,YAAY,IAAI,4BAA4B;EAClD,MAAM,aAAa,IAAI,oBAAoB;AAG3C,QAAM;GAAC;GAAW;GAAY;GAAc,CAAC;AAE7C,OAAK,gBAAgB;AACrB,OAAK,YAAY;AACjB,OAAK,aAAa;;CAGpB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WAAW,KAAK,QAAQ,YAAY,mBAAmB,QAC7D;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,eAAe;GAC1C,EACD;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,mBAAmB,SAC/C;GACH,MAAM,KAAK;GACX,aAAa,EAAE,WAAW,gBAAgB;GAC3C,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,UAC/B;AAGF,OAAK,mBAAmB,OAAO,MAAM;;;;;;;;;CAUvC,AAAS,UACP,OACwB;AAqBxB,SAAO,kBApBW,MAAM,UAAU,MAAM,CAGP,SAAS,QAAQ,UAAe;GAC/D,MAAM,aAAa,MAAM,cAAc,EAAE;GACzC,MAAM,iBAAiB,WAAW;GAIlC,MAAMA,oBAA8B,CAHvB,WAAW,KAG4B;AAGpD,OAAI,KAAK,WAAW,eAAe,CACjC,mBAAkB,KAAK,eAAyB;AAGlD,UAAO,CAAC,kBAAkB,SAAS,QAAQ;IAC3C,CAG6C;;;;;;;CAQjD,AAAQ,mBACN,OACA,OACA;EACA,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAGF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,YAAY,UAAU;EAElD,IAAIC;EACJ,MAAM,aAAa,4BAA4B,cAAc;AAE7D,MAAI,aAAa;GAEf,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAC5B,MAAM,UAAU,YAAY;GAE5B,MAAM,EAAE,OAAO,QAAQ,SAAS,wCAC9B,SACA,SACA,SACA,cACD;AAED,UAAO,uBAAuB,OAAO,QAAQ,MAAM,WAAW;SACzD;GAEL,MAAM,EACJ,WACA,WACA,MAAM,gBACJ,sCACF,aACA,cACD;AAED,UAAO,qBACL,WACA,WACA,aACA,WACD;;AAIH,OAAK,UAAU;GACb,UAAU;GACV;GACD"}
@@ -0,0 +1,87 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { formatCircleTooltip } from "../../shared/constants.js";
15
+ import { DEFAULT_DISTANCE_UNITS, getDistanceUnitAbbreviation } from "../../../../shared/units.js";
16
+ import { computeCircleMeasurements } from "../../shared/utils/geometry-measurements.js";
17
+ import { BaseTransformMode } from "./base-transform-mode.js";
18
+ import { ResizeCircleMode, TranslateMode } from "@deck.gl-community/editable-layers";
19
+ import { centroid } from "@turf/turf";
20
+
21
+ //#region src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts
22
+ /**
23
+ * Transform mode for circles combining resize and translate.
24
+ *
25
+ * This composite mode provides:
26
+ * - **Resize** (ResizeCircleMode): Drag edge to resize from center
27
+ * - **Translation** (TranslateMode): Drag the circle body to move it
28
+ * - **Live tooltip**: Shows diameter and area during resize
29
+ *
30
+ * Priority logic:
31
+ * - If dragging on the edge/handle, resize takes priority
32
+ * - If dragging on the circle body, translate takes priority
33
+ */
34
+ var CircleTransformMode = class extends BaseTransformMode {
35
+ resizeMode;
36
+ translateMode;
37
+ constructor() {
38
+ const resizeMode = new ResizeCircleMode();
39
+ const translateMode = new TranslateMode();
40
+ super([resizeMode, translateMode]);
41
+ this.resizeMode = resizeMode;
42
+ this.translateMode = translateMode;
43
+ }
44
+ getHandleMatchers() {
45
+ return [{
46
+ match: (pick) => Boolean(pick.isGuide && pick.object?.properties?.guideType === "editHandle" && pick.object?.properties?.editHandleType === "intermediate"),
47
+ mode: this.resizeMode
48
+ }];
49
+ }
50
+ getDefaultMode() {
51
+ return this.translateMode;
52
+ }
53
+ /**
54
+ * Update the tooltip with circle diameter and area during resize.
55
+ */
56
+ onDragging(event, props) {
57
+ if (this.activeDragMode !== this.resizeMode) return;
58
+ const { mapCoords } = event;
59
+ const distanceUnits = props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;
60
+ const selectedIndex = props.selectedIndexes?.[0];
61
+ if (selectedIndex === void 0) {
62
+ this.tooltip = null;
63
+ return;
64
+ }
65
+ const feature = props.data.features[selectedIndex];
66
+ if (!feature || feature.geometry.type !== "Polygon") {
67
+ this.tooltip = null;
68
+ return;
69
+ }
70
+ const coordinates = feature.geometry.coordinates[0];
71
+ if (!coordinates || coordinates.length < 3) {
72
+ this.tooltip = null;
73
+ return;
74
+ }
75
+ const center = centroid(feature).geometry.coordinates;
76
+ const firstPoint = coordinates[0];
77
+ const { diameter, area: area$1 } = computeCircleMeasurements(center, firstPoint, distanceUnits);
78
+ this.tooltip = {
79
+ position: mapCoords,
80
+ text: formatCircleTooltip(diameter, area$1, getDistanceUnitAbbreviation(distanceUnits))
81
+ };
82
+ }
83
+ };
84
+
85
+ //#endregion
86
+ export { CircleTransformMode };
87
+ //# sourceMappingURL=circle-transform-mode.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"circle-transform-mode.js","names":["area"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.ts"],"sourcesContent":["/*\n * Copyright 2025 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 GeoJsonEditMode,\n type ModeProps,\n ResizeCircleMode,\n TranslateMode,\n} from '@deck.gl-community/editable-layers';\nimport { centroid } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\nimport { BaseTransformMode, type HandleMatcher } from './base-transform-mode';\nimport type { Feature, Polygon } from 'geojson';\n\n/**\n * Transform mode for circles combining resize and translate.\n *\n * This composite mode provides:\n * - **Resize** (ResizeCircleMode): Drag edge to resize from center\n * - **Translation** (TranslateMode): Drag the circle body to move it\n * - **Live tooltip**: Shows diameter and area during resize\n *\n * Priority logic:\n * - If dragging on the edge/handle, resize takes priority\n * - If dragging on the circle body, translate takes priority\n */\nexport class CircleTransformMode extends BaseTransformMode {\n private resizeMode: ResizeCircleMode;\n private translateMode: TranslateMode;\n\n constructor() {\n const resizeMode = new ResizeCircleMode();\n const translateMode = new TranslateMode();\n\n // Order matters: resize first so edge handles take priority\n super([resizeMode, translateMode]);\n\n this.resizeMode = resizeMode;\n this.translateMode = translateMode;\n }\n\n protected override getHandleMatchers(): HandleMatcher[] {\n return [\n {\n // Resize handle: intermediate point on circle edge\n match: (pick) =>\n Boolean(\n pick.isGuide &&\n pick.object?.properties?.guideType === 'editHandle' &&\n pick.object?.properties?.editHandleType === 'intermediate',\n ),\n mode: this.resizeMode,\n // No shift config - resize doesn't have modifiers\n },\n ];\n }\n\n protected override getDefaultMode(): GeoJsonEditMode {\n return this.translateMode;\n }\n\n /**\n * Update the tooltip with circle diameter and area during resize.\n */\n protected override onDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ): void {\n // Only show tooltip when resizing\n if (this.activeDragMode !== this.resizeMode) {\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n // Get the selected feature to calculate radius from its geometry\n const selectedIndexes = props.selectedIndexes;\n const selectedIndex = selectedIndexes?.[0];\n if (selectedIndex === undefined) {\n this.tooltip = null;\n return;\n }\n\n const feature = props.data.features[selectedIndex] as\n | Feature<Polygon>\n | undefined;\n if (!feature || feature.geometry.type !== 'Polygon') {\n this.tooltip = null;\n return;\n }\n\n // Calculate center and radius from the polygon geometry\n const coordinates = feature.geometry.coordinates[0];\n if (!coordinates || coordinates.length < 3) {\n this.tooltip = null;\n return;\n }\n\n const centerFeature = centroid(feature);\n const center = centerFeature.geometry.coordinates as [number, number];\n const firstPoint = coordinates[0] as [number, number];\n const { diameter, area } = computeCircleMeasurements(\n center,\n firstPoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n // Position tooltip at cursor - offset is applied via getPixelOffset in sublayer props\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(diameter, area, unitAbbrev),\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CA,IAAa,sBAAb,cAAyC,kBAAkB;CACzD,AAAQ;CACR,AAAQ;CAER,cAAc;EACZ,MAAM,aAAa,IAAI,kBAAkB;EACzC,MAAM,gBAAgB,IAAI,eAAe;AAGzC,QAAM,CAAC,YAAY,cAAc,CAAC;AAElC,OAAK,aAAa;AAClB,OAAK,gBAAgB;;CAGvB,AAAmB,oBAAqC;AACtD,SAAO,CACL;GAEE,QAAQ,SACN,QACE,KAAK,WACH,KAAK,QAAQ,YAAY,cAAc,gBACvC,KAAK,QAAQ,YAAY,mBAAmB,eAC/C;GACH,MAAM,KAAK;GAEZ,CACF;;CAGH,AAAmB,iBAAkC;AACnD,SAAO,KAAK;;;;;CAMd,AAAmB,WACjB,OACA,OACM;AAEN,MAAI,KAAK,mBAAmB,KAAK,WAC/B;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAIrC,MAAM,gBADkB,MAAM,kBACU;AACxC,MAAI,kBAAkB,QAAW;AAC/B,QAAK,UAAU;AACf;;EAGF,MAAM,UAAU,MAAM,KAAK,SAAS;AAGpC,MAAI,CAAC,WAAW,QAAQ,SAAS,SAAS,WAAW;AACnD,QAAK,UAAU;AACf;;EAIF,MAAM,cAAc,QAAQ,SAAS,YAAY;AACjD,MAAI,CAAC,eAAe,YAAY,SAAS,GAAG;AAC1C,QAAK,UAAU;AACf;;EAIF,MAAM,SADgB,SAAS,QAAQ,CACV,SAAS;EACtC,MAAM,aAAa,YAAY;EAC/B,MAAM,EAAE,UAAU,iBAAS,0BACzB,QACA,YACA,cACD;AAID,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,UAAUA,QALnB,4BAA4B,cAAc,CAKN;GACtD"}
@@ -0,0 +1,61 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { BoundingTransformMode } from "./bounding-transform-mode.js";
15
+ import { CircleTransformMode } from "./circle-transform-mode.js";
16
+ import { VertexTransformMode } from "./vertex-transform-mode.js";
17
+ import { TranslateMode, ViewMode } from "@deck.gl-community/editable-layers";
18
+
19
+ //#region src/deckgl/shapes/edit-shape-layer/modes/index.ts
20
+ /**
21
+ * Cached edit mode instances.
22
+ *
23
+ * CRITICAL: Mode instances must be cached at module level to prevent
24
+ * deck.gl assertion failures. Creating new mode instances on each render
25
+ * causes the EditableGeoJsonLayer to fail with assertion errors.
26
+ *
27
+ * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and
28
+ * TranslateMode for shapes without vertex editing (ellipses, rectangles),
29
+ * allowing non-uniform scaling plus rotate/translate via bounding box handles.
30
+ * Shows live dimension tooltips during scaling.
31
+ *
32
+ * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,
33
+ * RotateMode, and TranslateMode for shapes that support vertex editing
34
+ * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.
35
+ *
36
+ * CircleTransformMode combines ResizeCircleMode with TranslateMode
37
+ * for circles, allowing resize from edge plus drag to translate.
38
+ * Shows live diameter/area tooltips during resize.
39
+ *
40
+ * TranslateMode allows dragging to move the shape (used for points).
41
+ */
42
+ const EDIT_MODE_INSTANCES = {
43
+ view: new ViewMode(),
44
+ "bounding-transform": new BoundingTransformMode(),
45
+ "vertex-transform": new VertexTransformMode(),
46
+ "circle-transform": new CircleTransformMode(),
47
+ translate: new TranslateMode()
48
+ };
49
+ /**
50
+ * Get the cached mode instance for an edit mode.
51
+ *
52
+ * @param mode - The edit mode to get the instance for
53
+ * @returns The cached mode instance
54
+ */
55
+ function getEditModeInstance(mode) {
56
+ return EDIT_MODE_INSTANCES[mode];
57
+ }
58
+
59
+ //#endregion
60
+ export { getEditModeInstance };
61
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/index.ts"],"sourcesContent":["/*\n * Copyright 2025 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 { TranslateMode, ViewMode } from '@deck.gl-community/editable-layers';\nimport { BoundingTransformMode } from './bounding-transform-mode';\nimport { CircleTransformMode } from './circle-transform-mode';\nimport { VertexTransformMode } from './vertex-transform-mode';\nimport type { EditMode } from '../types';\n\n/**\n * Cached edit mode instances.\n *\n * CRITICAL: Mode instances must be cached at module level to prevent\n * deck.gl assertion failures. Creating new mode instances on each render\n * causes the EditableGeoJsonLayer to fail with assertion errors.\n *\n * BoundingTransformMode combines ScaleModeWithFreeTransform, RotateMode, and\n * TranslateMode for shapes without vertex editing (ellipses, rectangles),\n * allowing non-uniform scaling plus rotate/translate via bounding box handles.\n * Shows live dimension tooltips during scaling.\n *\n * VertexTransformMode combines ModifyMode with ScaleModeWithFreeTransform,\n * RotateMode, and TranslateMode for shapes that support vertex editing\n * (polygons, lines), allowing vertex manipulation plus scale/rotate/translate.\n *\n * CircleTransformMode combines ResizeCircleMode with TranslateMode\n * for circles, allowing resize from edge plus drag to translate.\n * Shows live diameter/area tooltips during resize.\n *\n * TranslateMode allows dragging to move the shape (used for points).\n */\nconst EDIT_MODE_INSTANCES = {\n view: new ViewMode(),\n 'bounding-transform': new BoundingTransformMode(),\n 'vertex-transform': new VertexTransformMode(),\n 'circle-transform': new CircleTransformMode(),\n translate: new TranslateMode(),\n} as const;\n\n/**\n * Get the cached mode instance for an edit mode.\n *\n * @param mode - The edit mode to get the instance for\n * @returns The cached mode instance\n */\nexport function getEditModeInstance(\n mode: EditMode,\n): (typeof EDIT_MODE_INSTANCES)[EditMode] {\n return EDIT_MODE_INSTANCES[mode];\n}\n\n/**\n * Get the ViewMode instance (for when not editing).\n *\n * @returns The cached ViewMode instance\n */\nexport function getViewModeInstance(): ViewMode {\n return EDIT_MODE_INSTANCES.view;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAwCA,MAAM,sBAAsB;CAC1B,MAAM,IAAI,UAAU;CACpB,sBAAsB,IAAI,uBAAuB;CACjD,oBAAoB,IAAI,qBAAqB;CAC7C,oBAAoB,IAAI,qBAAqB;CAC7C,WAAW,IAAI,eAAe;CAC/B;;;;;;;AAQD,SAAgB,oBACd,MACwC;AACxC,QAAO,oBAAoB"}
@@ -0,0 +1,109 @@
1
+ /*
2
+ * Copyright 2025 Hypergiant Galactic Systems Inc. All rights reserved.
3
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
4
+ * you may not use this file except in compliance with the License. You may obtain a copy
5
+ * of the License at https://www.apache.org/licenses/LICENSE-2.0
6
+ *
7
+ * Unless required by applicable law or agreed to in writing, software distributed under
8
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
9
+ * OF ANY KIND, either express or implied. See the License for the specific language
10
+ * governing permissions and limitations under the License.
11
+ */
12
+
13
+
14
+ import { ImmutableFeatureCollection, RotateMode } from "@deck.gl-community/editable-layers";
15
+ import { bearing, centroid, transformRotate } from "@turf/turf";
16
+
17
+ //#region src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.ts
18
+ /** Snap interval in degrees (45° = 8 positions around the circle) */
19
+ const SNAP_INTERVAL_DEGREES = 45;
20
+ /**
21
+ * Calculate the angle between two points relative to a centroid.
22
+ * Uses turfBearing for geographic bearing convention (matches parent RotateMode).
23
+ * Returns angle in degrees.
24
+ *
25
+ * Note: centroid must be a turf Point Feature (not just coordinates) to match
26
+ * the parent RotateMode's behavior exactly.
27
+ */
28
+ function getRotationAngle(centroidFeature, startPoint, endPoint) {
29
+ const bearing1 = bearing(centroidFeature, startPoint);
30
+ return bearing(centroidFeature, endPoint) - bearing1;
31
+ }
32
+ /**
33
+ * Snap an angle to the nearest interval.
34
+ */
35
+ function snapAngle(angle, interval) {
36
+ return Math.round(angle / interval) * interval;
37
+ }
38
+ /**
39
+ * Extends RotateMode to support snapping rotation to 45° intervals.
40
+ *
41
+ * Features:
42
+ * - Default: Free rotation
43
+ * - With modeConfig.snapRotation = true: Snap to 45° intervals (0°, 45°, 90°, etc.)
44
+ *
45
+ * This allows precise alignment of shapes to common angles.
46
+ */
47
+ var RotateModeWithSnap = class extends RotateMode {
48
+ /**
49
+ * Override handleDragging to support snapped rotation.
50
+ * When snapRotation is true, rotates to nearest 45° interval.
51
+ * When snapRotation is false, delegates to parent for standard rotation.
52
+ */
53
+ handleDragging(event, props) {
54
+ if (!(props.modeConfig?.snapRotation ?? false)) {
55
+ super.handleDragging(event, props);
56
+ return;
57
+ }
58
+ if (!this._isRotating) return;
59
+ const rotateAction = this.getRotateActionWithSnap(event.pointerDownMapCoords, event.mapCoords, "rotating", props);
60
+ if (rotateAction) props.onEdit(rotateAction);
61
+ event.cancelPan();
62
+ }
63
+ /**
64
+ * Override handleStopDragging to emit the final rotated geometry with snap.
65
+ * When snapRotation is false, delegates to parent for standard rotation.
66
+ */
67
+ handleStopDragging(event, props) {
68
+ if (!(props.modeConfig?.snapRotation ?? false)) {
69
+ super.handleStopDragging(event, props);
70
+ return;
71
+ }
72
+ const self = this;
73
+ if (self._isRotating) {
74
+ const rotateAction = this.getRotateActionWithSnap(event.pointerDownMapCoords, event.mapCoords, "rotated", props);
75
+ if (rotateAction) props.onEdit(rotateAction);
76
+ self._geometryBeingRotated = null;
77
+ self._selectedEditHandle = null;
78
+ self._isRotating = false;
79
+ }
80
+ }
81
+ /**
82
+ * Get a rotate action, optionally snapping to 45° intervals.
83
+ */
84
+ getRotateActionWithSnap(startDragPoint, currentPoint, editType, props) {
85
+ const self = this;
86
+ if (!self._geometryBeingRotated) return null;
87
+ const geometry = self._geometryBeingRotated;
88
+ const centerFeature = centroid(geometry);
89
+ let angle = getRotationAngle(centerFeature, startDragPoint, currentPoint);
90
+ if (props.modeConfig?.snapRotation ?? false) angle = snapAngle(angle, SNAP_INTERVAL_DEGREES);
91
+ const rotatedFeatures = transformRotate(geometry, angle, { pivot: centerFeature });
92
+ const selectedIndexes = props.selectedIndexes;
93
+ let updatedData = new ImmutableFeatureCollection(props.data);
94
+ for (let i = 0; i < selectedIndexes.length; i++) {
95
+ const selectedIndex = selectedIndexes[i];
96
+ const movedFeature = rotatedFeatures.features[i];
97
+ if (selectedIndex !== void 0 && movedFeature) updatedData = updatedData.replaceGeometry(selectedIndex, movedFeature.geometry);
98
+ }
99
+ return {
100
+ updatedData: updatedData.getObject(),
101
+ editType,
102
+ editContext: { featureIndexes: selectedIndexes }
103
+ };
104
+ }
105
+ };
106
+
107
+ //#endregion
108
+ export { RotateModeWithSnap };
109
+ //# sourceMappingURL=rotate-mode-with-snap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rotate-mode-with-snap.js","names":["rotatedFeatures: FeatureCollection"],"sources":["../../../../../src/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.ts"],"sourcesContent":["/*\n * Copyright 2025 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 ImmutableFeatureCollection,\n type ModeProps,\n RotateMode,\n type StopDraggingEvent,\n} from '@deck.gl-community/editable-layers';\nimport { bearing, centroid, transformRotate } from '@turf/turf';\nimport type { Position } from 'geojson';\n\n/** Snap interval in degrees (45° = 8 positions around the circle) */\nconst SNAP_INTERVAL_DEGREES = 45;\n\n/**\n * Calculate the angle between two points relative to a centroid.\n * Uses turfBearing for geographic bearing convention (matches parent RotateMode).\n * Returns angle in degrees.\n *\n * Note: centroid must be a turf Point Feature (not just coordinates) to match\n * the parent RotateMode's behavior exactly.\n */\nfunction getRotationAngle(\n centroidFeature: ReturnType<typeof centroid>,\n startPoint: Position,\n endPoint: Position,\n): number {\n const bearing1 = bearing(centroidFeature, startPoint);\n const bearing2 = bearing(centroidFeature, endPoint);\n return bearing2 - bearing1;\n}\n\n/**\n * Snap an angle to the nearest interval.\n */\nfunction snapAngle(angle: number, interval: number): number {\n return Math.round(angle / interval) * interval;\n}\n\n/**\n * Extends RotateMode to support snapping rotation to 45° intervals.\n *\n * Features:\n * - Default: Free rotation\n * - With modeConfig.snapRotation = true: Snap to 45° intervals (0°, 45°, 90°, etc.)\n *\n * This allows precise alignment of shapes to common angles.\n */\nexport class RotateModeWithSnap extends RotateMode {\n /**\n * Override handleDragging to support snapped rotation.\n * When snapRotation is true, rotates to nearest 45° interval.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleDragging(\n event: DraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (!self._isRotating) {\n return;\n }\n\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotating',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n event.cancelPan();\n }\n\n /**\n * Override handleStopDragging to emit the final rotated geometry with snap.\n * When snapRotation is false, delegates to parent for standard rotation.\n */\n override handleStopDragging(\n event: StopDraggingEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n\n // If not snapping, use parent's rotation logic\n if (!snapRotation) {\n super.handleStopDragging(event, props);\n return;\n }\n\n // biome-ignore lint/suspicious/noExplicitAny: Accessing private properties from parent class\n const self = this as any;\n\n if (self._isRotating) {\n const rotateAction = this.getRotateActionWithSnap(\n event.pointerDownMapCoords,\n event.mapCoords,\n 'rotated',\n props,\n );\n\n if (rotateAction) {\n props.onEdit(rotateAction);\n }\n\n // Reset state\n self._geometryBeingRotated = null;\n self._selectedEditHandle = null;\n self._isRotating = false;\n }\n }\n\n /**\n * Get a rotate action, optionally snapping to 45° intervals.\n */\n private getRotateActionWithSnap(\n startDragPoint: Position,\n currentPoint: Position,\n editType: string,\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._geometryBeingRotated) {\n return null;\n }\n\n const geometry = self._geometryBeingRotated as FeatureCollection;\n // @ts-expect-error turf types differ from editable-layers types\n const centerFeature = centroid(geometry);\n\n // Calculate the rotation angle (pass centroid Feature to match parent RotateMode)\n let angle = getRotationAngle(centerFeature, startDragPoint, currentPoint);\n\n // Snap to 45° intervals if enabled\n const snapRotation = props.modeConfig?.snapRotation ?? false;\n if (snapRotation) {\n angle = snapAngle(angle, SNAP_INTERVAL_DEGREES);\n }\n\n // Apply the rotation using turf (use centroid Feature as pivot to match parent)\n // @ts-expect-error turf types differ from editable-layers types\n const rotatedFeatures: FeatureCollection = transformRotate(\n // @ts-expect-error turf types differ from editable-layers types\n geometry,\n angle,\n {\n pivot: centerFeature,\n },\n );\n\n // Build the updated data using ImmutableFeatureCollection (matches parent RotateMode)\n const selectedIndexes = props.selectedIndexes;\n let updatedData = new ImmutableFeatureCollection(props.data);\n\n for (let i = 0; i < selectedIndexes.length; i++) {\n const selectedIndex = selectedIndexes[i];\n const movedFeature = rotatedFeatures.features[i];\n if (selectedIndex !== undefined && movedFeature) {\n updatedData = updatedData.replaceGeometry(\n selectedIndex,\n movedFeature.geometry,\n );\n }\n }\n\n return {\n updatedData: updatedData.getObject(),\n editType,\n editContext: {\n featureIndexes: selectedIndexes,\n },\n };\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAwBA,MAAM,wBAAwB;;;;;;;;;AAU9B,SAAS,iBACP,iBACA,YACA,UACQ;CACR,MAAM,WAAW,QAAQ,iBAAiB,WAAW;AAErD,QADiB,QAAQ,iBAAiB,SAAS,GACjC;;;;;AAMpB,SAAS,UAAU,OAAe,UAA0B;AAC1D,QAAO,KAAK,MAAM,QAAQ,SAAS,GAAG;;;;;;;;;;;AAYxC,IAAa,qBAAb,cAAwC,WAAW;;;;;;CAMjD,AAAS,eACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,eAAe,OAAO,MAAM;AAClC;;AAMF,MAAI,CAFS,KAEH,YACR;EAGF,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,YACA,MACD;AAED,MAAI,aACF,OAAM,OAAO,aAAa;AAG5B,QAAM,WAAW;;;;;;CAOnB,AAAS,mBACP,OACA,OACA;AAIA,MAAI,EAHiB,MAAM,YAAY,gBAAgB,QAGpC;AACjB,SAAM,mBAAmB,OAAO,MAAM;AACtC;;EAIF,MAAM,OAAO;AAEb,MAAI,KAAK,aAAa;GACpB,MAAM,eAAe,KAAK,wBACxB,MAAM,sBACN,MAAM,WACN,WACA,MACD;AAED,OAAI,aACF,OAAM,OAAO,aAAa;AAI5B,QAAK,wBAAwB;AAC7B,QAAK,sBAAsB;AAC3B,QAAK,cAAc;;;;;;CAOvB,AAAQ,wBACN,gBACA,cACA,UACA,OACA;EAEA,MAAM,OAAO;AAEb,MAAI,CAAC,KAAK,sBACR,QAAO;EAGT,MAAM,WAAW,KAAK;EAEtB,MAAM,gBAAgB,SAAS,SAAS;EAGxC,IAAI,QAAQ,iBAAiB,eAAe,gBAAgB,aAAa;AAIzE,MADqB,MAAM,YAAY,gBAAgB,MAErD,SAAQ,UAAU,OAAO,sBAAsB;EAKjD,MAAMA,kBAAqC,gBAEzC,UACA,OACA,EACE,OAAO,eACR,CACF;EAGD,MAAM,kBAAkB,MAAM;EAC9B,IAAI,cAAc,IAAI,2BAA2B,MAAM,KAAK;AAE5D,OAAK,IAAI,IAAI,GAAG,IAAI,gBAAgB,QAAQ,KAAK;GAC/C,MAAM,gBAAgB,gBAAgB;GACtC,MAAM,eAAe,gBAAgB,SAAS;AAC9C,OAAI,kBAAkB,UAAa,aACjC,eAAc,YAAY,gBACxB,eACA,aAAa,SACd;;AAIL,SAAO;GACL,aAAa,YAAY,WAAW;GACpC;GACA,aAAa,EACX,gBAAgB,iBACjB;GACF"}