@accelint/map-toolkit 1.2.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.
Files changed (131) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/catalog-info.yaml +3 -3
  3. package/dist/camera/events.d.ts +45 -0
  4. package/dist/camera/events.js +45 -0
  5. package/dist/camera/events.js.map +1 -1
  6. package/dist/camera/store.d.ts +47 -0
  7. package/dist/camera/store.js +81 -0
  8. package/dist/camera/store.js.map +1 -1
  9. package/dist/camera/types.d.ts +81 -0
  10. package/dist/cursor-coordinates/constants.d.ts +8 -0
  11. package/dist/cursor-coordinates/constants.js +22 -0
  12. package/dist/cursor-coordinates/constants.js.map +1 -0
  13. package/dist/cursor-coordinates/store.d.ts +1 -0
  14. package/dist/cursor-coordinates/store.js +1 -0
  15. package/dist/cursor-coordinates/store.js.map +1 -1
  16. package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +5 -0
  17. package/dist/cursor-coordinates/use-cursor-coordinates.js +23 -8
  18. package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
  19. package/dist/deckgl/base-map/constants.d.ts +12 -0
  20. package/dist/deckgl/base-map/constants.js +12 -0
  21. package/dist/deckgl/base-map/constants.js.map +1 -1
  22. package/dist/deckgl/base-map/controls.d.ts +11 -1
  23. package/dist/deckgl/base-map/controls.js +5 -0
  24. package/dist/deckgl/base-map/controls.js.map +1 -1
  25. package/dist/deckgl/base-map/events.d.ts +30 -0
  26. package/dist/deckgl/base-map/events.js +30 -0
  27. package/dist/deckgl/base-map/events.js.map +1 -1
  28. package/dist/deckgl/base-map/index.d.ts +2 -2
  29. package/dist/deckgl/base-map/index.js +33 -3
  30. package/dist/deckgl/base-map/index.js.map +1 -1
  31. package/dist/deckgl/base-map/provider.d.ts +2 -2
  32. package/dist/deckgl/index.js +1 -1
  33. package/dist/deckgl/saved-viewports/index.d.ts +75 -0
  34. package/dist/deckgl/saved-viewports/index.js +58 -0
  35. package/dist/deckgl/saved-viewports/index.js.map +1 -1
  36. package/dist/deckgl/saved-viewports/storage.d.ts +51 -0
  37. package/dist/deckgl/saved-viewports/storage.js +64 -0
  38. package/dist/deckgl/saved-viewports/storage.js.map +1 -1
  39. package/dist/deckgl/shapes/display-shape-layer/constants.js +18 -6
  40. package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
  41. package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +7 -0
  42. package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
  43. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +61 -4
  44. package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
  45. package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +22 -8
  46. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +75 -4
  47. package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
  48. package/dist/deckgl/shapes/draw-shape-layer/constants.js +30 -0
  49. package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
  50. package/dist/deckgl/shapes/draw-shape-layer/fiber.js +36 -0
  51. package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
  52. package/dist/deckgl/shapes/draw-shape-layer/index.d.ts +2 -2
  53. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +32 -1
  54. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
  55. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +37 -8
  56. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
  57. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +43 -1
  58. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
  59. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +44 -1
  60. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
  61. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +46 -3
  62. package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
  63. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +37 -1
  64. package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
  65. package/dist/deckgl/shapes/draw-shape-layer/store.js +50 -2
  66. package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
  67. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +138 -17
  68. package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
  69. package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
  70. package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
  71. package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
  72. package/dist/deckgl/shapes/edit-shape-layer/index.js +14 -0
  73. package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
  74. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +56 -8
  75. package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
  76. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +26 -4
  77. package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
  78. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +28 -3
  79. package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
  80. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +24 -0
  81. package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
  82. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +33 -4
  83. package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
  84. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +21 -2
  85. package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
  86. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +35 -11
  87. package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
  88. package/dist/deckgl/shapes/edit-shape-layer/store.js +1 -1
  89. package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
  90. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +12 -0
  91. package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
  92. package/dist/deckgl/shapes/shared/types.d.ts +3 -3
  93. package/dist/deckgl/shapes/shared/types.js +2 -2
  94. package/dist/deckgl/shapes/shared/types.js.map +1 -1
  95. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +3 -3
  96. package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
  97. package/dist/deckgl/shapes/shared/utils/pick-filtering.js +1 -1
  98. package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
  99. package/dist/deckgl/symbol-layer/fiber.d.ts +18 -0
  100. package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
  101. package/dist/deckgl/symbol-layer/index.d.ts +79 -1
  102. package/dist/deckgl/symbol-layer/index.js +72 -1
  103. package/dist/deckgl/symbol-layer/index.js.map +1 -1
  104. package/dist/deckgl/text-layer/character-sets.d.ts +30 -0
  105. package/dist/deckgl/text-layer/character-sets.js +26 -0
  106. package/dist/deckgl/text-layer/character-sets.js.map +1 -1
  107. package/dist/deckgl/text-layer/default-settings.d.ts +29 -0
  108. package/dist/deckgl/text-layer/default-settings.js +28 -0
  109. package/dist/deckgl/text-layer/default-settings.js.map +1 -1
  110. package/dist/deckgl/text-layer/index.d.ts +65 -0
  111. package/dist/deckgl/text-layer/index.js +56 -0
  112. package/dist/deckgl/text-layer/index.js.map +1 -1
  113. package/dist/map-cursor/events.d.ts +19 -0
  114. package/dist/map-cursor/events.js +19 -0
  115. package/dist/map-cursor/events.js.map +1 -1
  116. package/dist/map-cursor/store.d.ts +34 -2
  117. package/dist/map-cursor/store.js +44 -3
  118. package/dist/map-cursor/store.js.map +1 -1
  119. package/dist/map-mode/store.d.ts +43 -4
  120. package/dist/map-mode/store.js +56 -6
  121. package/dist/map-mode/store.js.map +1 -1
  122. package/dist/shared/create-map-store.d.ts +14 -0
  123. package/dist/shared/create-map-store.js +26 -2
  124. package/dist/shared/create-map-store.js.map +1 -1
  125. package/dist/shared/units.d.ts +24 -0
  126. package/dist/shared/units.js +24 -0
  127. package/dist/shared/units.js.map +1 -1
  128. package/dist/viewport/store.d.ts +1 -0
  129. package/dist/viewport/store.js +4 -0
  130. package/dist/viewport/store.js.map +1 -1
  131. package/package.json +3 -3
@@ -207,15 +207,49 @@ function getPolygonPosition(geometry, shape, shapeOffset, shapeVertical, shapeHo
207
207
  };
208
208
  }
209
209
  /**
210
- * Get 2D position for label based on geometry type
211
- * Uses pixel-based offsets for consistent positioning at all zoom levels
210
+ * Get 2D position for label based on geometry type.
211
+ *
212
+ * Calculates label positioning using pixel-based offsets for consistent placement
213
+ * at all zoom levels. Handles Point, LineString, Polygon, and Circle geometries.
212
214
  *
213
215
  * Priority for positioning:
214
216
  * 1. Per-shape properties in styleProperties (highest)
215
217
  * 2. Global labelOptions from layer props
216
218
  * 3. Default values (fallback)
217
219
  *
218
- * Returns null if no valid coordinates can be determined
220
+ * Returns null if no valid coordinates can be determined.
221
+ *
222
+ * @param shape - The shape to position a label for
223
+ * @param options - Optional global label positioning options
224
+ * @returns Label position information or null if coordinates are invalid
225
+ *
226
+ * @example
227
+ * ```typescript
228
+ * import { getLabelPosition2d } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';
229
+ * import type { Shape, LabelPositionOptions } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';
230
+ *
231
+ * const shape: Shape = {
232
+ * id: 'point-1',
233
+ * name: 'Location',
234
+ * label: 'LOC',
235
+ * feature: {
236
+ * type: 'Feature',
237
+ * geometry: { type: 'Point', coordinates: [-122.4, 37.8] },
238
+ * properties: {},
239
+ * },
240
+ * };
241
+ *
242
+ * // Get default positioning
243
+ * const position = getLabelPosition2d(shape);
244
+ * // Returns: { coordinates: [-122.4, 37.8], textAnchor: 'middle', alignmentBaseline: 'top', pixelOffset: [0, 10] }
245
+ *
246
+ * // Use custom global options
247
+ * const options: LabelPositionOptions = {
248
+ * pointLabelVerticalAnchor: 'bottom',
249
+ * pointLabelOffset: [0, -15],
250
+ * };
251
+ * const customPosition = getLabelPosition2d(shape, options);
252
+ * ```
219
253
  */
220
254
  function getLabelPosition2d(shape, options) {
221
255
  const { geometry } = shape.feature;
@@ -232,7 +266,7 @@ function getLabelPosition2d(shape, options) {
232
266
  }
233
267
  }
234
268
  /**
235
- * Get label text for a shape
269
+ * Get label text for a shape.
236
270
  *
237
271
  * Returns the display label for the shape on the map in uppercase.
238
272
  * - `label`: Optional short display name shown on the map (e.g., "NYC")
@@ -240,6 +274,43 @@ function getLabelPosition2d(shape, options) {
240
274
  *
241
275
  * If `label` is not provided, falls back to using `name`.
242
276
  * Text is automatically converted to uppercase for display.
277
+ *
278
+ * @param shape - The shape to get label text for
279
+ * @returns The label text in uppercase
280
+ *
281
+ * @example
282
+ * ```typescript
283
+ * import { getLabelText } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';
284
+ * import type { Shape } from '@accelint/map-toolkit/deckgl/shapes/shared/types';
285
+ *
286
+ * const shape: Shape = {
287
+ * id: 'location-1',
288
+ * name: 'New York City Office',
289
+ * label: 'NYC',
290
+ * feature: {
291
+ * type: 'Feature',
292
+ * geometry: { type: 'Point', coordinates: [-74.0, 40.7] },
293
+ * properties: {},
294
+ * },
295
+ * };
296
+ *
297
+ * const text = getLabelText(shape);
298
+ * // Returns: "NYC"
299
+ *
300
+ * // Without label property, uses name
301
+ * const shapeWithoutLabel: Shape = {
302
+ * id: 'location-2',
303
+ * name: 'Boston Office',
304
+ * feature: {
305
+ * type: 'Feature',
306
+ * geometry: { type: 'Point', coordinates: [-71.0, 42.3] },
307
+ * properties: {},
308
+ * },
309
+ * };
310
+ *
311
+ * const textFromName = getLabelText(shapeWithoutLabel);
312
+ * // Returns: "BOSTON OFFICE"
313
+ * ```
243
314
  */
244
315
  function getLabelText(shape) {
245
316
  return (shape.label || shape.name).toUpperCase();
@@ -1 +1 @@
1
- {"version":3,"file":"labels.js","names":["defaultOffset: [number, number]","defaultVertical: LabelVerticalPosition","defaultHorizontal: LabelHorizontalPosition","defaultCoordinateAnchor: CardinalLabelCoordinateAnchor"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/labels.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Label positioning utilities for shapes\n *\n * This module provides utilities for positioning labels on shapes and calculating\n * points along line segments. Use these to customize label placement in your shape data.\n *\n * ## Label Styling\n *\n * Labels use a text-only style for legibility across different map backgrounds:\n * - **Text**: White uppercase with bold Roboto Mono font at 10px\n * - **Outline**: Black 2px outline around text for contrast\n * - **No background or border**: Clean text-only appearance\n *\n * ### Calculating Offsets\n *\n * When positioning labels, the text height is approximately 10px.\n * For example, to position a label exactly 5px above a point:\n * - Label height ≈ text height (10px)\n * - Offset needed: [0, -(10 + 5)] = [0, -15]\n *\n * @example Position label at the middle of a LineString\n * ```typescript\n * import { getLineStringMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * // Calculate midpoint for custom positioning\n * const coordinates = [[-122.4, 37.8], [-122.3, 37.9], [-122.2, 38.0]];\n * const midpoint = getLineStringMidpoint(coordinates);\n *\n * // Use in shape data with custom label properties\n * const shape = {\n * feature: {\n * properties: {\n * styleProperties: {\n * labelVerticalAnchor: 'top',\n * labelHorizontalAnchor: 'center',\n * labelOffset: [0, -10], // Small offset above the line\n * }\n * }\n * }\n * };\n * ```\n */\n\n'use client';\n\nimport { isCircleShape } from '../../shared/types';\nimport type { LineString, Point, Polygon } from 'geojson';\nimport type { Shape } from '../../shared/types';\n\n/**\n * Label positioning information including coordinates and screen-space offsets\n */\nexport interface LabelPosition2d {\n /** Geographic coordinates [longitude, latitude] */\n coordinates: [number, number];\n /** Horizontal text anchor point */\n textAnchor: 'start' | 'middle' | 'end';\n /** Vertical text alignment */\n alignmentBaseline: 'top' | 'center' | 'bottom';\n /** Pixel offset from coordinates [x, y] */\n pixelOffset: [number, number];\n}\n\n/**\n * Calculate a point along a line segment\n * @param start - Start coordinate [lon, lat]\n * @param end - End coordinate [lon, lat]\n * @param ratio - Position along segment (0 = start, 0.5 = middle, 1 = end)\n * @returns Interpolated coordinate [lon, lat]\n *\n * @example\n * // Get a point 25% along a line segment\n * const start: [number, number] = [-122.4, 37.8];\n * const end: [number, number] = [-122.3, 37.9];\n * const point = interpolatePoint(start, end, 0.25);\n */\nexport function interpolatePoint(\n start: [number, number],\n end: [number, number],\n ratio: number,\n): [number, number] {\n const clampedRatio = Math.max(0, Math.min(1, ratio));\n return [\n start[0] + (end[0] - start[0]) * clampedRatio,\n start[1] + (end[1] - start[1]) * clampedRatio,\n ];\n}\n\n/**\n * Calculate segment lengths for a line\n */\nfunction calculateSegmentLengths(coordinates: [number, number][]): {\n lengths: number[];\n total: number;\n} {\n let total = 0;\n const lengths: number[] = [];\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const length = Math.sqrt(\n (end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2,\n );\n lengths.push(length);\n total += length;\n }\n }\n\n return { lengths, total };\n}\n\n/**\n * Find point at specific distance along line\n */\nfunction findPointAtDistance(\n coordinates: [number, number][],\n segmentLengths: number[],\n targetDistance: number,\n): [number, number] {\n let accumulatedLength = 0;\n\n for (let i = 0; i < segmentLengths.length; i++) {\n const segmentLength = segmentLengths[i];\n if (!segmentLength) {\n continue;\n }\n\n if (accumulatedLength + segmentLength >= targetDistance) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const ratio = (targetDistance - accumulatedLength) / segmentLength;\n return interpolatePoint(start, end, ratio);\n }\n }\n accumulatedLength += segmentLength;\n }\n\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a LineString\n * @param coordinates - LineString coordinates array\n * @returns Midpoint coordinate [lon, lat]\n */\nexport function getLineStringMidpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n if (coordinates.length === 1) {\n return coordinates[0] ?? [0, 0];\n }\n\n const { lengths, total } = calculateSegmentLengths(coordinates);\n const halfLength = total / 2;\n\n return findPointAtDistance(coordinates, lengths, halfLength);\n}\n\n/**\n * Get the end point of a LineString\n * @param coordinates - LineString coordinates array\n * @returns End coordinate [lon, lat]\n */\nexport function getLineStringEndpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a Polygon's outer ring\n * @param coordinates - Polygon coordinates array (rings)\n * @returns Midpoint of outer ring [lon, lat]\n */\nexport function getPolygonMidpoint(\n coordinates: [number, number][][],\n): [number, number] {\n const outerRing = coordinates[0];\n if (!outerRing || outerRing.length === 0) {\n return [0, 0];\n }\n // Use the outer ring (first ring)\n return getLineStringMidpoint(outerRing);\n}\n\n/**\n * Vertical label position relative to anchor point\n */\nexport type LabelVerticalPosition = 'top' | 'middle' | 'bottom';\n\n/**\n * Horizontal label position relative to anchor point\n */\nexport type LabelHorizontalPosition = 'left' | 'center' | 'right';\n\n/**\n * Cardinal direction anchor for positioning labels on geometry edges\n * Uses edge positions relative to the geometry's bounding box\n * Works for LineString, Polygon, and Circle geometries\n * - 'center': centroid/midpoint of the geometry\n * - 'top'/'right'/'bottom'/'left': edge positions\n */\nexport type CardinalLabelCoordinateAnchor =\n | 'center'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n\n/**\n * Global label positioning options for DisplayShapeLayer\n *\n * ## Priority System\n * Label positioning follows a three-tier priority system:\n * 1. **Per-shape properties** in `styleProperties` (highest priority)\n * 2. **Global options** via this interface\n * 3. **Default values** (geometry-specific fallbacks)\n *\n * ## Label Appearance\n *\n * Labels use a clean text-only style:\n * - **Text**: White uppercase, bold Roboto Mono, 10px\n * - **Outline**: Black 2px outline for contrast\n * - **No background or border**\n *\n * Text height ≈ 10px\n *\n * ## Positioning Concepts\n *\n * ### Coordinate Anchor\n * Determines *where* on the geometry to place the label:\n * - **Point**: Label is always at the point coordinate\n * - **LineString/Polygon**: 'start', 'middle', or 'end' along the geometry\n * - **Circle**: 'top', 'right', 'bottom', or 'left' on the perimeter\n *\n * ### Vertical/Horizontal Anchor\n * Determines how the label aligns *relative to* the anchor point:\n * - **Vertical**: 'top' (label text below point), 'middle' (centered), 'bottom' (label text above point)\n * - **Horizontal**: 'left' (label text right of point), 'center' (centered), 'right' (label text left of point)\n *\n * ### Pixel Offset\n * Fine-tune label position with [x, y] pixel offsets:\n * - Positive x moves right, negative moves left\n * - Positive y moves down, negative moves up\n *\n * @example Position circle labels at the top with 5px clearance\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom', // Label above the top point\n * circleLabelOffset: [0, -15], // -(10px text height + 5px clearance)\n * };\n * ```\n *\n * @example Position line labels at middle with offset to the right\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * lineStringLabelCoordinateAnchor: 'middle',\n * lineStringLabelHorizontalAnchor: 'left',\n * lineStringLabelOffset: [10, 0], // 10px to the right\n * };\n * ```\n */\nexport interface LabelPositionOptions {\n // Point geometry options\n /** Vertical anchor for Point labels @default 'top' */\n pointLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Point labels @default 'center' */\n pointLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Point labels [x, y] @default [0, 10] */\n pointLabelOffset?: [number, number];\n\n // LineString geometry options\n /** Position on LineString edge (top/right/bottom/left) @default 'bottom' */\n lineStringLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for LineString labels @default 'top' */\n lineStringLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for LineString labels @default 'center' */\n lineStringLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for LineString labels [x, y] @default [0, 10] */\n lineStringLabelOffset?: [number, number];\n\n // Polygon geometry options\n /** Position on Polygon edge (top/right/bottom/left) @default 'bottom' */\n polygonLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Polygon labels @default 'top' */\n polygonLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Polygon labels @default 'center' */\n polygonLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Polygon labels [x, y] @default [0, 10] */\n polygonLabelOffset?: [number, number];\n\n // Circle geometry options\n /** Position on Circle perimeter (top/right/bottom/left) @default 'bottom' */\n circleLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Circle labels @default 'top' */\n circleLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Circle labels @default 'center' */\n circleLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Circle labels [x, y] @default [0, 10] */\n circleLabelOffset?: [number, number];\n}\n\n/**\n * Convert vertical/horizontal position to deck.gl textAnchor and alignmentBaseline\n */\nfunction convertPositionToAnchors(\n vertical: LabelVerticalPosition,\n horizontal: LabelHorizontalPosition,\n): {\n textAnchor: 'start' | 'middle' | 'end';\n alignmentBaseline: 'top' | 'center' | 'bottom';\n} {\n // Map horizontal to textAnchor\n const textAnchorMap: Record<\n LabelHorizontalPosition,\n 'start' | 'middle' | 'end'\n > = {\n left: 'start',\n center: 'middle',\n right: 'end',\n };\n\n // Map vertical to alignmentBaseline\n const alignmentBaselineMap: Record<\n LabelVerticalPosition,\n 'top' | 'center' | 'bottom'\n > = {\n top: 'top',\n middle: 'center',\n bottom: 'bottom',\n };\n\n return {\n textAnchor: textAnchorMap[horizontal],\n alignmentBaseline: alignmentBaselineMap[vertical],\n };\n}\n\n/**\n * Helper to resolve label properties with priority: shape > options > default\n */\nfunction resolveLabelProperties(\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n defaultOffset: [number, number],\n defaultVertical: LabelVerticalPosition,\n defaultHorizontal: LabelHorizontalPosition,\n optionsOffset?: [number, number],\n optionsVertical?: LabelVerticalPosition,\n optionsHorizontal?: LabelHorizontalPosition,\n) {\n const vertical = (shapeVertical ??\n optionsVertical ??\n defaultVertical) as LabelVerticalPosition;\n const horizontal = (shapeHorizontal ??\n optionsHorizontal ??\n defaultHorizontal) as LabelHorizontalPosition;\n const pixelOffset = shapeOffset ?? optionsOffset ?? defaultOffset;\n\n const anchors = convertPositionToAnchors(vertical, horizontal);\n\n return {\n pixelOffset,\n ...anchors,\n };\n}\n\n/**\n * Get position for Point geometry\n */\nfunction getPointPosition(\n geometry: Point,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.pointLabelOffset,\n options?.pointLabelVerticalAnchor,\n options?.pointLabelHorizontalAnchor,\n );\n\n return {\n coordinates: [\n geometry.coordinates[0] ?? 0,\n geometry.coordinates[1] ?? 0,\n ] as [number, number],\n ...resolved,\n };\n}\n\n/**\n * Get position for LineString geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getLineStringPosition(\n geometry: LineString,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.lineStringLabelOffset,\n options?.lineStringLabelVerticalAnchor,\n options?.lineStringLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.lineStringLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(\n geometry.coordinates as number[][],\n coordinateAnchor,\n );\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get vertex coordinate from ring\n */\nfunction getVertexCoordinate(\n vertex: number[] | undefined,\n): [number, number] | null {\n if (!vertex || vertex[0] === undefined || vertex[1] === undefined) {\n return null;\n }\n return [vertex[0], vertex[1]];\n}\n\n/**\n * Check if a vertex should replace the current target based on edge position\n */\nfunction shouldUpdateEdgeVertex(\n vertexValue: number,\n targetValue: number,\n position: CardinalLabelCoordinateAnchor,\n): boolean {\n // For top and right, find maximum value\n // For bottom and left, find minimum value\n return position === 'top' || position === 'right'\n ? vertexValue > targetValue\n : vertexValue < targetValue;\n}\n\n/**\n * Get the coordinate index based on edge position (0 for x/longitude, 1 for y/latitude)\n */\nfunction getCoordinateIndexForEdgePosition(\n position: CardinalLabelCoordinateAnchor,\n): number {\n return position === 'top' || position === 'bottom' ? 1 : 0;\n}\n\n/**\n * Calculate the centroid (center point) of a set of coordinates\n * For polygons, this calculates the geometric center\n * For lines, this calculates the midpoint of the bounding box\n * Returns null if no valid coordinates exist\n */\nfunction calculateCentroid(coordinates: number[][]): [number, number] | null {\n if (coordinates.length === 0) {\n return null;\n }\n\n let sumX = 0;\n let sumY = 0;\n let count = 0;\n\n for (const coord of coordinates) {\n if (coord && coord[0] !== undefined && coord[1] !== undefined) {\n sumX += coord[0];\n sumY += coord[1];\n count++;\n }\n }\n\n if (count === 0) {\n return null;\n }\n\n return [sumX / count, sumY / count];\n}\n\n/**\n * Find the point on a geometry's perimeter at the specified edge position\n * @param coordinates - Array of coordinates (ring or line)\n * @param position - Edge position (center/top/right/bottom/left) relative to bounding box\n * @returns Coordinate at the specified edge position, or null if no valid coordinates\n */\nfunction findEdgePoint(\n coordinates: number[][] | undefined,\n position: CardinalLabelCoordinateAnchor,\n): [number, number] | null {\n if (!coordinates || coordinates.length === 0) {\n return null;\n }\n\n // Handle center positioning\n if (position === 'center') {\n return calculateCentroid(coordinates);\n }\n\n // Find the vertex with max/min latitude or longitude\n let targetVertex = coordinates[0];\n const coordinateIndex = getCoordinateIndexForEdgePosition(position);\n\n for (const vertex of coordinates) {\n if (!vertex) {\n continue;\n }\n if (!targetVertex) {\n continue;\n }\n\n const vertexValue = vertex[coordinateIndex];\n const targetValue = targetVertex[coordinateIndex];\n\n if (vertexValue === undefined || targetValue === undefined) {\n continue;\n }\n\n if (shouldUpdateEdgeVertex(vertexValue, targetValue, position)) {\n targetVertex = vertex;\n }\n }\n\n return getVertexCoordinate(targetVertex);\n}\n\n/**\n * Get position for Circle geometry (special case of Polygon)\n */\nfunction getCirclePosition(\n ring: number[][] | undefined,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.circleLabelOffset,\n options?.circleLabelVerticalAnchor,\n options?.circleLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.circleLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on coordinate anchor\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get position for Polygon geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getPolygonPosition(\n geometry: Polygon,\n shape: Shape,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const ring = geometry.coordinates[0];\n\n // Circle shapes use circle-specific options\n if (isCircleShape(shape)) {\n return getCirclePosition(\n ring,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n }\n\n // Regular polygons use cardinal direction positioning\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.polygonLabelOffset,\n options?.polygonLabelVerticalAnchor,\n options?.polygonLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.polygonLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get 2D position for label based on geometry type\n * Uses pixel-based offsets for consistent positioning at all zoom levels\n *\n * Priority for positioning:\n * 1. Per-shape properties in styleProperties (highest)\n * 2. Global labelOptions from layer props\n * 3. Default values (fallback)\n *\n * Returns null if no valid coordinates can be determined\n */\nexport function getLabelPosition2d(\n shape: Shape,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const { geometry } = shape.feature;\n const styleProps = shape.feature.properties?.styleProperties;\n\n // Check if shape has custom label properties\n const shapeOffset = styleProps?.labelOffset as [number, number] | undefined;\n const shapeVertical = styleProps?.labelVerticalAnchor;\n const shapeHorizontal = styleProps?.labelHorizontalAnchor;\n const shapeCoordinateAnchor = styleProps?.labelCoordinateAnchor;\n\n switch (geometry.type) {\n case 'Point':\n return getPointPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n options,\n );\n\n case 'LineString':\n return getLineStringPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n case 'Polygon':\n return getPolygonPosition(\n geometry,\n shape,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n default:\n // Unknown geometry type - return null\n return null;\n }\n}\n\n/**\n * Get label text for a shape\n *\n * Returns the display label for the shape on the map in uppercase.\n * - `label`: Optional short display name shown on the map (e.g., \"NYC\")\n * - `name`: Full shape name used internally (e.g., \"New York City Office\")\n *\n * If `label` is not provided, falls back to using `name`.\n * Text is automatically converted to uppercase for display.\n */\nexport function getLabelText(shape: Shape): string {\n return (shape.label || shape.name).toUpperCase();\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"labels.js","names":["defaultOffset: [number, number]","defaultVertical: LabelVerticalPosition","defaultHorizontal: LabelHorizontalPosition","defaultCoordinateAnchor: CardinalLabelCoordinateAnchor"],"sources":["../../../../../src/deckgl/shapes/display-shape-layer/utils/labels.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n/**\n * Label positioning utilities for shapes\n *\n * This module provides utilities for positioning labels on shapes and calculating\n * points along line segments. Use these to customize label placement in your shape data.\n *\n * ## Label Styling\n *\n * Labels use a text-only style for legibility across different map backgrounds:\n * - **Text**: White uppercase with bold Roboto Mono font at 10px\n * - **Outline**: Black 2px outline around text for contrast\n * - **No background or border**: Clean text-only appearance\n *\n * ### Calculating Offsets\n *\n * When positioning labels, the text height is approximately 10px.\n * For example, to position a label exactly 5px above a point:\n * - Label height ≈ text height (10px)\n * - Offset needed: [0, -(10 + 5)] = [0, -15]\n *\n * @example Position label at the middle of a LineString\n * ```typescript\n * import { getLineStringMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * // Calculate midpoint for custom positioning\n * const coordinates = [[-122.4, 37.8], [-122.3, 37.9], [-122.2, 38.0]];\n * const midpoint = getLineStringMidpoint(coordinates);\n *\n * // Use in shape data with custom label properties\n * const shape = {\n * feature: {\n * properties: {\n * styleProperties: {\n * labelVerticalAnchor: 'top',\n * labelHorizontalAnchor: 'center',\n * labelOffset: [0, -10], // Small offset above the line\n * }\n * }\n * }\n * };\n * ```\n */\n\n'use client';\n\nimport { isCircleShape } from '../../shared/types';\nimport type { LineString, Point, Polygon } from 'geojson';\nimport type { Shape } from '../../shared/types';\n\n/**\n * Label positioning information including coordinates and screen-space offsets.\n *\n * Defines where a label should be positioned on the map, combining geographic coordinates\n * with text alignment and pixel-level offsets for precise placement.\n */\nexport interface LabelPosition2d {\n /** Geographic coordinates [longitude, latitude] */\n coordinates: [number, number];\n /** Horizontal text anchor point */\n textAnchor: 'start' | 'middle' | 'end';\n /** Vertical text alignment */\n alignmentBaseline: 'top' | 'center' | 'bottom';\n /** Pixel offset from coordinates [x, y] */\n pixelOffset: [number, number];\n}\n\n/**\n * Calculate a point along a line segment\n * @param start - Start coordinate [lon, lat]\n * @param end - End coordinate [lon, lat]\n * @param ratio - Position along segment (0 = start, 0.5 = middle, 1 = end)\n * @returns Interpolated coordinate [lon, lat]\n *\n * @example\n * // Get a point 25% along a line segment\n * const start: [number, number] = [-122.4, 37.8];\n * const end: [number, number] = [-122.3, 37.9];\n * const point = interpolatePoint(start, end, 0.25);\n */\nexport function interpolatePoint(\n start: [number, number],\n end: [number, number],\n ratio: number,\n): [number, number] {\n const clampedRatio = Math.max(0, Math.min(1, ratio));\n return [\n start[0] + (end[0] - start[0]) * clampedRatio,\n start[1] + (end[1] - start[1]) * clampedRatio,\n ];\n}\n\n/**\n * Calculate segment lengths for a line\n */\nfunction calculateSegmentLengths(coordinates: [number, number][]): {\n lengths: number[];\n total: number;\n} {\n let total = 0;\n const lengths: number[] = [];\n\n for (let i = 0; i < coordinates.length - 1; i++) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const length = Math.sqrt(\n (end[0] - start[0]) ** 2 + (end[1] - start[1]) ** 2,\n );\n lengths.push(length);\n total += length;\n }\n }\n\n return { lengths, total };\n}\n\n/**\n * Find point at specific distance along line\n */\nfunction findPointAtDistance(\n coordinates: [number, number][],\n segmentLengths: number[],\n targetDistance: number,\n): [number, number] {\n let accumulatedLength = 0;\n\n for (let i = 0; i < segmentLengths.length; i++) {\n const segmentLength = segmentLengths[i];\n if (!segmentLength) {\n continue;\n }\n\n if (accumulatedLength + segmentLength >= targetDistance) {\n const start = coordinates[i];\n const end = coordinates[i + 1];\n if (start && end) {\n const ratio = (targetDistance - accumulatedLength) / segmentLength;\n return interpolatePoint(start, end, ratio);\n }\n }\n accumulatedLength += segmentLength;\n }\n\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a LineString.\n *\n * Calculates the geometric center point along a LineString by measuring distances\n * along each segment. This is more accurate than the bounding box center for curved lines.\n *\n * @param coordinates - LineString coordinates array\n * @returns Midpoint coordinate [lon, lat]\n *\n * @example\n * ```typescript\n * import { getLineStringMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * const lineCoordinates: [number, number][] = [\n * [-122.4, 37.8],\n * [-122.3, 37.85],\n * [-122.2, 37.9],\n * ];\n *\n * const midpoint = getLineStringMidpoint(lineCoordinates);\n * // Returns the coordinate at the middle of the line's total length\n * ```\n */\nexport function getLineStringMidpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n if (coordinates.length === 1) {\n return coordinates[0] ?? [0, 0];\n }\n\n const { lengths, total } = calculateSegmentLengths(coordinates);\n const halfLength = total / 2;\n\n return findPointAtDistance(coordinates, lengths, halfLength);\n}\n\n/**\n * Get the end point of a LineString.\n *\n * Returns the last coordinate in the LineString, useful for positioning labels\n * at the end of lines (e.g., directional arrows, route endpoints).\n *\n * @param coordinates - LineString coordinates array\n * @returns End coordinate [lon, lat]\n *\n * @example\n * ```typescript\n * import { getLineStringEndpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * const lineCoordinates: [number, number][] = [\n * [-122.4, 37.8],\n * [-122.3, 37.85],\n * [-122.2, 37.9],\n * ];\n *\n * const endpoint = getLineStringEndpoint(lineCoordinates);\n * // Returns: [-122.2, 37.9]\n * ```\n */\nexport function getLineStringEndpoint(\n coordinates: [number, number][],\n): [number, number] {\n if (coordinates.length === 0) {\n return [0, 0];\n }\n return coordinates[coordinates.length - 1] ?? [0, 0];\n}\n\n/**\n * Get the midpoint of a Polygon's outer ring.\n *\n * Calculates the geometric center point along the polygon's perimeter by using\n * the midpoint of the outer ring. This is useful for positioning labels on polygons.\n *\n * @param coordinates - Polygon coordinates array (rings)\n * @returns Midpoint of outer ring [lon, lat]\n *\n * @example\n * ```typescript\n * import { getPolygonMidpoint } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * const polygonCoordinates: [number, number][][] = [\n * [\n * [-122.4, 37.8],\n * [-122.3, 37.8],\n * [-122.3, 37.9],\n * [-122.4, 37.9],\n * [-122.4, 37.8], // Closing point\n * ]\n * ];\n *\n * const midpoint = getPolygonMidpoint(polygonCoordinates);\n * // Returns the coordinate at the middle of the outer ring's perimeter\n * ```\n */\nexport function getPolygonMidpoint(\n coordinates: [number, number][][],\n): [number, number] {\n const outerRing = coordinates[0];\n if (!outerRing || outerRing.length === 0) {\n return [0, 0];\n }\n // Use the outer ring (first ring)\n return getLineStringMidpoint(outerRing);\n}\n\n/**\n * Vertical label position relative to anchor point.\n *\n * Controls how the label aligns vertically with its anchor coordinate:\n * - `'top'`: Label text appears below the anchor point\n * - `'middle'`: Label text is vertically centered on the anchor point\n * - `'bottom'`: Label text appears above the anchor point\n */\nexport type LabelVerticalPosition = 'top' | 'middle' | 'bottom';\n\n/**\n * Horizontal label position relative to anchor point.\n *\n * Controls how the label aligns horizontally with its anchor coordinate:\n * - `'left'`: Label text appears to the right of the anchor point\n * - `'center'`: Label text is horizontally centered on the anchor point\n * - `'right'`: Label text appears to the left of the anchor point\n */\nexport type LabelHorizontalPosition = 'left' | 'center' | 'right';\n\n/**\n * Cardinal direction anchor for positioning labels on geometry edges.\n *\n * Uses edge positions relative to the geometry's bounding box.\n * Works for LineString, Polygon, and Circle geometries:\n * - `'center'`: centroid/midpoint of the geometry\n * - `'top'`/`'right'`/`'bottom'`/`'left'`: edge positions based on bounding box\n */\nexport type CardinalLabelCoordinateAnchor =\n | 'center'\n | 'top'\n | 'right'\n | 'bottom'\n | 'left';\n\n/**\n * Global label positioning options for DisplayShapeLayer\n *\n * ## Priority System\n * Label positioning follows a three-tier priority system:\n * 1. **Per-shape properties** in `styleProperties` (highest priority)\n * 2. **Global options** via this interface\n * 3. **Default values** (geometry-specific fallbacks)\n *\n * ## Label Appearance\n *\n * Labels use a clean text-only style:\n * - **Text**: White uppercase, bold Roboto Mono, 10px\n * - **Outline**: Black 2px outline for contrast\n * - **No background or border**\n *\n * Text height ≈ 10px\n *\n * ## Positioning Concepts\n *\n * ### Coordinate Anchor\n * Determines *where* on the geometry to place the label:\n * - **Point**: Label is always at the point coordinate\n * - **LineString/Polygon**: 'start', 'middle', or 'end' along the geometry\n * - **Circle**: 'top', 'right', 'bottom', or 'left' on the perimeter\n *\n * ### Vertical/Horizontal Anchor\n * Determines how the label aligns *relative to* the anchor point:\n * - **Vertical**: 'top' (label text below point), 'middle' (centered), 'bottom' (label text above point)\n * - **Horizontal**: 'left' (label text right of point), 'center' (centered), 'right' (label text left of point)\n *\n * ### Pixel Offset\n * Fine-tune label position with [x, y] pixel offsets:\n * - Positive x moves right, negative moves left\n * - Positive y moves down, negative moves up\n *\n * @example Position circle labels at the top with 5px clearance\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * circleLabelCoordinateAnchor: 'top',\n * circleLabelVerticalAnchor: 'bottom', // Label above the top point\n * circleLabelOffset: [0, -15], // -(10px text height + 5px clearance)\n * };\n * ```\n *\n * @example Position line labels at middle with offset to the right\n * ```tsx\n * const labelOptions: LabelPositionOptions = {\n * lineStringLabelCoordinateAnchor: 'middle',\n * lineStringLabelHorizontalAnchor: 'left',\n * lineStringLabelOffset: [10, 0], // 10px to the right\n * };\n * ```\n */\nexport interface LabelPositionOptions {\n // Point geometry options\n /** Vertical anchor for Point labels @default 'top' */\n pointLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Point labels @default 'center' */\n pointLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Point labels [x, y] @default [0, 10] */\n pointLabelOffset?: [number, number];\n\n // LineString geometry options\n /** Position on LineString edge (top/right/bottom/left) @default 'bottom' */\n lineStringLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for LineString labels @default 'top' */\n lineStringLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for LineString labels @default 'center' */\n lineStringLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for LineString labels [x, y] @default [0, 10] */\n lineStringLabelOffset?: [number, number];\n\n // Polygon geometry options\n /** Position on Polygon edge (top/right/bottom/left) @default 'bottom' */\n polygonLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Polygon labels @default 'top' */\n polygonLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Polygon labels @default 'center' */\n polygonLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Polygon labels [x, y] @default [0, 10] */\n polygonLabelOffset?: [number, number];\n\n // Circle geometry options\n /** Position on Circle perimeter (top/right/bottom/left) @default 'bottom' */\n circleLabelCoordinateAnchor?: CardinalLabelCoordinateAnchor;\n /** Vertical anchor for Circle labels @default 'top' */\n circleLabelVerticalAnchor?: LabelVerticalPosition;\n /** Horizontal anchor for Circle labels @default 'center' */\n circleLabelHorizontalAnchor?: LabelHorizontalPosition;\n /** Pixel offset for Circle labels [x, y] @default [0, 10] */\n circleLabelOffset?: [number, number];\n}\n\n/**\n * Convert vertical/horizontal position to deck.gl textAnchor and alignmentBaseline\n */\nfunction convertPositionToAnchors(\n vertical: LabelVerticalPosition,\n horizontal: LabelHorizontalPosition,\n): {\n textAnchor: 'start' | 'middle' | 'end';\n alignmentBaseline: 'top' | 'center' | 'bottom';\n} {\n // Map horizontal to textAnchor\n const textAnchorMap: Record<\n LabelHorizontalPosition,\n 'start' | 'middle' | 'end'\n > = {\n left: 'start',\n center: 'middle',\n right: 'end',\n };\n\n // Map vertical to alignmentBaseline\n const alignmentBaselineMap: Record<\n LabelVerticalPosition,\n 'top' | 'center' | 'bottom'\n > = {\n top: 'top',\n middle: 'center',\n bottom: 'bottom',\n };\n\n return {\n textAnchor: textAnchorMap[horizontal],\n alignmentBaseline: alignmentBaselineMap[vertical],\n };\n}\n\n/**\n * Helper to resolve label properties with priority: shape > options > default\n */\nfunction resolveLabelProperties(\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n defaultOffset: [number, number],\n defaultVertical: LabelVerticalPosition,\n defaultHorizontal: LabelHorizontalPosition,\n optionsOffset?: [number, number],\n optionsVertical?: LabelVerticalPosition,\n optionsHorizontal?: LabelHorizontalPosition,\n) {\n const vertical = (shapeVertical ??\n optionsVertical ??\n defaultVertical) as LabelVerticalPosition;\n const horizontal = (shapeHorizontal ??\n optionsHorizontal ??\n defaultHorizontal) as LabelHorizontalPosition;\n const pixelOffset = shapeOffset ?? optionsOffset ?? defaultOffset;\n\n const anchors = convertPositionToAnchors(vertical, horizontal);\n\n return {\n pixelOffset,\n ...anchors,\n };\n}\n\n/**\n * Get position for Point geometry\n */\nfunction getPointPosition(\n geometry: Point,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.pointLabelOffset,\n options?.pointLabelVerticalAnchor,\n options?.pointLabelHorizontalAnchor,\n );\n\n return {\n coordinates: [\n geometry.coordinates[0] ?? 0,\n geometry.coordinates[1] ?? 0,\n ] as [number, number],\n ...resolved,\n };\n}\n\n/**\n * Get position for LineString geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getLineStringPosition(\n geometry: LineString,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.lineStringLabelOffset,\n options?.lineStringLabelVerticalAnchor,\n options?.lineStringLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.lineStringLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(\n geometry.coordinates as number[][],\n coordinateAnchor,\n );\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get vertex coordinate from ring\n */\nfunction getVertexCoordinate(\n vertex: number[] | undefined,\n): [number, number] | null {\n if (!vertex || vertex[0] === undefined || vertex[1] === undefined) {\n return null;\n }\n return [vertex[0], vertex[1]];\n}\n\n/**\n * Check if a vertex should replace the current target based on edge position\n */\nfunction shouldUpdateEdgeVertex(\n vertexValue: number,\n targetValue: number,\n position: CardinalLabelCoordinateAnchor,\n): boolean {\n // For top and right, find maximum value\n // For bottom and left, find minimum value\n return position === 'top' || position === 'right'\n ? vertexValue > targetValue\n : vertexValue < targetValue;\n}\n\n/**\n * Get the coordinate index based on edge position (0 for x/longitude, 1 for y/latitude)\n */\nfunction getCoordinateIndexForEdgePosition(\n position: CardinalLabelCoordinateAnchor,\n): number {\n return position === 'top' || position === 'bottom' ? 1 : 0;\n}\n\n/**\n * Calculate the centroid (center point) of a set of coordinates\n * For polygons, this calculates the geometric center\n * For lines, this calculates the midpoint of the bounding box\n * Returns null if no valid coordinates exist\n */\nfunction calculateCentroid(coordinates: number[][]): [number, number] | null {\n if (coordinates.length === 0) {\n return null;\n }\n\n let sumX = 0;\n let sumY = 0;\n let count = 0;\n\n for (const coord of coordinates) {\n if (coord && coord[0] !== undefined && coord[1] !== undefined) {\n sumX += coord[0];\n sumY += coord[1];\n count++;\n }\n }\n\n if (count === 0) {\n return null;\n }\n\n return [sumX / count, sumY / count];\n}\n\n/**\n * Find the point on a geometry's perimeter at the specified edge position\n * @param coordinates - Array of coordinates (ring or line)\n * @param position - Edge position (center/top/right/bottom/left) relative to bounding box\n * @returns Coordinate at the specified edge position, or null if no valid coordinates\n */\nfunction findEdgePoint(\n coordinates: number[][] | undefined,\n position: CardinalLabelCoordinateAnchor,\n): [number, number] | null {\n if (!coordinates || coordinates.length === 0) {\n return null;\n }\n\n // Handle center positioning\n if (position === 'center') {\n return calculateCentroid(coordinates);\n }\n\n // Find the vertex with max/min latitude or longitude\n let targetVertex = coordinates[0];\n const coordinateIndex = getCoordinateIndexForEdgePosition(position);\n\n for (const vertex of coordinates) {\n if (!vertex) {\n continue;\n }\n if (!targetVertex) {\n continue;\n }\n\n const vertexValue = vertex[coordinateIndex];\n const targetValue = targetVertex[coordinateIndex];\n\n if (vertexValue === undefined || targetValue === undefined) {\n continue;\n }\n\n if (shouldUpdateEdgeVertex(vertexValue, targetValue, position)) {\n targetVertex = vertex;\n }\n }\n\n return getVertexCoordinate(targetVertex);\n}\n\n/**\n * Get position for Circle geometry (special case of Polygon)\n */\nfunction getCirclePosition(\n ring: number[][] | undefined,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.circleLabelOffset,\n options?.circleLabelVerticalAnchor,\n options?.circleLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.circleLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on coordinate anchor\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get position for Polygon geometry\n * Uses cardinal direction positioning to find the edge point\n */\nfunction getPolygonPosition(\n geometry: Polygon,\n shape: Shape,\n shapeOffset: [number, number] | undefined,\n shapeVertical: string | undefined,\n shapeHorizontal: string | undefined,\n shapeCoordinateAnchor: string | undefined,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const ring = geometry.coordinates[0];\n\n // Circle shapes use circle-specific options\n if (isCircleShape(shape)) {\n return getCirclePosition(\n ring,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n }\n\n // Regular polygons use cardinal direction positioning\n const defaultOffset: [number, number] = [0, 10];\n const defaultVertical: LabelVerticalPosition = 'top';\n const defaultHorizontal: LabelHorizontalPosition = 'center';\n const defaultCoordinateAnchor: CardinalLabelCoordinateAnchor = 'bottom';\n\n const resolved = resolveLabelProperties(\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n defaultOffset,\n defaultVertical,\n defaultHorizontal,\n options?.polygonLabelOffset,\n options?.polygonLabelVerticalAnchor,\n options?.polygonLabelHorizontalAnchor,\n );\n\n // Determine coordinate anchor (priority: shape > options > default)\n const coordinateAnchor = (shapeCoordinateAnchor ??\n options?.polygonLabelCoordinateAnchor ??\n defaultCoordinateAnchor) as CardinalLabelCoordinateAnchor;\n\n // Calculate position based on cardinal direction\n const coordinates = findEdgePoint(ring, coordinateAnchor);\n\n if (!coordinates) {\n return null;\n }\n\n return {\n coordinates,\n ...resolved,\n };\n}\n\n/**\n * Get 2D position for label based on geometry type.\n *\n * Calculates label positioning using pixel-based offsets for consistent placement\n * at all zoom levels. Handles Point, LineString, Polygon, and Circle geometries.\n *\n * Priority for positioning:\n * 1. Per-shape properties in styleProperties (highest)\n * 2. Global labelOptions from layer props\n * 3. Default values (fallback)\n *\n * Returns null if no valid coordinates can be determined.\n *\n * @param shape - The shape to position a label for\n * @param options - Optional global label positioning options\n * @returns Label position information or null if coordinates are invalid\n *\n * @example\n * ```typescript\n * import { getLabelPosition2d } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n * import type { Shape, LabelPositionOptions } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n *\n * const shape: Shape = {\n * id: 'point-1',\n * name: 'Location',\n * label: 'LOC',\n * feature: {\n * type: 'Feature',\n * geometry: { type: 'Point', coordinates: [-122.4, 37.8] },\n * properties: {},\n * },\n * };\n *\n * // Get default positioning\n * const position = getLabelPosition2d(shape);\n * // Returns: { coordinates: [-122.4, 37.8], textAnchor: 'middle', alignmentBaseline: 'top', pixelOffset: [0, 10] }\n *\n * // Use custom global options\n * const options: LabelPositionOptions = {\n * pointLabelVerticalAnchor: 'bottom',\n * pointLabelOffset: [0, -15],\n * };\n * const customPosition = getLabelPosition2d(shape, options);\n * ```\n */\nexport function getLabelPosition2d(\n shape: Shape,\n options?: LabelPositionOptions,\n): LabelPosition2d | null {\n const { geometry } = shape.feature;\n const styleProps = shape.feature.properties?.styleProperties;\n\n // Check if shape has custom label properties\n const shapeOffset = styleProps?.labelOffset as [number, number] | undefined;\n const shapeVertical = styleProps?.labelVerticalAnchor;\n const shapeHorizontal = styleProps?.labelHorizontalAnchor;\n const shapeCoordinateAnchor = styleProps?.labelCoordinateAnchor;\n\n switch (geometry.type) {\n case 'Point':\n return getPointPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n options,\n );\n\n case 'LineString':\n return getLineStringPosition(\n geometry,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n case 'Polygon':\n return getPolygonPosition(\n geometry,\n shape,\n shapeOffset,\n shapeVertical,\n shapeHorizontal,\n shapeCoordinateAnchor,\n options,\n );\n\n default:\n // Unknown geometry type - return null\n return null;\n }\n}\n\n/**\n * Get label text for a shape.\n *\n * Returns the display label for the shape on the map in uppercase.\n * - `label`: Optional short display name shown on the map (e.g., \"NYC\")\n * - `name`: Full shape name used internally (e.g., \"New York City Office\")\n *\n * If `label` is not provided, falls back to using `name`.\n * Text is automatically converted to uppercase for display.\n *\n * @param shape - The shape to get label text for\n * @returns The label text in uppercase\n *\n * @example\n * ```typescript\n * import { getLabelText } from '@accelint/map-toolkit/deckgl/shapes/display-shape-layer/utils/labels';\n * import type { Shape } from '@accelint/map-toolkit/deckgl/shapes/shared/types';\n *\n * const shape: Shape = {\n * id: 'location-1',\n * name: 'New York City Office',\n * label: 'NYC',\n * feature: {\n * type: 'Feature',\n * geometry: { type: 'Point', coordinates: [-74.0, 40.7] },\n * properties: {},\n * },\n * };\n *\n * const text = getLabelText(shape);\n * // Returns: \"NYC\"\n *\n * // Without label property, uses name\n * const shapeWithoutLabel: Shape = {\n * id: 'location-2',\n * name: 'Boston Office',\n * feature: {\n * type: 'Feature',\n * geometry: { type: 'Point', coordinates: [-71.0, 42.3] },\n * properties: {},\n * },\n * };\n *\n * const textFromName = getLabelText(shapeWithoutLabel);\n * // Returns: \"BOSTON OFFICE\"\n * ```\n */\nexport function getLabelText(shape: Shape): string {\n return (shape.label || shape.name).toUpperCase();\n}\n"],"mappings":"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"}
@@ -17,6 +17,36 @@ import { ShapeFeatureType } from "../shared/types.js";
17
17
 
18
18
  //#region src/deckgl/shapes/draw-shape-layer/constants.ts
19
19
  /**
20
+ * Drawing Constants
21
+ *
22
+ * Constants used by the DrawShapeLayer for map-mode and cursor integration.
23
+ *
24
+ * ## Map Mode Integration
25
+ * The drawing system uses a protected mode (`DRAW_SHAPE_MODE`) that prevents
26
+ * other map features from changing the mode or cursor while a shape is being drawn.
27
+ * This ensures drawing operations are not interrupted by other interactions.
28
+ *
29
+ * ## Cursor Management
30
+ * All shape types use the crosshair cursor (`DRAW_CURSOR`) during drawing operations.
31
+ * The cursor is automatically set when drawing starts and restored when complete.
32
+ *
33
+ * ## Layer Identification
34
+ * The `DRAW_SHAPE_LAYER_ID` serves dual purposes:
35
+ * - Default ID for the EditableGeoJsonLayer instance
36
+ * - Owner identifier for map-mode and cursor requests
37
+ *
38
+ * @example
39
+ * ```typescript
40
+ * import { DRAW_SHAPE_MODE, DRAW_CURSOR, DRAW_SHAPE_LAYER_ID } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer';
41
+ *
42
+ * // Request drawing mode and cursor
43
+ * requestModeAndCursor(mapId, DRAW_SHAPE_MODE, DRAW_CURSOR, DRAW_SHAPE_LAYER_ID);
44
+ *
45
+ * // Release when done
46
+ * releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);
47
+ * ```
48
+ */
49
+ /**
20
50
  * Mode name for the map-mode integration
21
51
  */
22
52
  const DRAW_SHAPE_MODE = "draw-shape";
@@ -1 +1 @@
1
- {"version":3,"file":"constants.js","names":["DRAW_CURSOR: CSSCursorType","DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/constants.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 { ShapeFeatureType } from '../shared/types';\nimport type { CSSCursorType } from '@/map-cursor/types';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const DRAW_SHAPE_MODE = 'draw-shape';\n\n/**\n * Identifier for the draw shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const DRAW_SHAPE_LAYER_ID = 'draw-shape-layer';\n\n/**\n * Cursor type to use when drawing shapes\n */\nexport const DRAW_CURSOR: CSSCursorType = 'crosshair';\n\n/**\n * Cursor mapping for each shape type (all use crosshair)\n */\nexport const DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType> = {\n [ShapeFeatureType.Point]: DRAW_CURSOR,\n [ShapeFeatureType.LineString]: DRAW_CURSOR,\n [ShapeFeatureType.Polygon]: DRAW_CURSOR,\n [ShapeFeatureType.Rectangle]: DRAW_CURSOR,\n [ShapeFeatureType.Circle]: DRAW_CURSOR,\n [ShapeFeatureType.Ellipse]: DRAW_CURSOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAoBA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,cAA6B;;;;AAK1C,MAAaC,kBAA2D;EACrE,iBAAiB,QAAQ;EACzB,iBAAiB,aAAa;EAC9B,iBAAiB,UAAU;EAC3B,iBAAiB,YAAY;EAC7B,iBAAiB,SAAS;EAC1B,iBAAiB,UAAU;CAC7B"}
1
+ {"version":3,"file":"constants.js","names":["DRAW_CURSOR: CSSCursorType","DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType>"],"sources":["../../../../src/deckgl/shapes/draw-shape-layer/constants.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 * Drawing Constants\n *\n * Constants used by the DrawShapeLayer for map-mode and cursor integration.\n *\n * ## Map Mode Integration\n * The drawing system uses a protected mode (`DRAW_SHAPE_MODE`) that prevents\n * other map features from changing the mode or cursor while a shape is being drawn.\n * This ensures drawing operations are not interrupted by other interactions.\n *\n * ## Cursor Management\n * All shape types use the crosshair cursor (`DRAW_CURSOR`) during drawing operations.\n * The cursor is automatically set when drawing starts and restored when complete.\n *\n * ## Layer Identification\n * The `DRAW_SHAPE_LAYER_ID` serves dual purposes:\n * - Default ID for the EditableGeoJsonLayer instance\n * - Owner identifier for map-mode and cursor requests\n *\n * @example\n * ```typescript\n * import { DRAW_SHAPE_MODE, DRAW_CURSOR, DRAW_SHAPE_LAYER_ID } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer';\n *\n * // Request drawing mode and cursor\n * requestModeAndCursor(mapId, DRAW_SHAPE_MODE, DRAW_CURSOR, DRAW_SHAPE_LAYER_ID);\n *\n * // Release when done\n * releaseModeAndCursor(mapId, DRAW_SHAPE_LAYER_ID);\n * ```\n */\nimport { ShapeFeatureType } from '../shared/types';\nimport type { CSSCursorType } from '@/map-cursor/types';\n\n/**\n * Mode name for the map-mode integration\n */\nexport const DRAW_SHAPE_MODE = 'draw-shape';\n\n/**\n * Identifier for the draw shape layer.\n * Used as the owner for map-mode/cursor and as the default layer ID.\n */\nexport const DRAW_SHAPE_LAYER_ID = 'draw-shape-layer';\n\n/**\n * Cursor type to use when drawing shapes\n */\nexport const DRAW_CURSOR: CSSCursorType = 'crosshair';\n\n/**\n * Cursor mapping for each shape type (all use crosshair)\n */\nexport const DRAW_CURSOR_MAP: Record<ShapeFeatureType, CSSCursorType> = {\n [ShapeFeatureType.Point]: DRAW_CURSOR,\n [ShapeFeatureType.LineString]: DRAW_CURSOR,\n [ShapeFeatureType.Polygon]: DRAW_CURSOR,\n [ShapeFeatureType.Rectangle]: DRAW_CURSOR,\n [ShapeFeatureType.Circle]: DRAW_CURSOR,\n [ShapeFeatureType.Ellipse]: DRAW_CURSOR,\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAiDA,MAAa,kBAAkB;;;;;AAM/B,MAAa,sBAAsB;;;;AAKnC,MAAaA,cAA6B;;;;AAK1C,MAAaC,kBAA2D;EACrE,iBAAiB,QAAQ;EACzB,iBAAiB,aAAa;EAC9B,iBAAiB,UAAU;EAC3B,iBAAiB,YAAY;EAC7B,iBAAiB,SAAS;EAC1B,iBAAiB,UAAU;CAC7B"}
@@ -15,6 +15,42 @@ import { extend } from "@deckgl-fiber-renderer/dom";
15
15
  import { EditableGeoJsonLayer } from "@deck.gl-community/editable-layers";
16
16
 
17
17
  //#region src/deckgl/shapes/draw-shape-layer/fiber.ts
18
+ /**
19
+ * DeckGL Fiber Registration for EditableGeoJsonLayer
20
+ *
21
+ * Registers the `EditableGeoJsonLayer` from @deck.gl-community/editable-layers
22
+ * with the DeckGL Fiber renderer, enabling JSX syntax for the layer.
23
+ *
24
+ * ## Why This Is Needed
25
+ * DeckGL Fiber (used by BaseMap) requires explicit registration of deck.gl layers
26
+ * before they can be used as JSX elements. This file extends the fiber renderer
27
+ * to recognize `<editableGeoJsonLayer>` as a valid JSX element.
28
+ *
29
+ * ## Usage
30
+ * Import this file once (typically in DrawShapeLayer) before using the layer in JSX:
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * // Import to register the layer
35
+ * import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';
36
+ *
37
+ * // Now you can use it in JSX
38
+ * function DrawShapeLayer() {
39
+ * return (
40
+ * <editableGeoJsonLayer
41
+ * id="draw-layer"
42
+ * mode={drawMode}
43
+ * onEdit={handleEdit}
44
+ * />
45
+ * );
46
+ * }
47
+ * ```
48
+ *
49
+ * ## Note on DrawShapeLayer
50
+ * `DrawShapeLayer` is a React component, not a deck.gl layer class, so it doesn't
51
+ * need fiber registration. It uses `<editableGeoJsonLayer>` internally, which is
52
+ * registered here.
53
+ */
18
54
  extend({ EditableGeoJsonLayer });
19
55
 
20
56
  //#endregion
@@ -1 +1 @@
1
- {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-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 { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\nimport { extend } from '@deckgl-fiber-renderer/dom';\n\n// Extend the fiber renderer with EditableGeoJsonLayer\nextend({ EditableGeoJsonLayer });\n\n// Note: DrawShapeLayer is a React component, not a deck.gl layer class,\n// so it doesn't need fiber registration. It uses <editableGeoJsonLayer>\n// internally which is registered above.\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n // biome-ignore lint/suspicious/noExplicitAny: EditableGeoJsonLayer props are complex and vary by mode\n editableGeoJsonLayer: any;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBA,OAAO,EAAE,sBAAsB,CAAC"}
1
+ {"version":3,"file":"fiber.js","names":[],"sources":["../../../../src/deckgl/shapes/draw-shape-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\n/**\n * DeckGL Fiber Registration for EditableGeoJsonLayer\n *\n * Registers the `EditableGeoJsonLayer` from @deck.gl-community/editable-layers\n * with the DeckGL Fiber renderer, enabling JSX syntax for the layer.\n *\n * ## Why This Is Needed\n * DeckGL Fiber (used by BaseMap) requires explicit registration of deck.gl layers\n * before they can be used as JSX elements. This file extends the fiber renderer\n * to recognize `<editableGeoJsonLayer>` as a valid JSX element.\n *\n * ## Usage\n * Import this file once (typically in DrawShapeLayer) before using the layer in JSX:\n *\n * @example\n * ```tsx\n * // Import to register the layer\n * import '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/fiber';\n *\n * // Now you can use it in JSX\n * function DrawShapeLayer() {\n * return (\n * <editableGeoJsonLayer\n * id=\"draw-layer\"\n * mode={drawMode}\n * onEdit={handleEdit}\n * />\n * );\n * }\n * ```\n *\n * ## Note on DrawShapeLayer\n * `DrawShapeLayer` is a React component, not a deck.gl layer class, so it doesn't\n * need fiber registration. It uses `<editableGeoJsonLayer>` internally, which is\n * registered here.\n */\n\nimport { EditableGeoJsonLayer } from '@deck.gl-community/editable-layers';\nimport { extend } from '@deckgl-fiber-renderer/dom';\n\n// Extend the fiber renderer with EditableGeoJsonLayer\nextend({ EditableGeoJsonLayer });\n\n// Note: DrawShapeLayer is a React component, not a deck.gl layer class,\n// so it doesn't need fiber registration. It uses <editableGeoJsonLayer>\n// internally which is registered above.\n\ndeclare global {\n namespace React {\n // biome-ignore lint/style/useNamingConvention: Built-in React namespace.\n namespace JSX {\n interface IntrinsicElements {\n // biome-ignore lint/suspicious/noExplicitAny: EditableGeoJsonLayer props are complex and vary by mode\n editableGeoJsonLayer: any;\n }\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAqDA,OAAO,EAAE,sBAAsB,CAAC"}
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { DrawShapeLayerProps } from "./types.js";
14
- import * as react_jsx_runtime2 from "react/jsx-runtime";
14
+ import * as react_jsx_runtime0 from "react/jsx-runtime";
15
15
 
16
16
  //#region src/deckgl/shapes/draw-shape-layer/index.d.ts
17
17
 
@@ -47,7 +47,7 @@ declare function DrawShapeLayer({
47
47
  id,
48
48
  mapId,
49
49
  unit
50
- }: DrawShapeLayerProps): react_jsx_runtime2.JSX.Element | null;
50
+ }: DrawShapeLayerProps): react_jsx_runtime0.JSX.Element | null;
51
51
  //#endregion
52
52
  export { DrawShapeLayer };
53
53
  //# sourceMappingURL=index.d.ts.map
@@ -21,10 +21,36 @@ import { DrawCircleFromCenterMode } from "@deck.gl-community/editable-layers";
21
21
  * Extends DrawCircleFromCenterMode to display diameter and area tooltip.
22
22
  *
23
23
  * Shows the diameter and area of the circle being drawn based on the radius
24
- * from center point to cursor position.
24
+ * from center point to cursor position. The tooltip updates in real-time as
25
+ * the cursor moves, displaying measurements in the configured distance units.
26
+ *
27
+ * ## Usage
28
+ * This mode is automatically used by DrawShapeLayer when drawing circles.
29
+ * The mode is cached at module level to prevent deck.gl assertion failures.
30
+ *
31
+ * ## Drawing Flow
32
+ * 1. Click to set center point
33
+ * 2. Move cursor to set radius (tooltip shows diameter and area)
34
+ * 3. Click to finish the circle
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { DrawCircleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';
39
+ *
40
+ * // Used internally by DrawShapeLayer
41
+ * const mode = new DrawCircleModeWithTooltip();
42
+ * ```
25
43
  */
26
44
  var DrawCircleModeWithTooltip = class extends DrawCircleFromCenterMode {
45
+ /** Current tooltip state (null when not drawing) */
27
46
  tooltip = null;
47
+ /**
48
+ * Handle pointer move events to update the tooltip with circle measurements.
49
+ * Calculates diameter and area based on the distance from center to cursor.
50
+ *
51
+ * @param event - Pointer move event with cursor position
52
+ * @param props - Mode properties including distance units configuration
53
+ */
28
54
  handlePointerMove(event, props) {
29
55
  super.handlePointerMove(event, props);
30
56
  const clickSequence = this.getClickSequence();
@@ -41,6 +67,11 @@ var DrawCircleModeWithTooltip = class extends DrawCircleFromCenterMode {
41
67
  text: formatCircleTooltip(diameter, area, getDistanceUnitAbbreviation(distanceUnits))
42
68
  };
43
69
  }
70
+ /**
71
+ * Get the current tooltip array for rendering.
72
+ *
73
+ * @returns Array containing the tooltip if one is active, empty array otherwise
74
+ */
44
75
  getTooltips() {
45
76
  return this.tooltip ? [this.tooltip] : [];
46
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\n\n/**\n * Extends DrawCircleFromCenterMode to display diameter and area tooltip.\n *\n * Shows the diameter and area of the circle being drawn based on the radius\n * from center point to cursor position.\n */\nexport class DrawCircleModeWithTooltip extends DrawCircleFromCenterMode {\n private tooltip: Tooltip | null = null;\n\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const centerPoint = clickSequence[clickSequence.length - 1] as [\n number,\n number,\n ];\n const edgePoint = mapCoords as [number, number];\n\n const { diameter, area } = computeCircleMeasurements(\n centerPoint,\n edgePoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(diameter, area, unitAbbrev),\n };\n }\n\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;AAgCA,IAAa,4BAAb,cAA+C,yBAAyB;CACtE,AAAQ,UAA0B;CAElC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAErC,MAAM,cAAc,cAAc,cAAc,SAAS;EAMzD,MAAM,EAAE,UAAU,SAAS,0BACzB,aAHgB,WAKhB,cACD;AAGD,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,UAAU,MAJnB,4BAA4B,cAAc,CAIN;GACtD;;CAGH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-circle-mode-with-tooltip.js","names":[],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawCircleFromCenterMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport { formatCircleTooltip } from '../../shared/constants';\nimport { computeCircleMeasurements } from '../../shared/utils/geometry-measurements';\n\n/**\n * Extends DrawCircleFromCenterMode to display diameter and area tooltip.\n *\n * Shows the diameter and area of the circle being drawn based on the radius\n * from center point to cursor position. The tooltip updates in real-time as\n * the cursor moves, displaying measurements in the configured distance units.\n *\n * ## Usage\n * This mode is automatically used by DrawShapeLayer when drawing circles.\n * The mode is cached at module level to prevent deck.gl assertion failures.\n *\n * ## Drawing Flow\n * 1. Click to set center point\n * 2. Move cursor to set radius (tooltip shows diameter and area)\n * 3. Click to finish the circle\n *\n * @example\n * ```typescript\n * import { DrawCircleModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawCircleModeWithTooltip();\n * ```\n */\nexport class DrawCircleModeWithTooltip extends DrawCircleFromCenterMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip with circle measurements.\n * Calculates diameter and area based on the distance from center to cursor.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n\n const centerPoint = clickSequence[clickSequence.length - 1] as [\n number,\n number,\n ];\n const edgePoint = mapCoords as [number, number];\n\n const { diameter, area } = computeCircleMeasurements(\n centerPoint,\n edgePoint,\n distanceUnits,\n );\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n\n this.tooltip = {\n position: mapCoords,\n text: formatCircleTooltip(diameter, area, unitAbbrev),\n };\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAkDA,IAAa,4BAAb,cAA+C,yBAAyB;;CAEtE,AAAQ,UAA0B;;;;;;;;CASlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EAErC,MAAM,cAAc,cAAc,cAAc,SAAS;EAMzD,MAAM,EAAE,UAAU,SAAS,0BACzB,aAHgB,WAKhB,cACD;AAGD,OAAK,UAAU;GACb,UAAU;GACV,MAAM,oBAAoB,UAAU,MAJnB,4BAA4B,cAAc,CAIN;GACtD;;;;;;;CAQH,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -20,19 +20,43 @@ import { distance } from "@turf/turf";
20
20
  /**
21
21
  * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.
22
22
  *
23
- * Drawing process (3 clicks):
24
- * 1. First click: Sets the first endpoint of the major axis
25
- * 2. Second click: Sets the second endpoint of the major axis
23
+ * Provides different tooltip content based on the drawing stage:
24
+ * - **Stage 1** (after first click): Distance from first point to cursor
25
+ * - **Stage 2** (after second click): Major/minor axes dimensions and area
26
+ *
27
+ * ## Drawing Flow
28
+ * 1. **First click**: Sets the first endpoint of the major axis
29
+ * 2. **Second click**: Sets the second endpoint of the major axis
26
30
  * - While moving to second click, shows distance tooltip (like line/polygon)
27
- * 3. Third click: Sets the minor axis radius
28
- * - While moving to third click, shows area tooltip (like circle)
31
+ * 3. **Third click**: Sets the minor axis radius
32
+ * - While moving to third click, shows dimensions and area tooltip
33
+ *
34
+ * ## Geometry Calculations
35
+ * - **Center**: Midpoint between first and second clicks
36
+ * - **Y semi-axis**: Half the distance between clicks 1 and 2
37
+ * - **X semi-axis**: Distance from center to cursor (becomes distance to click 3)
38
+ * - **Area**: π × X semi-axis × Y semi-axis
39
+ *
40
+ * @example
41
+ * ```typescript
42
+ * import { DrawEllipseModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';
29
43
  *
30
- * The ellipse center is the midpoint between the first two clicks.
31
- * The Y semi-axis is half the distance between clicks 1 and 2.
32
- * The X semi-axis is the distance from center to click 3.
44
+ * // Used internally by DrawShapeLayer
45
+ * const mode = new DrawEllipseModeWithTooltip();
46
+ * ```
33
47
  */
34
48
  var DrawEllipseModeWithTooltip = class extends DrawEllipseUsingThreePointsMode {
49
+ /** Current tooltip state (null when not drawing) */
35
50
  tooltip = null;
51
+ /**
52
+ * Handle pointer move events to update the tooltip based on drawing stage.
53
+ *
54
+ * Stage 1 (1 click): Shows distance from first point to cursor.
55
+ * Stage 2 (2 clicks): Shows major/minor axes dimensions and ellipse area.
56
+ *
57
+ * @param event - Pointer move event with cursor position
58
+ * @param props - Mode properties including distance units configuration
59
+ */
36
60
  handlePointerMove(event, props) {
37
61
  super.handlePointerMove(event, props);
38
62
  const clickSequence = this.getClickSequence();
@@ -63,6 +87,11 @@ var DrawEllipseModeWithTooltip = class extends DrawEllipseUsingThreePointsMode {
63
87
  };
64
88
  }
65
89
  }
90
+ /**
91
+ * Get the current tooltip array for rendering.
92
+ *
93
+ * @returns Array containing the tooltip if one is active, empty array otherwise
94
+ */
66
95
  getTooltips() {
67
96
  return this.tooltip ? [this.tooltip] : [];
68
97
  }
@@ -1 +1 @@
1
- {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Drawing process (3 clicks):\n * 1. First click: Sets the first endpoint of the major axis\n * 2. Second click: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. Third click: Sets the minor axis radius\n * - While moving to third click, shows area tooltip (like circle)\n *\n * The ellipse center is the midpoint between the first two clicks.\n * The Y semi-axis is half the distance between clicks 1 and 2.\n * The X semi-axis is the distance from center to click 3.\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n private tooltip: Tooltip | null = null;\n\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA2CA,IAAa,6BAAb,cAAgD,gCAAgC;CAC9E,AAAQ,UAA0B;CAElC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EACrC,MAAM,aAAa,4BAA4B,cAAc;EAC7D,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;CAIL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
1
+ {"version":3,"file":"draw-ellipse-mode-with-tooltip.js","names":["center: [number, number]"],"sources":["../../../../../src/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.ts"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\nimport {\n DrawEllipseUsingThreePointsMode,\n type FeatureCollection,\n type ModeProps,\n type PointerMoveEvent,\n type Tooltip,\n} from '@deck.gl-community/editable-layers';\nimport { type Coord, distance } from '@turf/turf';\nimport {\n DEFAULT_DISTANCE_UNITS,\n getDistanceUnitAbbreviation,\n} from '@/shared/units';\nimport {\n formatDistanceTooltip,\n formatEllipseTooltip,\n} from '../../shared/constants';\n\n/**\n * Extends DrawEllipseUsingThreePointsMode to display contextual tooltips.\n *\n * Provides different tooltip content based on the drawing stage:\n * - **Stage 1** (after first click): Distance from first point to cursor\n * - **Stage 2** (after second click): Major/minor axes dimensions and area\n *\n * ## Drawing Flow\n * 1. **First click**: Sets the first endpoint of the major axis\n * 2. **Second click**: Sets the second endpoint of the major axis\n * - While moving to second click, shows distance tooltip (like line/polygon)\n * 3. **Third click**: Sets the minor axis radius\n * - While moving to third click, shows dimensions and area tooltip\n *\n * ## Geometry Calculations\n * - **Center**: Midpoint between first and second clicks\n * - **Y semi-axis**: Half the distance between clicks 1 and 2\n * - **X semi-axis**: Distance from center to cursor (becomes distance to click 3)\n * - **Area**: π × X semi-axis × Y semi-axis\n *\n * @example\n * ```typescript\n * import { DrawEllipseModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';\n *\n * // Used internally by DrawShapeLayer\n * const mode = new DrawEllipseModeWithTooltip();\n * ```\n */\nexport class DrawEllipseModeWithTooltip extends DrawEllipseUsingThreePointsMode {\n /** Current tooltip state (null when not drawing) */\n private tooltip: Tooltip | null = null;\n\n /**\n * Handle pointer move events to update the tooltip based on drawing stage.\n *\n * Stage 1 (1 click): Shows distance from first point to cursor.\n * Stage 2 (2 clicks): Shows major/minor axes dimensions and ellipse area.\n *\n * @param event - Pointer move event with cursor position\n * @param props - Mode properties including distance units configuration\n */\n override handlePointerMove(\n event: PointerMoveEvent,\n props: ModeProps<FeatureCollection>,\n ) {\n super.handlePointerMove(event, props);\n\n const clickSequence = this.getClickSequence();\n if (!clickSequence.length) {\n this.tooltip = null;\n return;\n }\n\n const { mapCoords } = event;\n const distanceUnits =\n props.modeConfig?.distanceUnits ?? DEFAULT_DISTANCE_UNITS;\n const unitAbbrev = getDistanceUnitAbbreviation(distanceUnits);\n const tooltipPosition = mapCoords;\n\n if (clickSequence.length === 1) {\n // First segment: show distance from first click to cursor (like line/polygon)\n const firstPoint = clickSequence[0] as Coord;\n const currentPoint = mapCoords as Coord;\n\n const dist = distance(firstPoint, currentPoint, { units: distanceUnits });\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatDistanceTooltip(dist, unitAbbrev),\n };\n } else if (clickSequence.length === 2) {\n // Second segment: show dimensions and area (like rectangle)\n // The ellipse will have:\n // - center at midpoint of click1 and click2\n // - ySemiAxis = distance(click1, click2) / 2\n // - xSemiAxis = distance(center, cursor)\n const click1 = clickSequence[0] as [number, number];\n const click2 = clickSequence[1] as [number, number];\n const cursorPos = mapCoords as [number, number];\n\n // Calculate center (midpoint)\n const center: [number, number] = [\n (click1[0] + click2[0]) / 2,\n (click1[1] + click2[1]) / 2,\n ];\n\n // Calculate semi-axes\n const ySemiAxis = distance(click1, click2, { units: distanceUnits }) / 2;\n const xSemiAxis = distance(center, cursorPos, { units: distanceUnits });\n\n // Full axes (diameter equivalent)\n const majorAxis = Math.max(xSemiAxis, ySemiAxis) * 2;\n const minorAxis = Math.min(xSemiAxis, ySemiAxis) * 2;\n\n // Ellipse area = π × a × b\n const ellipseArea = Math.PI * xSemiAxis * ySemiAxis;\n\n this.tooltip = {\n position: tooltipPosition,\n text: formatEllipseTooltip(\n majorAxis,\n minorAxis,\n ellipseArea,\n unitAbbrev,\n ),\n };\n }\n }\n\n /**\n * Get the current tooltip array for rendering.\n *\n * @returns Array containing the tooltip if one is active, empty array otherwise\n */\n override getTooltips(): Tooltip[] {\n return this.tooltip ? [this.tooltip] : [];\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyDA,IAAa,6BAAb,cAAgD,gCAAgC;;CAE9E,AAAQ,UAA0B;;;;;;;;;;CAWlC,AAAS,kBACP,OACA,OACA;AACA,QAAM,kBAAkB,OAAO,MAAM;EAErC,MAAM,gBAAgB,KAAK,kBAAkB;AAC7C,MAAI,CAAC,cAAc,QAAQ;AACzB,QAAK,UAAU;AACf;;EAGF,MAAM,EAAE,cAAc;EACtB,MAAM,gBACJ,MAAM,YAAY,iBAAiB;EACrC,MAAM,aAAa,4BAA4B,cAAc;EAC7D,MAAM,kBAAkB;AAExB,MAAI,cAAc,WAAW,GAAG;GAE9B,MAAM,aAAa,cAAc;AAKjC,QAAK,UAAU;IACb,UAAU;IACV,MAAM,sBAJK,SAAS,YAFD,WAE2B,EAAE,OAAO,eAAe,CAAC,EAIrC,WAAW;IAC9C;aACQ,cAAc,WAAW,GAAG;GAMrC,MAAM,SAAS,cAAc;GAC7B,MAAM,SAAS,cAAc;GAC7B,MAAM,YAAY;GAGlB,MAAMA,SAA2B,EAC9B,OAAO,KAAK,OAAO,MAAM,IACzB,OAAO,KAAK,OAAO,MAAM,EAC3B;GAGD,MAAM,YAAY,SAAS,QAAQ,QAAQ,EAAE,OAAO,eAAe,CAAC,GAAG;GACvE,MAAM,YAAY,SAAS,QAAQ,WAAW,EAAE,OAAO,eAAe,CAAC;AASvE,QAAK,UAAU;IACb,UAAU;IACV,MAAM,qBARU,KAAK,IAAI,WAAW,UAAU,GAAG,GACjC,KAAK,IAAI,WAAW,UAAU,GAAG,GAG/B,KAAK,KAAK,YAAY,WAQtC,WACD;IACF;;;;;;;;CASL,AAAS,cAAyB;AAChC,SAAO,KAAK,UAAU,CAAC,KAAK,QAAQ,GAAG,EAAE"}
@@ -21,17 +21,39 @@ import { distance } from "@turf/turf";
21
21
  * Extends DrawLineStringMode to display distance tooltip between points.
22
22
  *
23
23
  * Shows the distance from the last clicked point to the current cursor position
24
- * while drawing a line string.
24
+ * while drawing a line string. The tooltip updates in real-time as the cursor moves.
25
25
  *
26
+ * ## Drawing Flow
27
+ * 1. Click to add first point
28
+ * 2. Move cursor (tooltip shows distance from last point)
29
+ * 3. Click to add more points
30
+ * 4. Double-click to finish the line string
31
+ *
32
+ * ## Double-Click Workaround
26
33
  * Includes a workaround for the double-click to finish issue in @deck.gl-community/editable-layers ~9.1.
27
34
  * This will be fixed in a future version (PR #225).
35
+ *
36
+ * @example
37
+ * ```typescript
38
+ * import { DrawLineStringModeWithTooltip } from '@accelint/map-toolkit/deckgl/shapes/draw-shape-layer/modes';
39
+ *
40
+ * // Used internally by DrawShapeLayer
41
+ * const mode = new DrawLineStringModeWithTooltip();
42
+ * ```
28
43
  */
29
44
  var DrawLineStringModeWithTooltip = class extends DrawLineStringMode {
45
+ /** Current tooltip state (null when not drawing) */
30
46
  tooltip = null;
47
+ /** Cached mode props for double-click workaround */
31
48
  lastModeProps = null;
32
49
  /**
33
50
  * Finish drawing the line string.
51
+ *
52
+ * Creates a LineString geometry from the click sequence and emits an edit action.
53
+ * Requires at least 2 points to create a valid line string.
34
54
  * Extracted to share between double-click workaround and parent class logic.
55
+ *
56
+ * @param props - Mode properties with onEdit callback
35
57
  */
36
58
  finishDrawing(props) {
37
59
  const clickSequence = this.getClickSequence();
@@ -57,11 +79,26 @@ var DrawLineStringModeWithTooltip = class extends DrawLineStringMode {
57
79
  }
58
80
  /**
59
81
  * Override handleClick to store props for double-click workaround.
82
+ *
83
+ * Caches the mode props so that the external double-click handler can
84
+ * access them when finishing the drawing.
85
+ *
86
+ * @param event - Click event with map coordinates
87
+ * @param props - Mode properties including onEdit callback
60
88
  */
61
89
  handleClick(event, props) {
62
90
  this.lastModeProps = props;
63
91
  super.handleClick(event, props);
64
92
  }
93
+ /**
94
+ * Handle pointer move events to update the tooltip with distance.
95
+ *
96
+ * Calculates the distance from the last clicked point to the current
97
+ * cursor position and displays it in the configured distance units.
98
+ *
99
+ * @param event - Pointer move event with cursor position
100
+ * @param props - Mode properties including distance units configuration
101
+ */
65
102
  handlePointerMove(event, props) {
66
103
  super.handlePointerMove(event, props);
67
104
  const clickSequence = this.getClickSequence();
@@ -77,6 +114,11 @@ var DrawLineStringModeWithTooltip = class extends DrawLineStringMode {
77
114
  text: formatDistanceTooltip(distance(lastPoint, mapCoords, { units: distanceUnits }), getDistanceUnitAbbreviation(distanceUnits))
78
115
  };
79
116
  }
117
+ /**
118
+ * Get the current tooltip array for rendering.
119
+ *
120
+ * @returns Array containing the tooltip if one is active, empty array otherwise
121
+ */
80
122
  getTooltips() {
81
123
  return this.tooltip ? [this.tooltip] : [];
82
124
  }