@accelint/map-toolkit 1.1.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +17 -0
- package/catalog-info.yaml +3 -3
- package/dist/camera/events.d.ts +45 -0
- package/dist/camera/events.js +45 -0
- package/dist/camera/events.js.map +1 -1
- package/dist/camera/store.d.ts +47 -0
- package/dist/camera/store.js +81 -0
- package/dist/camera/store.js.map +1 -1
- package/dist/camera/types.d.ts +81 -0
- package/dist/cursor-coordinates/constants.d.ts +8 -0
- package/dist/cursor-coordinates/constants.js +22 -0
- package/dist/cursor-coordinates/constants.js.map +1 -0
- package/dist/cursor-coordinates/store.d.ts +1 -0
- package/dist/cursor-coordinates/store.js +1 -0
- package/dist/cursor-coordinates/store.js.map +1 -1
- package/dist/cursor-coordinates/use-cursor-coordinates.d.ts +5 -0
- package/dist/cursor-coordinates/use-cursor-coordinates.js +21 -6
- package/dist/cursor-coordinates/use-cursor-coordinates.js.map +1 -1
- package/dist/deckgl/base-map/constants.d.ts +12 -0
- package/dist/deckgl/base-map/constants.js +12 -0
- package/dist/deckgl/base-map/constants.js.map +1 -1
- package/dist/deckgl/base-map/controls.d.ts +10 -0
- package/dist/deckgl/base-map/controls.js +5 -0
- package/dist/deckgl/base-map/controls.js.map +1 -1
- package/dist/deckgl/base-map/events.d.ts +30 -0
- package/dist/deckgl/base-map/events.js +30 -0
- package/dist/deckgl/base-map/events.js.map +1 -1
- package/dist/deckgl/base-map/index.d.ts +2 -2
- package/dist/deckgl/base-map/index.js +30 -0
- package/dist/deckgl/base-map/index.js.map +1 -1
- package/dist/deckgl/saved-viewports/index.d.ts +75 -0
- package/dist/deckgl/saved-viewports/index.js +58 -0
- package/dist/deckgl/saved-viewports/index.js.map +1 -1
- package/dist/deckgl/saved-viewports/storage.d.ts +51 -0
- package/dist/deckgl/saved-viewports/storage.js +64 -0
- package/dist/deckgl/saved-viewports/storage.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/constants.js +18 -6
- package/dist/deckgl/shapes/display-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/fiber.d.ts +7 -0
- package/dist/deckgl/shapes/display-shape-layer/fiber.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js +61 -4
- package/dist/deckgl/shapes/display-shape-layer/utils/display-style.js.map +1 -1
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.d.ts +22 -8
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js +75 -4
- package/dist/deckgl/shapes/display-shape-layer/utils/labels.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/constants.js +30 -0
- package/dist/deckgl/shapes/draw-shape-layer/constants.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js +36 -0
- package/dist/deckgl/shapes/draw-shape-layer/fiber.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js +32 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-circle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js +37 -8
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-ellipse-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js +43 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-line-string-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js +44 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-polygon-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js +46 -3
- package/dist/deckgl/shapes/draw-shape-layer/modes/draw-rectangle-mode-with-tooltip.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js +37 -1
- package/dist/deckgl/shapes/draw-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/store.js +50 -2
- package/dist/deckgl/shapes/draw-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js +137 -16
- package/dist/deckgl/shapes/draw-shape-layer/utils/feature-conversion.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/events.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/index.d.ts +2 -2
- package/dist/deckgl/shapes/edit-shape-layer/index.js +14 -0
- package/dist/deckgl/shapes/edit-shape-layer/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js +56 -8
- package/dist/deckgl/shapes/edit-shape-layer/modes/base-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js +26 -4
- package/dist/deckgl/shapes/edit-shape-layer/modes/bounding-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js +28 -3
- package/dist/deckgl/shapes/edit-shape-layer/modes/circle-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js +24 -0
- package/dist/deckgl/shapes/edit-shape-layer/modes/index.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js +33 -4
- package/dist/deckgl/shapes/edit-shape-layer/modes/rotate-mode-with-snap.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js +21 -2
- package/dist/deckgl/shapes/edit-shape-layer/modes/scale-mode-with-free-transform.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js +35 -11
- package/dist/deckgl/shapes/edit-shape-layer/modes/vertex-transform-mode.js.map +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js +1 -1
- package/dist/deckgl/shapes/edit-shape-layer/store.js.map +1 -1
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js +12 -0
- package/dist/deckgl/shapes/shared/hooks/use-shift-zoom-disable.js.map +1 -1
- package/dist/deckgl/shapes/shared/types.d.ts +3 -3
- package/dist/deckgl/shapes/shared/types.js +2 -2
- package/dist/deckgl/shapes/shared/types.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js +3 -3
- package/dist/deckgl/shapes/shared/utils/geometry-measurements.js.map +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js +1 -1
- package/dist/deckgl/shapes/shared/utils/pick-filtering.js.map +1 -1
- package/dist/deckgl/symbol-layer/fiber.d.ts +18 -0
- package/dist/deckgl/symbol-layer/fiber.js.map +1 -1
- package/dist/deckgl/symbol-layer/index.d.ts +79 -1
- package/dist/deckgl/symbol-layer/index.js +72 -1
- package/dist/deckgl/symbol-layer/index.js.map +1 -1
- package/dist/deckgl/text-layer/character-sets.d.ts +30 -0
- package/dist/deckgl/text-layer/character-sets.js +26 -0
- package/dist/deckgl/text-layer/character-sets.js.map +1 -1
- package/dist/deckgl/text-layer/default-settings.d.ts +29 -0
- package/dist/deckgl/text-layer/default-settings.js +28 -0
- package/dist/deckgl/text-layer/default-settings.js.map +1 -1
- package/dist/deckgl/text-layer/index.d.ts +65 -0
- package/dist/deckgl/text-layer/index.js +56 -0
- package/dist/deckgl/text-layer/index.js.map +1 -1
- package/dist/map-cursor/events.d.ts +19 -0
- package/dist/map-cursor/events.js +19 -0
- package/dist/map-cursor/events.js.map +1 -1
- package/dist/map-cursor/store.d.ts +34 -2
- package/dist/map-cursor/store.js +44 -3
- package/dist/map-cursor/store.js.map +1 -1
- package/dist/map-mode/store.d.ts +43 -4
- package/dist/map-mode/store.js +55 -5
- package/dist/map-mode/store.js.map +1 -1
- package/dist/shared/create-map-store.d.ts +14 -0
- package/dist/shared/create-map-store.js +26 -2
- package/dist/shared/create-map-store.js.map +1 -1
- package/dist/shared/units.d.ts +24 -0
- package/dist/shared/units.js +24 -0
- package/dist/shared/units.js.map +1 -1
- package/dist/viewport/store.d.ts +1 -0
- package/dist/viewport/store.js +4 -0
- package/dist/viewport/store.js.map +1 -1
- package/package.json +7 -7
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
'use client';
|
|
15
15
|
|
|
16
|
+
import { DEFAULT_LATLON_COORDS, DEFAULT_MGRS_UTM_COORDS, LONGITUDE_RANGE, MAX_LONGITUDE } from "./constants.js";
|
|
16
17
|
import { cursorCoordinateStore } from "./store.js";
|
|
17
18
|
import { MapContext } from "../deckgl/base-map/provider.js";
|
|
18
19
|
import { useContext, useMemo } from "react";
|
|
@@ -26,9 +27,6 @@ const logger = getLogger({
|
|
|
26
27
|
prefix: "[CursorCoordinates]",
|
|
27
28
|
pretty: true
|
|
28
29
|
});
|
|
29
|
-
const MAX_LONGITUDE = 180;
|
|
30
|
-
const LONGITUDE_RANGE = 360;
|
|
31
|
-
const DEFAULT_COORDINATE = "--, --";
|
|
32
30
|
/**
|
|
33
31
|
* Normalizes longitude to -180 to 180 range.
|
|
34
32
|
* Handles wraparound including multi-revolution values.
|
|
@@ -62,6 +60,11 @@ function buildRawCoordinate(coord) {
|
|
|
62
60
|
* @param coord - Coordinate tuple [longitude, latitude]
|
|
63
61
|
* @param format - Coordinate format type
|
|
64
62
|
* @returns Formatted coordinate string
|
|
63
|
+
* *
|
|
64
|
+
* @remarks
|
|
65
|
+
* **UTM/MGRS Limitations:** UTM and MGRS coordinate systems are only valid between 80°S and 84°N.
|
|
66
|
+
* Coordinates outside this range (e.g., polar regions) will return the default placeholder `--, --`.
|
|
67
|
+
* Other formats (DD, DDM, DMS) work correctly at all latitudes.
|
|
65
68
|
*/
|
|
66
69
|
function formatCoordinate(coord, format) {
|
|
67
70
|
const normalizedLon = normalizeLongitude(coord[0]);
|
|
@@ -89,10 +92,16 @@ function formatCoordinate(coord, format) {
|
|
|
89
92
|
case "utm": {
|
|
90
93
|
const lat = latLon[0];
|
|
91
94
|
const lon = latLon[1];
|
|
95
|
+
if (lat < -80 || lat > 84) return DEFAULT_MGRS_UTM_COORDS;
|
|
92
96
|
const latOrdinal = lat >= 0 ? "N" : "S";
|
|
93
97
|
const lonOrdinal = lon >= 0 ? "E" : "W";
|
|
94
98
|
const formattedInput = `${Math.abs(lon).toFixed(10)} ${lonOrdinal} / ${Math.abs(lat).toFixed(10)} ${latOrdinal}`;
|
|
95
|
-
|
|
99
|
+
const geoCoord = createCoordinate(coordinateSystems.dd, "LONLAT")(formattedInput);
|
|
100
|
+
if (!geoCoord.valid) {
|
|
101
|
+
logger.error(`Failed to create coordinate for ${format}: ${geoCoord.errors.join(", ")}`);
|
|
102
|
+
return DEFAULT_MGRS_UTM_COORDS;
|
|
103
|
+
}
|
|
104
|
+
return geoCoord[format]();
|
|
96
105
|
}
|
|
97
106
|
}
|
|
98
107
|
}
|
|
@@ -110,6 +119,11 @@ function formatCoordinate(coord, format) {
|
|
|
110
119
|
* @returns Object containing the formatted coordinate string, raw coordinate, format setter, and current format
|
|
111
120
|
* @throws {Error} When no id is provided and hook is used outside MapProvider context
|
|
112
121
|
*
|
|
122
|
+
* @remarks
|
|
123
|
+
* **UTM/MGRS Limitations:** UTM and MGRS coordinate systems are only valid between 80°S and 84°N.
|
|
124
|
+
* Coordinates outside this range (e.g., polar regions) will display the default placeholder `--, --`.
|
|
125
|
+
* Other formats (DD, DDM, DMS) work correctly at all latitudes.
|
|
126
|
+
*
|
|
113
127
|
* @example
|
|
114
128
|
* Basic usage:
|
|
115
129
|
* ```tsx
|
|
@@ -177,12 +191,13 @@ function useCursorCoordinates(id, options) {
|
|
|
177
191
|
const { state, setFormat } = cursorCoordinateStore.use(actualId);
|
|
178
192
|
const rawCoord = useMemo(() => buildRawCoordinate(state.coordinate), [state.coordinate]);
|
|
179
193
|
const formattedCoord = useMemo(() => {
|
|
180
|
-
|
|
194
|
+
const getDefaultCoords = () => state.format === "mgrs" || state.format === "utm" ? DEFAULT_MGRS_UTM_COORDS : DEFAULT_LATLON_COORDS;
|
|
195
|
+
if (!(rawCoord && state.coordinate)) return getDefaultCoords();
|
|
181
196
|
if (customFormatter) try {
|
|
182
197
|
return customFormatter(rawCoord);
|
|
183
198
|
} catch (error) {
|
|
184
199
|
logger.error(`Custom formatter failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
185
|
-
return
|
|
200
|
+
return getDefaultCoords();
|
|
186
201
|
}
|
|
187
202
|
return formatCoordinate(state.coordinate, state.format);
|
|
188
203
|
}, [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"use-cursor-coordinates.js","names":["latLon: [number, number]"],"sources":["../../src/cursor-coordinates/use-cursor-coordinates.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'use client';\n\nimport 'client-only';\nimport {\n coordinateSystems,\n createCoordinate,\n formatDecimalDegrees,\n formatDegreesDecimalMinutes,\n formatDegreesMinutesSeconds,\n} from '@accelint/geo';\nimport { getLogger } from '@accelint/logger';\nimport { useContext, useMemo } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport { cursorCoordinateStore } from './store';\nimport type { UniqueId } from '@accelint/core';\nimport type {\n CoordinateFormatTypes,\n RawCoordinate,\n UseCursorCoordinatesOptions,\n UseCursorCoordinatesReturn,\n} from './types';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[CursorCoordinates]',\n pretty: true,\n});\n\nconst MAX_LONGITUDE = 180;\nconst LONGITUDE_RANGE = 360;\nconst DEFAULT_COORDINATE = '--, --';\n\n/**\n * Normalizes longitude to -180 to 180 range.\n * Handles wraparound including multi-revolution values.\n *\n * @param lon - Longitude value in degrees\n * @returns Normalized longitude between -180 and 180\n */\nfunction normalizeLongitude(lon: number): number {\n return (\n ((((lon + MAX_LONGITUDE) % LONGITUDE_RANGE) + LONGITUDE_RANGE) %\n LONGITUDE_RANGE) -\n MAX_LONGITUDE\n );\n}\n\n/**\n * Builds a RawCoordinate object from a coordinate tuple.\n *\n * @param coord - Coordinate tuple [longitude, latitude] or null\n * @returns RawCoordinate object or null\n */\nfunction buildRawCoordinate(coord: [number, number] | null): RawCoordinate {\n if (!coord) {\n return null;\n }\n\n const normalizedLon = normalizeLongitude(coord[0]);\n\n return {\n longitude: normalizedLon,\n latitude: coord[1],\n };\n}\n\n/**\n * Formats a coordinate using the specified format.\n * Uses @accelint/geo formatters which match CoordinateField precision:\n * - DD: 6 decimal places\n * - DDM: 4 decimal places for minutes\n * - DMS: 2 decimal places for seconds\n *\n * @param coord - Coordinate tuple [longitude, latitude]\n * @param format - Coordinate format type\n * @returns Formatted coordinate string\n */\nfunction formatCoordinate(\n coord: [number, number],\n format: CoordinateFormatTypes,\n): string {\n // Normalize longitude and convert to [lat, lon] for geo formatters\n const normalizedLon = normalizeLongitude(coord[0]);\n const latLon: [number, number] = [coord[1], normalizedLon];\n\n switch (format) {\n case 'dd':\n return formatDecimalDegrees(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'ddm':\n return formatDegreesDecimalMinutes(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'dms':\n return formatDegreesMinutesSeconds(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'mgrs':\n case 'utm': {\n // Use createCoordinate for grid-based formats\n // Input format: \"lon E / lat N\" for LONLAT (matching geo package DD tests)\n // Limit to 10 decimal places (geo parser max) and avoid floating point precision issues\n const lat = latLon[0];\n const lon = latLon[1];\n const latOrdinal = lat >= 0 ? 'N' : 'S';\n const lonOrdinal = lon >= 0 ? 'E' : 'W';\n // Use LONLAT format: longitude first, then latitude\n // toFixed(10) ensures we stay within the parser's regex limits\n const formattedInput = `${Math.abs(lon).toFixed(10)} ${lonOrdinal} / ${Math.abs(lat).toFixed(10)} ${latOrdinal}`;\n\n const geoCoord = createCoordinate(\n coordinateSystems.dd,\n 'LONLAT',\n )(formattedInput);\n\n return geoCoord[format]();\n }\n }\n}\n\n/**\n * React hook that tracks and formats the cursor hover position coordinates on a map.\n *\n * Subscribes to map hover events via the event bus and converts coordinates to various\n * geographic formats (Decimal Degrees, DMS, MGRS, UTM, etc.). The hook automatically\n * filters events to only process those from the specified map instance.\n *\n * Uses the shared store factory for efficient state management and automatic cleanup.\n *\n * @param id - Optional map instance ID. If not provided, attempts to use the ID from MapProvider context.\n * @param options - Optional configuration options\n * @returns Object containing the formatted coordinate string, raw coordinate, format setter, and current format\n * @throws {Error} When no id is provided and hook is used outside MapProvider context\n *\n * @example\n * Basic usage:\n * ```tsx\n * import { uuid } from '@accelint/core';\n * import { useCursorCoordinates } from '@accelint/map-toolkit/cursor-coordinates';\n *\n * const MAP_ID = uuid();\n *\n * function CoordinateDisplay() {\n * const { formattedCoord, setFormat } = useCursorCoordinates(MAP_ID);\n *\n * return (\n * <div>\n * <select onChange={(e) => setFormat(e.target.value as CoordinateFormatTypes)}>\n * <option value=\"dd\">Decimal Degrees</option>\n * <option value=\"ddm\">Degrees Decimal Minutes</option>\n * <option value=\"dms\">Degrees Minutes Seconds</option>\n * <option value=\"mgrs\">MGRS</option>\n * <option value=\"utm\">UTM</option>\n * </select>\n * <div>{formattedCoord}</div>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * With custom formatter:\n * ```tsx\n * function CustomCoordinateDisplay() {\n * const { formattedCoord, rawCoord } = useCursorCoordinates(MAP_ID, {\n * formatter: (coord) =>\n * `Lat: ${coord.latitude.toFixed(6)}° Lng: ${coord.longitude.toFixed(6)}°`,\n * });\n *\n * return <div>{formattedCoord}</div>;\n * }\n * ```\n *\n * @example\n * Accessing raw coordinates:\n * ```tsx\n * function RawCoordinateDisplay() {\n * const { rawCoord, currentFormat } = useCursorCoordinates(MAP_ID);\n *\n * if (!rawCoord) {\n * return <div>Move cursor over map</div>;\n * }\n *\n * return (\n * <div>\n * <div>Longitude: {rawCoord.longitude}</div>\n * <div>Latitude: {rawCoord.latitude}</div>\n * <div>Format: {currentFormat}</div>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCursorCoordinates(\n id?: UniqueId,\n options?: UseCursorCoordinatesOptions,\n): UseCursorCoordinatesReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useCursorCoordinates requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n const customFormatter = options?.formatter;\n\n // Use the store hook to get state and actions\n const { state, setFormat } = cursorCoordinateStore.use(actualId);\n\n // Build raw coordinate object\n const rawCoord = useMemo(\n () => buildRawCoordinate(state.coordinate),\n [state.coordinate],\n );\n\n // Compute formatted coordinate string\n const formattedCoord = useMemo(() => {\n if (!rawCoord) {\n return DEFAULT_COORDINATE;\n }\n\n // Use custom formatter if provided\n if (customFormatter) {\n try {\n return customFormatter(rawCoord);\n } catch (error) {\n logger.error(\n `Custom formatter failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return DEFAULT_COORDINATE;\n }\n }\n\n // Use built-in formatter\n return formatCoordinate(state.coordinate!, state.format);\n }, [rawCoord, customFormatter, state.format, state.coordinate]);\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n formattedCoord,\n setFormat,\n rawCoord,\n currentFormat: state.format,\n }),\n [formattedCoord, setFormat, rawCoord, state.format],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiCA,MAAM,SAAS,UAAU;CACvB,SACE,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,aAAa;CACpE,OAAO;CACP,QAAQ;CACR,QAAQ;CACT,CAAC;AAEF,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,qBAAqB;;;;;;;;AAS3B,SAAS,mBAAmB,KAAqB;AAC/C,UACM,MAAM,iBAAiB,kBAAmB,mBAC5C,kBACF;;;;;;;;AAUJ,SAAS,mBAAmB,OAA+C;AACzE,KAAI,CAAC,MACH,QAAO;AAKT,QAAO;EACL,WAHoB,mBAAmB,MAAM,GAAG;EAIhD,UAAU,MAAM;EACjB;;;;;;;;;;;;;AAcH,SAAS,iBACP,OACA,QACQ;CAER,MAAM,gBAAgB,mBAAmB,MAAM,GAAG;CAClD,MAAMA,SAA2B,CAAC,MAAM,IAAI,cAAc;AAE1D,SAAQ,QAAR;EACE,KAAK,KACH,QAAO,qBAAqB,QAAQ;GAClC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK,MACH,QAAO,4BAA4B,QAAQ;GACzC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK,MACH,QAAO,4BAA4B,QAAQ;GACzC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK;EACL,KAAK,OAAO;GAIV,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;GACnB,MAAM,aAAa,OAAO,IAAI,MAAM;GACpC,MAAM,aAAa,OAAO,IAAI,MAAM;GAGpC,MAAM,iBAAiB,GAAG,KAAK,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,WAAW,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG;AAOpG,UALiB,iBACf,kBAAkB,IAClB,SACD,CAAC,eAAe,CAED,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA8E/B,SAAgB,qBACd,IACA,SAC4B;CAC5B,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,0FACD;CAGH,MAAM,kBAAkB,SAAS;CAGjC,MAAM,EAAE,OAAO,cAAc,sBAAsB,IAAI,SAAS;CAGhE,MAAM,WAAW,cACT,mBAAmB,MAAM,WAAW,EAC1C,CAAC,MAAM,WAAW,CACnB;CAGD,MAAM,iBAAiB,cAAc;AACnC,MAAI,CAAC,SACH,QAAO;AAIT,MAAI,gBACF,KAAI;AACF,UAAO,gBAAgB,SAAS;WACzB,OAAO;AACd,UAAO,MACL,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACnF;AACD,UAAO;;AAKX,SAAO,iBAAiB,MAAM,YAAa,MAAM,OAAO;IACvD;EAAC;EAAU;EAAiB,MAAM;EAAQ,MAAM;EAAW,CAAC;AAG/D,QAAO,eACE;EACL;EACA;EACA;EACA,eAAe,MAAM;EACtB,GACD;EAAC;EAAgB;EAAW;EAAU,MAAM;EAAO,CACpD"}
|
|
1
|
+
{"version":3,"file":"use-cursor-coordinates.js","names":["latLon: [number, number]"],"sources":["../../src/cursor-coordinates/use-cursor-coordinates.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'use client';\n\nimport type { UniqueId } from '@accelint/core';\nimport {\n coordinateSystems,\n createCoordinate,\n formatDecimalDegrees,\n formatDegreesDecimalMinutes,\n formatDegreesMinutesSeconds,\n} from '@accelint/geo';\nimport { getLogger } from '@accelint/logger';\nimport 'client-only';\nimport { useContext, useMemo } from 'react';\nimport { MapContext } from '../deckgl/base-map/provider';\nimport {\n DEFAULT_LATLON_COORDS,\n DEFAULT_MGRS_UTM_COORDS,\n LONGITUDE_RANGE,\n MAX_LONGITUDE,\n} from './constants';\nimport { cursorCoordinateStore } from './store';\nimport type {\n CoordinateFormatTypes,\n RawCoordinate,\n UseCursorCoordinatesOptions,\n UseCursorCoordinatesReturn,\n} from './types';\n\nconst logger = getLogger({\n enabled:\n process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test',\n level: 'warn',\n prefix: '[CursorCoordinates]',\n pretty: true,\n});\n\n/**\n * Normalizes longitude to -180 to 180 range.\n * Handles wraparound including multi-revolution values.\n *\n * @param lon - Longitude value in degrees\n * @returns Normalized longitude between -180 and 180\n */\nfunction normalizeLongitude(lon: number): number {\n return (\n ((((lon + MAX_LONGITUDE) % LONGITUDE_RANGE) + LONGITUDE_RANGE) %\n LONGITUDE_RANGE) -\n MAX_LONGITUDE\n );\n}\n\n/**\n * Builds a RawCoordinate object from a coordinate tuple.\n *\n * @param coord - Coordinate tuple [longitude, latitude] or null\n * @returns RawCoordinate object or null\n */\nfunction buildRawCoordinate(coord: [number, number] | null): RawCoordinate {\n if (!coord) {\n return null;\n }\n\n const normalizedLon = normalizeLongitude(coord[0]);\n\n return {\n longitude: normalizedLon,\n latitude: coord[1],\n };\n}\n\n/**\n * Formats a coordinate using the specified format.\n * Uses @accelint/geo formatters which match CoordinateField precision:\n * - DD: 6 decimal places\n * - DDM: 4 decimal places for minutes\n * - DMS: 2 decimal places for seconds\n *\n * @param coord - Coordinate tuple [longitude, latitude]\n * @param format - Coordinate format type\n * @returns Formatted coordinate string\n * *\n * @remarks\n * **UTM/MGRS Limitations:** UTM and MGRS coordinate systems are only valid between 80°S and 84°N.\n * Coordinates outside this range (e.g., polar regions) will return the default placeholder `--, --`.\n * Other formats (DD, DDM, DMS) work correctly at all latitudes.\n */\nfunction formatCoordinate(\n coord: [number, number],\n format: CoordinateFormatTypes,\n): string {\n // Normalize longitude and convert to [lat, lon] for geo formatters\n const normalizedLon = normalizeLongitude(coord[0]);\n const latLon: [number, number] = [coord[1], normalizedLon];\n\n switch (format) {\n case 'dd':\n return formatDecimalDegrees(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'ddm':\n return formatDegreesDecimalMinutes(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'dms':\n return formatDegreesMinutesSeconds(latLon, {\n withOrdinal: true,\n separator: ' / ',\n prefix: '',\n suffix: '',\n });\n case 'mgrs':\n case 'utm': {\n // UTM and MGRS are only valid between 80°S and 84°N\n // Use createCoordinate for grid-based formats\n // Input format: \"lon E / lat N\" for LONLAT (matching geo package DD tests)\n // Limit to 10 decimal places (geo parser max) and avoid floating point precision issues\n const lat = latLon[0];\n const lon = latLon[1];\n\n // Check if coordinate is within valid UTM/MGRS range\n if (lat < -80 || lat > 84) {\n return DEFAULT_MGRS_UTM_COORDS;\n }\n\n const latOrdinal = lat >= 0 ? 'N' : 'S';\n const lonOrdinal = lon >= 0 ? 'E' : 'W';\n // Use LONLAT format: longitude first, then latitude\n // toFixed(10) ensures we stay within the parser's regex limits\n const formattedInput = `${Math.abs(lon).toFixed(10)} ${lonOrdinal} / ${Math.abs(lat).toFixed(10)} ${latOrdinal}`;\n\n const geoCoord = createCoordinate(\n coordinateSystems.dd,\n 'LONLAT',\n )(formattedInput);\n\n // Validate the coordinate was created successfully\n if (!geoCoord.valid) {\n logger.error(\n `Failed to create coordinate for ${format}: ${geoCoord.errors.join(', ')}`,\n );\n return DEFAULT_MGRS_UTM_COORDS;\n }\n\n return geoCoord[format]();\n }\n }\n}\n\n/**\n * React hook that tracks and formats the cursor hover position coordinates on a map.\n *\n * Subscribes to map hover events via the event bus and converts coordinates to various\n * geographic formats (Decimal Degrees, DMS, MGRS, UTM, etc.). The hook automatically\n * filters events to only process those from the specified map instance.\n *\n * Uses the shared store factory for efficient state management and automatic cleanup.\n *\n * @param id - Optional map instance ID. If not provided, attempts to use the ID from MapProvider context.\n * @param options - Optional configuration options\n * @returns Object containing the formatted coordinate string, raw coordinate, format setter, and current format\n * @throws {Error} When no id is provided and hook is used outside MapProvider context\n *\n * @remarks\n * **UTM/MGRS Limitations:** UTM and MGRS coordinate systems are only valid between 80°S and 84°N.\n * Coordinates outside this range (e.g., polar regions) will display the default placeholder `--, --`.\n * Other formats (DD, DDM, DMS) work correctly at all latitudes.\n *\n * @example\n * Basic usage:\n * ```tsx\n * import { uuid } from '@accelint/core';\n * import { useCursorCoordinates } from '@accelint/map-toolkit/cursor-coordinates';\n *\n * const MAP_ID = uuid();\n *\n * function CoordinateDisplay() {\n * const { formattedCoord, setFormat } = useCursorCoordinates(MAP_ID);\n *\n * return (\n * <div>\n * <select onChange={(e) => setFormat(e.target.value as CoordinateFormatTypes)}>\n * <option value=\"dd\">Decimal Degrees</option>\n * <option value=\"ddm\">Degrees Decimal Minutes</option>\n * <option value=\"dms\">Degrees Minutes Seconds</option>\n * <option value=\"mgrs\">MGRS</option>\n * <option value=\"utm\">UTM</option>\n * </select>\n * <div>{formattedCoord}</div>\n * </div>\n * );\n * }\n * ```\n *\n * @example\n * With custom formatter:\n * ```tsx\n * function CustomCoordinateDisplay() {\n * const { formattedCoord, rawCoord } = useCursorCoordinates(MAP_ID, {\n * formatter: (coord) =>\n * `Lat: ${coord.latitude.toFixed(6)}° Lng: ${coord.longitude.toFixed(6)}°`,\n * });\n *\n * return <div>{formattedCoord}</div>;\n * }\n * ```\n *\n * @example\n * Accessing raw coordinates:\n * ```tsx\n * function RawCoordinateDisplay() {\n * const { rawCoord, currentFormat } = useCursorCoordinates(MAP_ID);\n *\n * if (!rawCoord) {\n * return <div>Move cursor over map</div>;\n * }\n *\n * return (\n * <div>\n * <div>Longitude: {rawCoord.longitude}</div>\n * <div>Latitude: {rawCoord.latitude}</div>\n * <div>Format: {currentFormat}</div>\n * </div>\n * );\n * }\n * ```\n */\nexport function useCursorCoordinates(\n id?: UniqueId,\n options?: UseCursorCoordinatesOptions,\n): UseCursorCoordinatesReturn {\n const contextId = useContext(MapContext);\n const actualId = id ?? contextId;\n\n if (!actualId) {\n throw new Error(\n 'useCursorCoordinates requires either an id parameter or to be used within a MapProvider',\n );\n }\n\n const customFormatter = options?.formatter;\n\n // Use the store hook to get state and actions\n const { state, setFormat } = cursorCoordinateStore.use(actualId);\n\n // Build raw coordinate object\n const rawCoord = useMemo(\n () => buildRawCoordinate(state.coordinate),\n [state.coordinate],\n );\n\n // Compute formatted coordinate string\n const formattedCoord = useMemo(() => {\n // Return default coords based on current format.\n const getDefaultCoords = () =>\n state.format === 'mgrs' || state.format === 'utm'\n ? DEFAULT_MGRS_UTM_COORDS\n : DEFAULT_LATLON_COORDS;\n\n if (!(rawCoord && state.coordinate)) {\n return getDefaultCoords();\n }\n\n // Use custom formatter if provided\n if (customFormatter) {\n try {\n return customFormatter(rawCoord);\n } catch (error) {\n logger.error(\n `Custom formatter failed: ${error instanceof Error ? error.message : String(error)}`,\n );\n return getDefaultCoords();\n }\n }\n\n // Use built-in formatter\n return formatCoordinate(state.coordinate, state.format);\n }, [rawCoord, customFormatter, state.format, state.coordinate]);\n\n // Memoize the return value to prevent unnecessary re-renders\n return useMemo(\n () => ({\n formattedCoord,\n setFormat,\n rawCoord,\n currentFormat: state.format,\n }),\n [formattedCoord, setFormat, rawCoord, state.format],\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAuCA,MAAM,SAAS,UAAU;CACvB,SACE,QAAQ,IAAI,aAAa,gBAAgB,QAAQ,IAAI,aAAa;CACpE,OAAO;CACP,QAAQ;CACR,QAAQ;CACT,CAAC;;;;;;;;AASF,SAAS,mBAAmB,KAAqB;AAC/C,UACM,MAAM,iBAAiB,kBAAmB,mBAC5C,kBACF;;;;;;;;AAUJ,SAAS,mBAAmB,OAA+C;AACzE,KAAI,CAAC,MACH,QAAO;AAKT,QAAO;EACL,WAHoB,mBAAmB,MAAM,GAAG;EAIhD,UAAU,MAAM;EACjB;;;;;;;;;;;;;;;;;;AAmBH,SAAS,iBACP,OACA,QACQ;CAER,MAAM,gBAAgB,mBAAmB,MAAM,GAAG;CAClD,MAAMA,SAA2B,CAAC,MAAM,IAAI,cAAc;AAE1D,SAAQ,QAAR;EACE,KAAK,KACH,QAAO,qBAAqB,QAAQ;GAClC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK,MACH,QAAO,4BAA4B,QAAQ;GACzC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK,MACH,QAAO,4BAA4B,QAAQ;GACzC,aAAa;GACb,WAAW;GACX,QAAQ;GACR,QAAQ;GACT,CAAC;EACJ,KAAK;EACL,KAAK,OAAO;GAKV,MAAM,MAAM,OAAO;GACnB,MAAM,MAAM,OAAO;AAGnB,OAAI,MAAM,OAAO,MAAM,GACrB,QAAO;GAGT,MAAM,aAAa,OAAO,IAAI,MAAM;GACpC,MAAM,aAAa,OAAO,IAAI,MAAM;GAGpC,MAAM,iBAAiB,GAAG,KAAK,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG,WAAW,KAAK,KAAK,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,GAAG;GAEpG,MAAM,WAAW,iBACf,kBAAkB,IAClB,SACD,CAAC,eAAe;AAGjB,OAAI,CAAC,SAAS,OAAO;AACnB,WAAO,MACL,mCAAmC,OAAO,IAAI,SAAS,OAAO,KAAK,KAAK,GACzE;AACD,WAAO;;AAGT,UAAO,SAAS,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAmF/B,SAAgB,qBACd,IACA,SAC4B;CAC5B,MAAM,YAAY,WAAW,WAAW;CACxC,MAAM,WAAW,MAAM;AAEvB,KAAI,CAAC,SACH,OAAM,IAAI,MACR,0FACD;CAGH,MAAM,kBAAkB,SAAS;CAGjC,MAAM,EAAE,OAAO,cAAc,sBAAsB,IAAI,SAAS;CAGhE,MAAM,WAAW,cACT,mBAAmB,MAAM,WAAW,EAC1C,CAAC,MAAM,WAAW,CACnB;CAGD,MAAM,iBAAiB,cAAc;EAEnC,MAAM,yBACJ,MAAM,WAAW,UAAU,MAAM,WAAW,QACxC,0BACA;AAEN,MAAI,EAAE,YAAY,MAAM,YACtB,QAAO,kBAAkB;AAI3B,MAAI,gBACF,KAAI;AACF,UAAO,gBAAgB,SAAS;WACzB,OAAO;AACd,UAAO,MACL,4BAA4B,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM,GACnF;AACD,UAAO,kBAAkB;;AAK7B,SAAO,iBAAiB,MAAM,YAAY,MAAM,OAAO;IACtD;EAAC;EAAU;EAAiB,MAAM;EAAQ,MAAM;EAAW,CAAC;AAG/D,QAAO,eACE;EACL;EACA;EACA;EACA,eAAe,MAAM;EACtB,GACD;EAAC;EAAgB;EAAW;EAAU,MAAM;EAAO,CACpD"}
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
//#region src/deckgl/base-map/constants.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Dark theme base map style URL from Carto.
|
|
4
|
+
* Provides a dark background for visualization layers.
|
|
5
|
+
*/
|
|
2
6
|
declare const DARK_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
|
|
7
|
+
/**
|
|
8
|
+
* Light theme base map style URL from Carto.
|
|
9
|
+
* Provides a light background for visualization layers.
|
|
10
|
+
*/
|
|
3
11
|
declare const LIGHT_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/voyager-gl-style/style.json";
|
|
4
12
|
/**
|
|
5
13
|
* Default picking radius in pixels
|
|
@@ -7,6 +15,10 @@ declare const LIGHT_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/voy
|
|
|
7
15
|
* Makes thin lines and small shapes easier to hover
|
|
8
16
|
*/
|
|
9
17
|
declare const PICKING_RADIUS = 5;
|
|
18
|
+
/**
|
|
19
|
+
* Default WebGL rendering parameters for Deck.gl.
|
|
20
|
+
* Configures depth testing, blending, and rendering behavior for optimal layer compositing.
|
|
21
|
+
*/
|
|
10
22
|
declare const PARAMETERS: {
|
|
11
23
|
depthWriteEnabled: boolean;
|
|
12
24
|
depthCompare: string;
|
|
@@ -12,7 +12,15 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
//#region src/deckgl/base-map/constants.ts
|
|
15
|
+
/**
|
|
16
|
+
* Dark theme base map style URL from Carto.
|
|
17
|
+
* Provides a dark background for visualization layers.
|
|
18
|
+
*/
|
|
15
19
|
const DARK_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json";
|
|
20
|
+
/**
|
|
21
|
+
* Light theme base map style URL from Carto.
|
|
22
|
+
* Provides a light background for visualization layers.
|
|
23
|
+
*/
|
|
16
24
|
const LIGHT_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/voyager-gl-style/style.json";
|
|
17
25
|
/**
|
|
18
26
|
* Default picking radius in pixels
|
|
@@ -20,6 +28,10 @@ const LIGHT_BASE_MAP_STYLE = "https://tiles.basemaps.cartocdn.com/gl/voyager-gl-
|
|
|
20
28
|
* Makes thin lines and small shapes easier to hover
|
|
21
29
|
*/
|
|
22
30
|
const PICKING_RADIUS = 5;
|
|
31
|
+
/**
|
|
32
|
+
* Default WebGL rendering parameters for Deck.gl.
|
|
33
|
+
* Configures depth testing, blending, and rendering behavior for optimal layer compositing.
|
|
34
|
+
*/
|
|
23
35
|
const PARAMETERS = {
|
|
24
36
|
depthWriteEnabled: true,
|
|
25
37
|
depthCompare: "always",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.js","names":[],"sources":["../../../src/deckgl/base-map/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\nexport const DARK_BASE_MAP_STYLE =\n 'https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';\n\nexport const LIGHT_BASE_MAP_STYLE =\n 'https://tiles.basemaps.cartocdn.com/gl/voyager-gl-style/style.json';\n\n/**\n * Default picking radius in pixels\n * Creates a 5-pixel detection radius around the pointer for pickable objects\n * Makes thin lines and small shapes easier to hover\n */\nexport const PICKING_RADIUS = 5;\n\nexport const PARAMETERS = {\n depthWriteEnabled: true,\n depthCompare: 'always',\n depthBias: 0,\n blend: true,\n depthTest: false,\n blendColorSrcFactor: 'src-alpha',\n blendColorDstFactor: 'one-minus-src-alpha',\n blendAlphaSrcFactor: 'one',\n blendAlphaDstFactor: 'one-minus-src-alpha',\n blendColorOperation: 'add',\n blendAlphaOperation: 'add',\n};\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"constants.js","names":[],"sources":["../../../src/deckgl/base-map/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/**\n * Dark theme base map style URL from Carto.\n * Provides a dark background for visualization layers.\n */\nexport const DARK_BASE_MAP_STYLE =\n 'https://tiles.basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json';\n\n/**\n * Light theme base map style URL from Carto.\n * Provides a light background for visualization layers.\n */\nexport const LIGHT_BASE_MAP_STYLE =\n 'https://tiles.basemaps.cartocdn.com/gl/voyager-gl-style/style.json';\n\n/**\n * Default picking radius in pixels\n * Creates a 5-pixel detection radius around the pointer for pickable objects\n * Makes thin lines and small shapes easier to hover\n */\nexport const PICKING_RADIUS = 5;\n\n/**\n * Default WebGL rendering parameters for Deck.gl.\n * Configures depth testing, blending, and rendering behavior for optimal layer compositing.\n */\nexport const PARAMETERS = {\n depthWriteEnabled: true,\n depthCompare: 'always',\n depthBias: 0,\n blend: true,\n depthTest: false,\n blendColorSrcFactor: 'src-alpha',\n blendColorDstFactor: 'one-minus-src-alpha',\n blendAlphaSrcFactor: 'one',\n blendAlphaDstFactor: 'one-minus-src-alpha',\n blendColorOperation: 'add',\n blendAlphaOperation: 'add',\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAgBA,MAAa,sBACX;;;;;AAMF,MAAa,uBACX;;;;;;AAOF,MAAa,iBAAiB;;;;;AAM9B,MAAa,aAAa;CACxB,mBAAmB;CACnB,cAAc;CACd,WAAW;CACX,OAAO;CACP,WAAW;CACX,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACrB,qBAAqB;CACtB"}
|
|
@@ -15,8 +15,13 @@ import { UniqueId } from "@accelint/core";
|
|
|
15
15
|
import { MapRef } from "react-map-gl/maplibre";
|
|
16
16
|
|
|
17
17
|
//#region src/deckgl/base-map/controls.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Props for the MapControls component.
|
|
20
|
+
*/
|
|
18
21
|
type MapControlsProps = {
|
|
22
|
+
/** Unique identifier for the map instance */
|
|
19
23
|
id: UniqueId;
|
|
24
|
+
/** Reference to the MapLibre map instance */
|
|
20
25
|
mapRef: RefObject<MapRef | null>;
|
|
21
26
|
};
|
|
22
27
|
/**
|
|
@@ -24,6 +29,11 @@ type MapControlsProps = {
|
|
|
24
29
|
*
|
|
25
30
|
* This component is rendered inside BaseMap to wire up event listeners
|
|
26
31
|
* for pan and zoom control events.
|
|
32
|
+
*
|
|
33
|
+
* @param props - Component props
|
|
34
|
+
* @param props.id - Unique identifier for the map instance
|
|
35
|
+
* @param props.mapRef - Reference to the MapLibre map instance
|
|
36
|
+
* @returns null (headless component)
|
|
27
37
|
*/
|
|
28
38
|
declare function MapControls({
|
|
29
39
|
id,
|
|
@@ -22,6 +22,11 @@ import { useOn } from "@accelint/bus/react";
|
|
|
22
22
|
*
|
|
23
23
|
* This component is rendered inside BaseMap to wire up event listeners
|
|
24
24
|
* for pan and zoom control events.
|
|
25
|
+
*
|
|
26
|
+
* @param props - Component props
|
|
27
|
+
* @param props.id - Unique identifier for the map instance
|
|
28
|
+
* @param props.mapRef - Reference to the MapLibre map instance
|
|
29
|
+
* @returns null (headless component)
|
|
25
30
|
*/
|
|
26
31
|
function MapControls({ id, mapRef }) {
|
|
27
32
|
useOn(MapEvents.enablePan, (event) => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"controls.js","names":[],"sources":["../../../src/deckgl/base-map/controls.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { useOn } from '@accelint/bus/react';\nimport { MapEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { RefObject } from 'react';\nimport type { MapRef } from 'react-map-gl/maplibre';\nimport type {\n MapDisablePanEvent,\n MapDisableZoomEvent,\n MapEnablePanEvent,\n MapEnableZoomEvent,\n} from './types';\n\ntype MapControlsProps = {\n id: UniqueId;\n mapRef: RefObject<MapRef | null>;\n};\n\n/**\n * Headless component that listens for map control events and applies them to the MapLibre instance.\n *\n * This component is rendered inside BaseMap to wire up event listeners\n * for pan and zoom control events.\n */\nexport function MapControls({ id, mapRef }: MapControlsProps) {\n useOn<MapEnablePanEvent>(MapEvents.enablePan, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().dragPan.enable();\n }\n });\n\n useOn<MapDisablePanEvent>(MapEvents.disablePan, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().dragPan.disable();\n }\n });\n\n useOn<MapEnableZoomEvent>(MapEvents.enableZoom, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().scrollZoom.enable();\n mapRef.current?.getMap().doubleClickZoom.enable();\n mapRef.current?.getMap().boxZoom.enable();\n }\n });\n\n useOn<MapDisableZoomEvent>(MapEvents.disableZoom, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().scrollZoom.disable();\n mapRef.current?.getMap().doubleClickZoom.disable();\n mapRef.current?.getMap().boxZoom.disable();\n }\n });\n\n return null;\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"controls.js","names":[],"sources":["../../../src/deckgl/base-map/controls.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { useOn } from '@accelint/bus/react';\nimport { MapEvents } from './events';\nimport type { UniqueId } from '@accelint/core';\nimport type { RefObject } from 'react';\nimport type { MapRef } from 'react-map-gl/maplibre';\nimport type {\n MapDisablePanEvent,\n MapDisableZoomEvent,\n MapEnablePanEvent,\n MapEnableZoomEvent,\n} from './types';\n\n/**\n * Props for the MapControls component.\n */\ntype MapControlsProps = {\n /** Unique identifier for the map instance */\n id: UniqueId;\n /** Reference to the MapLibre map instance */\n mapRef: RefObject<MapRef | null>;\n};\n\n/**\n * Headless component that listens for map control events and applies them to the MapLibre instance.\n *\n * This component is rendered inside BaseMap to wire up event listeners\n * for pan and zoom control events.\n *\n * @param props - Component props\n * @param props.id - Unique identifier for the map instance\n * @param props.mapRef - Reference to the MapLibre map instance\n * @returns null (headless component)\n */\nexport function MapControls({ id, mapRef }: MapControlsProps) {\n useOn<MapEnablePanEvent>(MapEvents.enablePan, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().dragPan.enable();\n }\n });\n\n useOn<MapDisablePanEvent>(MapEvents.disablePan, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().dragPan.disable();\n }\n });\n\n useOn<MapEnableZoomEvent>(MapEvents.enableZoom, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().scrollZoom.enable();\n mapRef.current?.getMap().doubleClickZoom.enable();\n mapRef.current?.getMap().boxZoom.enable();\n }\n });\n\n useOn<MapDisableZoomEvent>(MapEvents.disableZoom, (event) => {\n if (event.payload.id === id) {\n mapRef.current?.getMap().scrollZoom.disable();\n mapRef.current?.getMap().doubleClickZoom.disable();\n mapRef.current?.getMap().boxZoom.disable();\n }\n });\n\n return null;\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAgDA,SAAgB,YAAY,EAAE,IAAI,UAA4B;AAC5D,OAAyB,UAAU,YAAY,UAAU;AACvD,MAAI,MAAM,QAAQ,OAAO,GACvB,QAAO,SAAS,QAAQ,CAAC,QAAQ,QAAQ;GAE3C;AAEF,OAA0B,UAAU,aAAa,UAAU;AACzD,MAAI,MAAM,QAAQ,OAAO,GACvB,QAAO,SAAS,QAAQ,CAAC,QAAQ,SAAS;GAE5C;AAEF,OAA0B,UAAU,aAAa,UAAU;AACzD,MAAI,MAAM,QAAQ,OAAO,IAAI;AAC3B,UAAO,SAAS,QAAQ,CAAC,WAAW,QAAQ;AAC5C,UAAO,SAAS,QAAQ,CAAC,gBAAgB,QAAQ;AACjD,UAAO,SAAS,QAAQ,CAAC,QAAQ,QAAQ;;GAE3C;AAEF,OAA2B,UAAU,cAAc,UAAU;AAC3D,MAAI,MAAM,QAAQ,OAAO,IAAI;AAC3B,UAAO,SAAS,QAAQ,CAAC,WAAW,SAAS;AAC7C,UAAO,SAAS,QAAQ,CAAC,gBAAgB,SAAS;AAClD,UAAO,SAAS,QAAQ,CAAC,QAAQ,SAAS;;GAE5C;AAEF,QAAO"}
|
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
//#region src/deckgl/base-map/events.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Namespace for all map-related events.
|
|
4
|
+
*/
|
|
2
5
|
declare const MapEventsNamespace = "map";
|
|
6
|
+
/**
|
|
7
|
+
* Event keys for map interactions and control changes.
|
|
8
|
+
* These events are emitted through the event bus for communication between map components and consumers.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { Broadcast } from '@accelint/bus';
|
|
13
|
+
* import { MapEvents } from '@accelint/map-toolkit/deckgl/base-map';
|
|
14
|
+
* import type { MapEventType } from '@accelint/map-toolkit/deckgl/base-map';
|
|
15
|
+
*
|
|
16
|
+
* const bus = Broadcast.getInstance<MapEventType>();
|
|
17
|
+
*
|
|
18
|
+
* // Listen for click events
|
|
19
|
+
* bus.on(MapEvents.click, (event) => {
|
|
20
|
+
* console.log('Map clicked:', event.payload.info);
|
|
21
|
+
* });
|
|
22
|
+
*
|
|
23
|
+
* // Listen for viewport changes
|
|
24
|
+
* bus.on(MapEvents.viewport, (event) => {
|
|
25
|
+
* console.log('Viewport changed:', event.payload.bounds);
|
|
26
|
+
* });
|
|
27
|
+
*
|
|
28
|
+
* // Emit control events
|
|
29
|
+
* bus.emit(MapEvents.disablePan, { id: mapId });
|
|
30
|
+
* bus.emit(MapEvents.enableZoom, { id: mapId });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
3
33
|
declare const MapEvents: {
|
|
4
34
|
readonly click: "map:click";
|
|
5
35
|
readonly hover: "map:hover";
|
|
@@ -12,7 +12,37 @@
|
|
|
12
12
|
|
|
13
13
|
|
|
14
14
|
//#region src/deckgl/base-map/events.ts
|
|
15
|
+
/**
|
|
16
|
+
* Namespace for all map-related events.
|
|
17
|
+
*/
|
|
15
18
|
const MapEventsNamespace = "map";
|
|
19
|
+
/**
|
|
20
|
+
* Event keys for map interactions and control changes.
|
|
21
|
+
* These events are emitted through the event bus for communication between map components and consumers.
|
|
22
|
+
*
|
|
23
|
+
* @example
|
|
24
|
+
* ```typescript
|
|
25
|
+
* import { Broadcast } from '@accelint/bus';
|
|
26
|
+
* import { MapEvents } from '@accelint/map-toolkit/deckgl/base-map';
|
|
27
|
+
* import type { MapEventType } from '@accelint/map-toolkit/deckgl/base-map';
|
|
28
|
+
*
|
|
29
|
+
* const bus = Broadcast.getInstance<MapEventType>();
|
|
30
|
+
*
|
|
31
|
+
* // Listen for click events
|
|
32
|
+
* bus.on(MapEvents.click, (event) => {
|
|
33
|
+
* console.log('Map clicked:', event.payload.info);
|
|
34
|
+
* });
|
|
35
|
+
*
|
|
36
|
+
* // Listen for viewport changes
|
|
37
|
+
* bus.on(MapEvents.viewport, (event) => {
|
|
38
|
+
* console.log('Viewport changed:', event.payload.bounds);
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Emit control events
|
|
42
|
+
* bus.emit(MapEvents.disablePan, { id: mapId });
|
|
43
|
+
* bus.emit(MapEvents.enableZoom, { id: mapId });
|
|
44
|
+
* ```
|
|
45
|
+
*/
|
|
16
46
|
const MapEvents = {
|
|
17
47
|
click: `${MapEventsNamespace}:click`,
|
|
18
48
|
hover: `${MapEventsNamespace}:hover`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"events.js","names":[],"sources":["../../../src/deckgl/base-map/events.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\nexport const MapEventsNamespace = 'map';\n\nexport const MapEvents = {\n click: `${MapEventsNamespace}:click`,\n hover: `${MapEventsNamespace}:hover`,\n viewport: `${MapEventsNamespace}:viewport`,\n // Control events\n enablePan: `${MapEventsNamespace}:enablePan`,\n disablePan: `${MapEventsNamespace}:disablePan`,\n enableZoom: `${MapEventsNamespace}:enableZoom`,\n disableZoom: `${MapEventsNamespace}:disableZoom`,\n} as const;\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"events.js","names":[],"sources":["../../../src/deckgl/base-map/events.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 * Namespace for all map-related events.\n */\nexport const MapEventsNamespace = 'map';\n\n/**\n * Event keys for map interactions and control changes.\n * These events are emitted through the event bus for communication between map components and consumers.\n *\n * @example\n * ```typescript\n * import { Broadcast } from '@accelint/bus';\n * import { MapEvents } from '@accelint/map-toolkit/deckgl/base-map';\n * import type { MapEventType } from '@accelint/map-toolkit/deckgl/base-map';\n *\n * const bus = Broadcast.getInstance<MapEventType>();\n *\n * // Listen for click events\n * bus.on(MapEvents.click, (event) => {\n * console.log('Map clicked:', event.payload.info);\n * });\n *\n * // Listen for viewport changes\n * bus.on(MapEvents.viewport, (event) => {\n * console.log('Viewport changed:', event.payload.bounds);\n * });\n *\n * // Emit control events\n * bus.emit(MapEvents.disablePan, { id: mapId });\n * bus.emit(MapEvents.enableZoom, { id: mapId });\n * ```\n */\nexport const MapEvents = {\n click: `${MapEventsNamespace}:click`,\n hover: `${MapEventsNamespace}:hover`,\n viewport: `${MapEventsNamespace}:viewport`,\n // Control events\n enablePan: `${MapEventsNamespace}:enablePan`,\n disablePan: `${MapEventsNamespace}:disablePan`,\n enableZoom: `${MapEventsNamespace}:enableZoom`,\n disableZoom: `${MapEventsNamespace}:disableZoom`,\n} as const;\n"],"mappings":";;;;;;;;;;;;;;;;;AAeA,MAAa,qBAAqB;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA6BlC,MAAa,YAAY;CACvB,OAAO,GAAG,mBAAmB;CAC7B,OAAO,GAAG,mBAAmB;CAC7B,UAAU,GAAG,mBAAmB;CAEhC,WAAW,GAAG,mBAAmB;CACjC,YAAY,GAAG,mBAAmB;CAClC,YAAY,GAAG,mBAAmB;CAClC,aAAa,GAAG,mBAAmB;CACpC"}
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { BaseMapProps } from "./types.js";
|
|
14
|
-
import * as
|
|
14
|
+
import * as react_jsx_runtime3 from "react/jsx-runtime";
|
|
15
15
|
|
|
16
16
|
//#region src/deckgl/base-map/index.d.ts
|
|
17
17
|
|
|
@@ -106,7 +106,7 @@ declare function BaseMap({
|
|
|
106
106
|
onViewStateChange,
|
|
107
107
|
pickingRadius,
|
|
108
108
|
...rest
|
|
109
|
-
}: BaseMapProps):
|
|
109
|
+
}: BaseMapProps): react_jsx_runtime3.JSX.Element;
|
|
110
110
|
//#endregion
|
|
111
111
|
export { BaseMap };
|
|
112
112
|
//# sourceMappingURL=index.d.ts.map
|
|
@@ -30,6 +30,9 @@ import { Map, useControl } from "react-map-gl/maplibre";
|
|
|
30
30
|
/**
|
|
31
31
|
* Serializes PickingInfo for event bus transmission.
|
|
32
32
|
* Omits viewport, layer, and sourceLayer (contain functions) but preserves layer IDs.
|
|
33
|
+
*
|
|
34
|
+
* @param info - The PickingInfo object from Deck.gl
|
|
35
|
+
* @returns Serializable picking info with layer IDs extracted
|
|
33
36
|
*/
|
|
34
37
|
function serializePickingInfo(info) {
|
|
35
38
|
const { viewport, layer, sourceLayer, ...infoRest } = info;
|
|
@@ -47,6 +50,12 @@ function serializeMjolnirEvent(event) {
|
|
|
47
50
|
}
|
|
48
51
|
return rest;
|
|
49
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Internal component that registers the Deck.gl instance as a MapLibre control.
|
|
55
|
+
* Enables the Deck.gl canvas to render within the MapLibre GL map container.
|
|
56
|
+
*
|
|
57
|
+
* @returns null (headless component)
|
|
58
|
+
*/
|
|
50
59
|
function AddDeckglControl() {
|
|
51
60
|
const deckglInstance = useDeckgl();
|
|
52
61
|
useControl(() => deckglInstance);
|
|
@@ -163,6 +172,7 @@ function BaseMap({ id, className, children, controller = true, enableControlEven
|
|
|
163
172
|
const emitClick = useEmit(MapEvents.click);
|
|
164
173
|
const emitHover = useEmit(MapEvents.hover);
|
|
165
174
|
const emitViewport = useEmit(MapEvents.viewport);
|
|
175
|
+
const resizeTimeoutRef = useRef(null);
|
|
166
176
|
const handleClick = useCallback((info, event) => {
|
|
167
177
|
onClick?.(info, event);
|
|
168
178
|
emitClick({
|
|
@@ -194,6 +204,7 @@ function BaseMap({ id, className, children, controller = true, enableControlEven
|
|
|
194
204
|
onViewStateChange?.(params);
|
|
195
205
|
const { viewId, viewState: { latitude, longitude, zoom } } = params;
|
|
196
206
|
const viewport = deckglInstance._deck.getViewports()?.find((vp) => vp.id === viewId);
|
|
207
|
+
if (!viewport) return;
|
|
197
208
|
emitViewport({
|
|
198
209
|
id,
|
|
199
210
|
bounds: viewport?.getBounds(),
|
|
@@ -204,6 +215,24 @@ function BaseMap({ id, className, children, controller = true, enableControlEven
|
|
|
204
215
|
height: viewport?.height ?? 0
|
|
205
216
|
});
|
|
206
217
|
});
|
|
218
|
+
const handleResize = useEffectEvent((params) => {
|
|
219
|
+
if (resizeTimeoutRef.current) clearTimeout(resizeTimeoutRef.current);
|
|
220
|
+
resizeTimeoutRef.current = setTimeout(() => {
|
|
221
|
+
const viewports = deckglInstance._deck.getViewports() ?? [];
|
|
222
|
+
for (const vp of viewports) handleViewStateChange({
|
|
223
|
+
viewId: vp.id,
|
|
224
|
+
viewState: {
|
|
225
|
+
latitude: vp.latitude,
|
|
226
|
+
longitude: vp.longitude,
|
|
227
|
+
zoom: vp.zoom,
|
|
228
|
+
id: vp.id,
|
|
229
|
+
bounds: vp.getBounds(),
|
|
230
|
+
width: params.width,
|
|
231
|
+
height: params.height
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
}, 200);
|
|
235
|
+
});
|
|
207
236
|
const handleLoad = useEffectEvent(() => {
|
|
208
237
|
const viewports = deckglInstance._deck.getViewports() ?? [];
|
|
209
238
|
for (const vp of viewports) handleViewStateChange({
|
|
@@ -242,6 +271,7 @@ function BaseMap({ id, className, children, controller = true, enableControlEven
|
|
|
242
271
|
pickingRadius: pickingRadius ?? PICKING_RADIUS,
|
|
243
272
|
onHover: handleHover,
|
|
244
273
|
onLoad: handleLoad,
|
|
274
|
+
onResize: handleResize,
|
|
245
275
|
onViewStateChange: handleViewStateChange,
|
|
246
276
|
parameters: {
|
|
247
277
|
...PARAMETERS,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["MapLibre"],"sources":["../../../src/deckgl/base-map/index.tsx"],"sourcesContent":["/*\n * Copyright 2026 Hypergiant Galactic Systems Inc. All rights reserved.\n * This file is licensed to you under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License. You may obtain a copy\n * of the License at https://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software distributed under\n * the License is distributed on an \"AS IS\" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS\n * OF ANY KIND, either express or implied. See the License for the specific language\n * governing permissions and limitations under the License.\n */\n\n'use client';\n\nimport 'client-only';\nimport { useEffectEvent, useEmit } from '@accelint/bus/react';\nimport { Deckgl, useDeckgl } from '@deckgl-fiber-renderer/dom';\nimport { useCallback, useId, useMemo, useRef } from 'react';\nimport {\n Map as MapLibre,\n type MapRef,\n useControl,\n type ViewState,\n} from 'react-map-gl/maplibre';\nimport { useMapCamera } from '../../camera';\nimport { getCursor } from '../../map-cursor/store';\nimport { DEFAULT_VIEW_STATE } from '../../shared/constants';\nimport { DARK_BASE_MAP_STYLE, PARAMETERS, PICKING_RADIUS } from './constants';\nimport { MapControls } from './controls';\nimport { MapEvents } from './events';\nimport { MapProvider } from './provider';\nimport type { PickingInfo, ViewStateChangeParameters } from '@deck.gl/core';\nimport type { IControl } from 'maplibre-gl';\nimport type { MjolnirGestureEvent, MjolnirPointerEvent } from 'mjolnir.js';\nimport type {\n BaseMapProps,\n MapClickEvent,\n MapHoverEvent,\n MapViewportEvent,\n SerializablePickingInfo,\n} from './types';\n\n/**\n * Serializes PickingInfo for event bus transmission.\n * Omits viewport, layer, and sourceLayer (contain functions) but preserves layer IDs.\n */\nfunction serializePickingInfo(info: PickingInfo): SerializablePickingInfo {\n const { viewport, layer, sourceLayer, ...infoRest } = info;\n return {\n layerId: layer?.id,\n sourceLayerId: sourceLayer?.id,\n ...infoRest,\n };\n}\n\n/**\n * Strips non-serializable properties from MjolnirGestureEvent for event bus transmission.\n * Removes functions, DOM elements, and PointerEvent objects that cannot be cloned.\n */\nfunction serializeMjolnirEvent(\n event: MjolnirGestureEvent,\n): Omit<\n MjolnirGestureEvent,\n | 'stopPropagation'\n | 'preventDefault'\n | 'stopImmediatePropagation'\n | 'srcEvent'\n | 'rootElement'\n | 'target'\n | 'changedPointers'\n | 'pointers'\n>;\n/**\n * Strips non-serializable properties from MjolnirPointerEvent for event bus transmission.\n * Removes functions and DOM elements that cannot be cloned.\n */\nfunction serializeMjolnirEvent(\n event: MjolnirPointerEvent,\n): Omit<\n MjolnirPointerEvent,\n | 'stopPropagation'\n | 'preventDefault'\n | 'stopImmediatePropagation'\n | 'srcEvent'\n | 'rootElement'\n | 'target'\n>;\nfunction serializeMjolnirEvent(\n event: MjolnirGestureEvent | MjolnirPointerEvent,\n) {\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n ...rest\n } = event;\n\n // Remove pointer arrays if present (only on MjolnirGestureEvent)\n if ('changedPointers' in rest) {\n const { changedPointers, pointers, ...gestureRest } = rest;\n return gestureRest;\n }\n\n return rest;\n}\n\nfunction AddDeckglControl() {\n const deckglInstance = useDeckgl();\n useControl(() => deckglInstance as IControl);\n\n return null;\n}\n\n/**\n * A React component that provides a Deck.gl-powered base map with MapLibre GL integration.\n *\n * This component serves as the foundation for building interactive map applications with\n * support for click and hover events through a centralized event bus. It integrates\n * Deck.gl for 3D visualizations with MapLibre GL for the base map tiles.\n *\n * **Map Mode Integration**: BaseMap automatically creates a `MapProvider` internally,\n * which sets up the map mode state management for this instance.\n * - **Children**: Only Deck.gl layer components can be rendered as children. Custom Deck.gl\n * layers can use `useMapMode()` without parameters to access context.\n * - **Siblings**: UI components (buttons, toolbars, etc.) must be rendered as siblings\n * and pass `id` to `useMapMode(id)`.\n *\n * **Event Bus**: Click and hover events are emitted through the event bus with the `id`\n * included in the payload, allowing multiple map instances to coexist without interference.\n *\n * @param props - Component props including id (required), className, onClick, onHover, and all Deck.gl props\n * @returns A map component with Deck.gl and MapLibre GL integration\n *\n * @example\n * Basic usage with id (recommended: module-level constant):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { View } from '@deckgl-fiber-renderer/dom';\n * import { uuid } from '@accelint/core';\n *\n * // Create id at module level for stability and easy sharing\n * const MAIN_MAP_ID = uuid();\n *\n * export function MapView() {\n * return (\n * <BaseMap className=\"w-full h-full\" id={MAIN_MAP_ID}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example\n * With map mode and event handlers (module-level constant for sharing):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { useMapMode } from '@accelint/map-toolkit/map-mode';\n * import { uuid } from '@accelint/core';\n * import type { PickingInfo } from '@deck.gl/core';\n * import type { MjolnirGestureEvent } from 'mjolnir.js';\n *\n * // Module-level constant - stable and shareable across all components\n * const MAIN_MAP_ID = uuid();\n *\n * function Toolbar() {\n * // Access map mode using the shared id\n * const { mode, requestModeChange } = useMapMode(MAIN_MAP_ID);\n * return <div>Current mode: {mode}</div>;\n * }\n *\n * export function InteractiveMap() {\n * const handleClick = (info: PickingInfo, event: MjolnirGestureEvent) => {\n * console.log('Clicked:', info.object);\n * };\n *\n * return (\n * <div className=\"relative w-full h-full\">\n * <BaseMap className=\"absolute inset-0\" id={MAIN_MAP_ID} onClick={handleClick}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * <Toolbar />\n * </div>\n * );\n * }\n * ```\n */\nexport function BaseMap({\n id,\n className,\n children,\n controller = true,\n enableControlEvents = true,\n interleaved = true,\n parameters = {},\n styleUrl = DARK_BASE_MAP_STYLE,\n useDevicePixels = false,\n widgets: widgetsProp = [],\n defaultView = '2D',\n initialViewState,\n onClick,\n onHover,\n onViewStateChange,\n pickingRadius,\n ...rest\n}: BaseMapProps) {\n const deckglInstance = useDeckgl();\n const container = useId();\n const mapRef = useRef<MapRef>(null);\n\n const { cameraState, setCameraState } = useMapCamera(id, {\n view: defaultView,\n zoom: initialViewState?.zoom ?? DEFAULT_VIEW_STATE.zoom,\n latitude: initialViewState?.latitude ?? DEFAULT_VIEW_STATE.latitude,\n longitude: initialViewState?.longitude ?? DEFAULT_VIEW_STATE.longitude,\n });\n\n const viewState = useMemo<ViewState>(\n () => ({\n // @ts-expect-error squirrelly deckglInstance typing\n ...(deckglInstance?._deck?._getViewState() as ViewState),\n ...cameraState,\n bearing: cameraState.rotation,\n }),\n // @ts-expect-error squirrelly deckglInstance typing\n [cameraState, deckglInstance?._deck?._getViewState],\n );\n\n // Memoize MapLibre options to avoid creating new object on every render\n const mapOptions = useMemo(\n () => ({\n container,\n zoom: viewState.zoom,\n pitch: viewState.pitch,\n bearing: viewState.bearing,\n latitude: viewState.latitude,\n longitude: viewState.longitude,\n doubleClickZoom: false,\n dragRotate: false,\n pitchWithRotate: false,\n rollEnabled: false,\n attributionControl: { compact: true },\n projection: cameraState.projection,\n maxPitch: cameraState.view === '2D' ? 0 : 85,\n }),\n [viewState, container, cameraState.projection, cameraState.view],\n );\n\n const emitClick = useEmit<MapClickEvent>(MapEvents.click);\n const emitHover = useEmit<MapHoverEvent>(MapEvents.hover);\n const emitViewport = useEmit<MapViewportEvent>(MapEvents.viewport);\n\n const handleClick = useCallback(\n (info: PickingInfo, event: MjolnirGestureEvent) => {\n // send full pickingInfo and event to user-defined onClick\n onClick?.(info, event);\n\n emitClick({\n info: serializePickingInfo(info),\n event: serializeMjolnirEvent(event),\n id,\n });\n },\n [emitClick, id, onClick],\n );\n\n const handleHover = useCallback(\n (info: PickingInfo, event: MjolnirPointerEvent) => {\n // send full pickingInfo and event to user-defined onHover\n onHover?.(info, event);\n\n emitHover({\n info: serializePickingInfo(info),\n event: serializeMjolnirEvent(event),\n id,\n });\n },\n [emitHover, id, onHover],\n );\n\n const handleGetCursor = useCallback(() => {\n return getCursor(id);\n }, [id]);\n\n const handleViewStateChange = useEffectEvent(\n (params: ViewStateChangeParameters) => {\n onViewStateChange?.(params);\n\n const {\n viewId,\n viewState: { latitude, longitude, zoom },\n } = params;\n\n // @ts-expect-error squirrelly deckglInstance typing\n const viewport = deckglInstance._deck\n .getViewports()\n // @ts-expect-error squirrelly deckglInstance typing\n ?.find((vp) => vp.id === viewId);\n\n emitViewport({\n id,\n bounds: viewport?.getBounds(),\n latitude,\n longitude,\n zoom,\n width: viewport?.width ?? 0,\n height: viewport?.height ?? 0,\n });\n },\n );\n\n const handleLoad = useEffectEvent(() => {\n //--- force update viewport state once all viewports initialized ---\n // @ts-expect-error squirrelly deckglInstance typing\n const viewports = deckglInstance._deck.getViewports() ?? [];\n for (const vp of viewports) {\n handleViewStateChange({\n viewId: vp.id,\n viewState: {\n latitude: vp.latitude,\n longitude: vp.longitude,\n zoom: vp.zoom,\n id: vp.id,\n bounds: vp.getBounds(),\n width: vp.width,\n height: vp.height,\n },\n } as ViewStateChangeParameters);\n }\n });\n\n return (\n <div id={container} className={className}>\n {enableControlEvents && <MapControls id={id} mapRef={mapRef} />}\n <MapProvider id={id}>\n <MapLibre\n onMove={(evt) => setCameraState(evt.viewState)}\n mapStyle={styleUrl}\n ref={mapRef}\n {...mapOptions}\n >\n <Deckgl\n {...rest}\n controller={controller}\n interleaved={interleaved}\n getCursor={handleGetCursor}\n useDevicePixels={useDevicePixels}\n onClick={handleClick}\n pickingRadius={pickingRadius ?? PICKING_RADIUS}\n onHover={handleHover}\n onLoad={handleLoad}\n onViewStateChange={handleViewStateChange}\n // @ts-expect-error - DeckglProps parameters type is overly strict for WebGL parameter spreading.\n // The merged object is valid at runtime but TypeScript cannot verify all possible parameter combinations.\n parameters={{ ...PARAMETERS, ...parameters }}\n >\n <AddDeckglControl />\n {children}\n </Deckgl>\n </MapLibre>\n </MapProvider>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["MapLibre"],"sources":["../../../src/deckgl/base-map/index.tsx"],"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 { useEffectEvent, useEmit } from '@accelint/bus/react';\nimport type { PickingInfo, ViewStateChangeParameters } from '@deck.gl/core';\nimport { Deckgl, useDeckgl } from '@deckgl-fiber-renderer/dom';\nimport 'client-only';\nimport type { IControl } from 'maplibre-gl';\nimport type { MjolnirGestureEvent, MjolnirPointerEvent } from 'mjolnir.js';\nimport { useCallback, useId, useMemo, useRef } from 'react';\nimport {\n Map as MapLibre,\n type MapRef,\n useControl,\n type ViewState,\n} from 'react-map-gl/maplibre';\nimport { useMapCamera } from '../../camera';\nimport { getCursor } from '../../map-cursor/store';\nimport { DEFAULT_VIEW_STATE } from '../../shared/constants';\nimport { DARK_BASE_MAP_STYLE, PARAMETERS, PICKING_RADIUS } from './constants';\nimport { MapControls } from './controls';\nimport { MapEvents } from './events';\nimport { MapProvider } from './provider';\nimport type {\n BaseMapProps,\n MapClickEvent,\n MapHoverEvent,\n MapViewportEvent,\n SerializablePickingInfo,\n} from './types';\n\n/**\n * Serializes PickingInfo for event bus transmission.\n * Omits viewport, layer, and sourceLayer (contain functions) but preserves layer IDs.\n *\n * @param info - The PickingInfo object from Deck.gl\n * @returns Serializable picking info with layer IDs extracted\n */\nfunction serializePickingInfo(info: PickingInfo): SerializablePickingInfo {\n const { viewport, layer, sourceLayer, ...infoRest } = info;\n return {\n layerId: layer?.id,\n sourceLayerId: sourceLayer?.id,\n ...infoRest,\n };\n}\n\n/**\n * Strips non-serializable properties from MjolnirGestureEvent for event bus transmission.\n * Removes functions, DOM elements, and PointerEvent objects that cannot be cloned.\n *\n * @param event - The MjolnirGestureEvent from Deck.gl\n * @returns Serializable gesture event with non-cloneable properties removed\n */\nfunction serializeMjolnirEvent(\n event: MjolnirGestureEvent,\n): Omit<\n MjolnirGestureEvent,\n | 'stopPropagation'\n | 'preventDefault'\n | 'stopImmediatePropagation'\n | 'srcEvent'\n | 'rootElement'\n | 'target'\n | 'changedPointers'\n | 'pointers'\n>;\n/**\n * Strips non-serializable properties from MjolnirPointerEvent for event bus transmission.\n * Removes functions and DOM elements that cannot be cloned.\n *\n * @param event - The MjolnirPointerEvent from Deck.gl\n * @returns Serializable pointer event with non-cloneable properties removed\n */\nfunction serializeMjolnirEvent(\n event: MjolnirPointerEvent,\n): Omit<\n MjolnirPointerEvent,\n | 'stopPropagation'\n | 'preventDefault'\n | 'stopImmediatePropagation'\n | 'srcEvent'\n | 'rootElement'\n | 'target'\n>;\nfunction serializeMjolnirEvent(\n event: MjolnirGestureEvent | MjolnirPointerEvent,\n) {\n const {\n stopImmediatePropagation,\n stopPropagation,\n preventDefault,\n srcEvent,\n rootElement,\n target,\n ...rest\n } = event;\n\n // Remove pointer arrays if present (only on MjolnirGestureEvent)\n if ('changedPointers' in rest) {\n const { changedPointers, pointers, ...gestureRest } = rest;\n return gestureRest;\n }\n\n return rest;\n}\n\n/**\n * Internal component that registers the Deck.gl instance as a MapLibre control.\n * Enables the Deck.gl canvas to render within the MapLibre GL map container.\n *\n * @returns null (headless component)\n */\nfunction AddDeckglControl() {\n const deckglInstance = useDeckgl();\n useControl(() => deckglInstance as IControl);\n\n return null;\n}\n\n/**\n * A React component that provides a Deck.gl-powered base map with MapLibre GL integration.\n *\n * This component serves as the foundation for building interactive map applications with\n * support for click and hover events through a centralized event bus. It integrates\n * Deck.gl for 3D visualizations with MapLibre GL for the base map tiles.\n *\n * **Map Mode Integration**: BaseMap automatically creates a `MapProvider` internally,\n * which sets up the map mode state management for this instance.\n * - **Children**: Only Deck.gl layer components can be rendered as children. Custom Deck.gl\n * layers can use `useMapMode()` without parameters to access context.\n * - **Siblings**: UI components (buttons, toolbars, etc.) must be rendered as siblings\n * and pass `id` to `useMapMode(id)`.\n *\n * **Event Bus**: Click and hover events are emitted through the event bus with the `id`\n * included in the payload, allowing multiple map instances to coexist without interference.\n *\n * @param props - Component props including id (required), className, onClick, onHover, and all Deck.gl props\n * @returns A map component with Deck.gl and MapLibre GL integration\n *\n * @example\n * Basic usage with id (recommended: module-level constant):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { View } from '@deckgl-fiber-renderer/dom';\n * import { uuid } from '@accelint/core';\n *\n * // Create id at module level for stability and easy sharing\n * const MAIN_MAP_ID = uuid();\n *\n * export function MapView() {\n * return (\n * <BaseMap className=\"w-full h-full\" id={MAIN_MAP_ID}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * );\n * }\n * ```\n *\n * @example\n * With map mode and event handlers (module-level constant for sharing):\n * ```tsx\n * import { BaseMap } from '@accelint/map-toolkit/deckgl';\n * import { useMapMode } from '@accelint/map-toolkit/map-mode';\n * import { uuid } from '@accelint/core';\n * import type { PickingInfo } from '@deck.gl/core';\n * import type { MjolnirGestureEvent } from 'mjolnir.js';\n *\n * // Module-level constant - stable and shareable across all components\n * const MAIN_MAP_ID = uuid();\n *\n * function Toolbar() {\n * // Access map mode using the shared id\n * const { mode, requestModeChange } = useMapMode(MAIN_MAP_ID);\n * return <div>Current mode: {mode}</div>;\n * }\n *\n * export function InteractiveMap() {\n * const handleClick = (info: PickingInfo, event: MjolnirGestureEvent) => {\n * console.log('Clicked:', info.object);\n * };\n *\n * return (\n * <div className=\"relative w-full h-full\">\n * <BaseMap className=\"absolute inset-0\" id={MAIN_MAP_ID} onClick={handleClick}>\n * <View id=\"main\" controller />\n * </BaseMap>\n * <Toolbar />\n * </div>\n * );\n * }\n * ```\n */\nexport function BaseMap({\n id,\n className,\n children,\n controller = true,\n enableControlEvents = true,\n interleaved = true,\n parameters = {},\n styleUrl = DARK_BASE_MAP_STYLE,\n useDevicePixels = false,\n widgets: widgetsProp = [],\n defaultView = '2D',\n initialViewState,\n onClick,\n onHover,\n onViewStateChange,\n pickingRadius,\n ...rest\n}: BaseMapProps) {\n const deckglInstance = useDeckgl();\n const container = useId();\n const mapRef = useRef<MapRef>(null);\n\n const { cameraState, setCameraState } = useMapCamera(id, {\n view: defaultView,\n zoom: initialViewState?.zoom ?? DEFAULT_VIEW_STATE.zoom,\n latitude: initialViewState?.latitude ?? DEFAULT_VIEW_STATE.latitude,\n longitude: initialViewState?.longitude ?? DEFAULT_VIEW_STATE.longitude,\n });\n\n const viewState = useMemo<ViewState>(\n () => ({\n // @ts-expect-error squirrelly deckglInstance typing\n ...(deckglInstance?._deck?._getViewState() as ViewState),\n ...cameraState,\n bearing: cameraState.rotation,\n }),\n // @ts-expect-error squirrelly deckglInstance typing\n [cameraState, deckglInstance?._deck?._getViewState],\n );\n\n // Memoize MapLibre options to avoid creating new object on every render\n const mapOptions = useMemo(\n () => ({\n container,\n zoom: viewState.zoom,\n pitch: viewState.pitch,\n bearing: viewState.bearing,\n latitude: viewState.latitude,\n longitude: viewState.longitude,\n doubleClickZoom: false,\n dragRotate: false,\n pitchWithRotate: false,\n rollEnabled: false,\n attributionControl: { compact: true },\n projection: cameraState.projection,\n maxPitch: cameraState.view === '2D' ? 0 : 85,\n }),\n [viewState, container, cameraState.projection, cameraState.view],\n );\n\n const emitClick = useEmit<MapClickEvent>(MapEvents.click);\n const emitHover = useEmit<MapHoverEvent>(MapEvents.hover);\n const emitViewport = useEmit<MapViewportEvent>(MapEvents.viewport);\n\n const resizeTimeoutRef = useRef<NodeJS.Timeout | null>(null);\n\n const handleClick = useCallback(\n (info: PickingInfo, event: MjolnirGestureEvent) => {\n // send full pickingInfo and event to user-defined onClick\n onClick?.(info, event);\n\n emitClick({\n info: serializePickingInfo(info),\n event: serializeMjolnirEvent(event),\n id,\n });\n },\n [emitClick, id, onClick],\n );\n\n const handleHover = useCallback(\n (info: PickingInfo, event: MjolnirPointerEvent) => {\n // send full pickingInfo and event to user-defined onHover\n onHover?.(info, event);\n\n emitHover({\n info: serializePickingInfo(info),\n event: serializeMjolnirEvent(event),\n id,\n });\n },\n [emitHover, id, onHover],\n );\n\n const handleGetCursor = useCallback(() => {\n return getCursor(id);\n }, [id]);\n\n const handleViewStateChange = useEffectEvent(\n (params: ViewStateChangeParameters) => {\n onViewStateChange?.(params);\n\n const {\n viewId,\n viewState: { latitude, longitude, zoom },\n } = params;\n\n // @ts-expect-error squirrelly deckglInstance typing\n const viewport = deckglInstance._deck\n .getViewports()\n // @ts-expect-error squirrelly deckglInstance typing\n ?.find((vp) => vp.id === viewId);\n\n if (!viewport) {\n return;\n }\n\n emitViewport({\n id,\n bounds: viewport?.getBounds(),\n latitude,\n longitude,\n zoom,\n width: viewport?.width ?? 0,\n height: viewport?.height ?? 0,\n });\n },\n );\n\n const handleResize = useEffectEvent((params) => {\n // Clear existing timeout\n if (resizeTimeoutRef.current) {\n clearTimeout(resizeTimeoutRef.current);\n }\n\n // Debounce\n resizeTimeoutRef.current = setTimeout(() => {\n // @ts-expect-error squirrelly deckglInstance typing\n const viewports = deckglInstance._deck.getViewports() ?? [];\n for (const vp of viewports) {\n handleViewStateChange({\n viewId: vp.id,\n viewState: {\n latitude: vp.latitude,\n longitude: vp.longitude,\n zoom: vp.zoom,\n id: vp.id,\n bounds: vp.getBounds(),\n width: params.width,\n height: params.height,\n },\n } as ViewStateChangeParameters);\n }\n }, 200);\n });\n\n const handleLoad = useEffectEvent(() => {\n //--- force update viewport state once all viewports initialized ---\n // @ts-expect-error squirrelly deckglInstance typing\n const viewports = deckglInstance._deck.getViewports() ?? [];\n for (const vp of viewports) {\n handleViewStateChange({\n viewId: vp.id,\n viewState: {\n latitude: vp.latitude,\n longitude: vp.longitude,\n zoom: vp.zoom,\n id: vp.id,\n bounds: vp.getBounds(),\n width: vp.width,\n height: vp.height,\n },\n } as ViewStateChangeParameters);\n }\n });\n\n return (\n <div id={container} className={className}>\n {enableControlEvents && <MapControls id={id} mapRef={mapRef} />}\n <MapProvider id={id}>\n <MapLibre\n onMove={(evt) => setCameraState(evt.viewState)}\n mapStyle={styleUrl}\n ref={mapRef}\n {...mapOptions}\n >\n <Deckgl\n {...rest}\n controller={controller}\n interleaved={interleaved}\n getCursor={handleGetCursor}\n useDevicePixels={useDevicePixels}\n onClick={handleClick}\n pickingRadius={pickingRadius ?? PICKING_RADIUS}\n onHover={handleHover}\n onLoad={handleLoad}\n onResize={handleResize}\n onViewStateChange={handleViewStateChange}\n // @ts-expect-error - DeckglProps parameters type is overly strict for WebGL parameter spreading.\n // The merged object is valid at runtime but TypeScript cannot verify all possible parameter combinations.\n parameters={{ ...PARAMETERS, ...parameters }}\n >\n <AddDeckglControl />\n {children}\n </Deckgl>\n </MapLibre>\n </MapProvider>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -15,17 +15,92 @@ import { MapViewState } from "@deck.gl/core";
|
|
|
15
15
|
import { RequireAllOrNone } from "type-fest";
|
|
16
16
|
|
|
17
17
|
//#region src/deckgl/saved-viewports/index.d.ts
|
|
18
|
+
/**
|
|
19
|
+
* Base options required for saved viewport functionality.
|
|
20
|
+
*/
|
|
18
21
|
type BaseOptions = {
|
|
22
|
+
/** Optional identifier to namespace saved viewports for multiple map instances */
|
|
19
23
|
uniqueIdentifier?: string;
|
|
24
|
+
/** Milliseconds to hold a key before saving (default: 1000) */
|
|
20
25
|
threshold?: number;
|
|
26
|
+
/** Function that returns the current map viewport state */
|
|
21
27
|
getCurrentViewport: () => MapViewState;
|
|
28
|
+
/** Function that receives a viewport state and applies it to the map */
|
|
22
29
|
setCurrentViewport: (viewport: MapViewState) => void;
|
|
23
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* Optional custom storage functions for persisting viewports.
|
|
33
|
+
* Both functions must be provided together or omitted entirely.
|
|
34
|
+
*/
|
|
24
35
|
type PersistOptions = RequireAllOrNone<{
|
|
36
|
+
/** Custom function to retrieve saved viewports from storage */
|
|
25
37
|
getSavedViewport: (id: KeyCombinationId, uniqueIdentifier?: string) => MapViewState;
|
|
38
|
+
/** Custom function to persist saved viewports to storage */
|
|
26
39
|
setSavedViewport: (id: KeyCombinationId, viewport: MapViewState, uniqueIdentifier?: string) => void;
|
|
27
40
|
}>;
|
|
41
|
+
/**
|
|
42
|
+
* Configuration options for creating a saved viewport hotkey system.
|
|
43
|
+
* Combines hotkey options with viewport management callbacks and optional custom storage.
|
|
44
|
+
*/
|
|
28
45
|
type SavedViewportOptions = Partial<HotkeyOptions> & BaseOptions & PersistOptions;
|
|
46
|
+
/**
|
|
47
|
+
* Creates a React hook that registers hotkeys for saving and restoring map viewports.
|
|
48
|
+
*
|
|
49
|
+
* By default, uses number keys 0-9 where:
|
|
50
|
+
* - **Hold** a key for the threshold duration (default 1s) to save the current viewport
|
|
51
|
+
* - **Tap** a key to restore the saved viewport
|
|
52
|
+
*
|
|
53
|
+
* Viewports are persisted to localStorage by default but can be customized with
|
|
54
|
+
* `getSavedViewport` and `setSavedViewport` options.
|
|
55
|
+
*
|
|
56
|
+
* @param options - Configuration for viewport saving behavior and storage
|
|
57
|
+
* @returns A React hook function that must be called within a component to activate hotkeys
|
|
58
|
+
*
|
|
59
|
+
* @example
|
|
60
|
+
* ```tsx
|
|
61
|
+
* import { Broadcast } from '@accelint/bus';
|
|
62
|
+
* import { useOn } from '@accelint/bus/react';
|
|
63
|
+
* import { uuid } from '@accelint/core';
|
|
64
|
+
* import { BaseMap } from '@accelint/map-toolkit/deckgl';
|
|
65
|
+
* import { MapEvents } from '@accelint/map-toolkit/deckgl/base-map';
|
|
66
|
+
* import { CameraEventTypes } from '@accelint/map-toolkit/camera';
|
|
67
|
+
* import { createSavedViewport } from '@accelint/map-toolkit/deckgl/saved-viewports';
|
|
68
|
+
* import { globalBind } from '@accelint/hotkey-manager';
|
|
69
|
+
* import type { MapViewState } from '@deck.gl/core';
|
|
70
|
+
* import type { CameraEvent } from '@accelint/map-toolkit/camera';
|
|
71
|
+
* import type { MapViewportEvent, MapViewportPayload } from '@accelint/map-toolkit/deckgl/base-map';
|
|
72
|
+
*
|
|
73
|
+
* globalBind();
|
|
74
|
+
*
|
|
75
|
+
* const MAP_ID = uuid();
|
|
76
|
+
* const cameraBus = Broadcast.getInstance<CameraEvent>();
|
|
77
|
+
* let currentViewport: MapViewportPayload;
|
|
78
|
+
*
|
|
79
|
+
* const useSavedViewportHotkey = createSavedViewport({
|
|
80
|
+
* threshold: 1000,
|
|
81
|
+
* getCurrentViewport: () => currentViewport,
|
|
82
|
+
* setCurrentViewport: (newState: MapViewState) => {
|
|
83
|
+
* currentViewport = { ...currentViewport, ...newState };
|
|
84
|
+
* cameraBus.emit(CameraEventTypes.setCenter, {
|
|
85
|
+
* id: MAP_ID,
|
|
86
|
+
* latitude: newState.latitude ?? currentViewport.latitude,
|
|
87
|
+
* longitude: newState.longitude ?? currentViewport.longitude,
|
|
88
|
+
* zoom: newState.zoom,
|
|
89
|
+
* });
|
|
90
|
+
* },
|
|
91
|
+
* });
|
|
92
|
+
*
|
|
93
|
+
* export function MapView() {
|
|
94
|
+
* useSavedViewportHotkey();
|
|
95
|
+
*
|
|
96
|
+
* useOn<MapViewportEvent>(MapEvents.viewport, (event) => {
|
|
97
|
+
* currentViewport = { ...currentViewport, ...event.payload };
|
|
98
|
+
* });
|
|
99
|
+
*
|
|
100
|
+
* return <BaseMap id={MAP_ID} className="w-full h-full" />;
|
|
101
|
+
* }
|
|
102
|
+
* ```
|
|
103
|
+
*/
|
|
29
104
|
declare const createSavedViewport: (options: SavedViewportOptions) => ReturnType<typeof registerHotkey>;
|
|
30
105
|
//#endregion
|
|
31
106
|
export { SavedViewportOptions, createSavedViewport };
|