@defra/interactive-map 0.0.15-alpha → 0.0.17-alpha
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/assets/css/docusaurus.css +104 -0
- package/assets/images/favicon.svg +1 -0
- package/assets/images/hero.png +0 -0
- package/assets/images/slot-map.svg +264 -0
- package/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/im-shell.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api/slots.md +90 -6
- package/docs/api.md +4 -4
- package/docs/architecture.md +3 -1
- package/docs/{demo.mdx → examples.mdx} +1 -1
- package/docs/getting-started.md +5 -4
- package/docs/index.mdx +42 -0
- package/docs/plugins/datasets.md +561 -0
- package/docs/plugins/interact.md +176 -55
- package/docs/plugins/map-styles.md +64 -7
- package/docs/plugins/search.md +207 -63
- package/docs/plugins.md +8 -16
- package/docusaurus.config.cjs +34 -34
- package/jest.setup.js +1 -1
- package/package.json +6 -5
- package/plugins/beta/datasets/dist/css/index.css +85 -15
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/DatasetsInit.jsx +24 -9
- package/plugins/beta/datasets/src/adapters/maplibre/index.js +18 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +113 -0
- package/plugins/beta/datasets/src/adapters/maplibre/layerIds.js +69 -0
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +338 -0
- package/plugins/beta/datasets/src/adapters/maplibre/patternRegistry.js +48 -0
- package/plugins/beta/datasets/src/api/addDataset.js +3 -9
- package/plugins/beta/datasets/src/api/getOpacity.js +17 -0
- package/plugins/beta/datasets/src/api/getStyle.js +13 -0
- package/plugins/beta/datasets/src/api/removeDataset.js +3 -45
- package/plugins/beta/datasets/src/api/setData.js +8 -0
- package/plugins/beta/datasets/src/api/setDatasetVisibility.js +37 -0
- package/plugins/beta/datasets/src/api/setFeatureVisibility.js +22 -0
- package/plugins/beta/datasets/src/api/setOpacity.js +29 -0
- package/plugins/beta/datasets/src/api/setStyle.js +22 -0
- package/plugins/beta/datasets/src/datasets.js +33 -59
- package/plugins/beta/datasets/src/defaults.js +43 -9
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +39 -30
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +2 -2
- package/plugins/beta/datasets/src/manifest.js +27 -19
- package/plugins/beta/datasets/src/panels/Key.jsx +129 -49
- package/plugins/beta/datasets/src/panels/Key.module.scss +48 -9
- package/plugins/beta/datasets/src/panels/Layers.jsx +131 -29
- package/plugins/beta/datasets/src/panels/Layers.module.scss +50 -8
- package/plugins/beta/datasets/src/reducer.js +128 -9
- package/plugins/beta/datasets/src/styles/patterns.js +157 -0
- package/plugins/beta/datasets/src/utils/bbox.js +8 -6
- package/plugins/beta/datasets/src/utils/filters.js +5 -2
- package/plugins/beta/datasets/src/utils/mergeSublayer.js +78 -0
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/DrawInit.jsx +16 -16
- package/plugins/beta/draw-es/src/api/addFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/deleteFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/editFeature.js +3 -3
- package/plugins/beta/draw-es/src/api/newPolygon.js +3 -3
- package/plugins/beta/draw-es/src/events.js +52 -20
- package/plugins/beta/draw-es/src/events.test.js +301 -0
- package/plugins/beta/draw-es/src/graphic.js +1 -1
- package/plugins/beta/draw-es/src/manifest.js +4 -4
- package/plugins/beta/draw-es/src/reducer.js +1 -1
- package/plugins/beta/draw-es/src/sketchViewModel.js +1 -1
- package/plugins/beta/draw-ml/dist/css/index.css +1 -1
- package/plugins/beta/draw-ml/dist/esm/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/umd/im-draw-ml-plugin.js +1 -1
- package/plugins/beta/draw-ml/src/DrawInit.jsx +49 -52
- package/plugins/beta/draw-ml/src/api/deleteFeature.js +1 -1
- package/plugins/beta/draw-ml/src/api/editFeature.js +8 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +0 -1
- package/plugins/beta/draw-ml/src/api/newPolygon.js +0 -1
- package/plugins/beta/draw-ml/src/api/split.js +4 -4
- package/plugins/beta/draw-ml/src/defaults.js +1 -1
- package/plugins/beta/draw-ml/src/draw.scss +0 -7
- package/plugins/beta/draw-ml/src/events.js +8 -6
- package/plugins/beta/draw-ml/src/manifest.js +29 -29
- package/plugins/beta/draw-ml/src/mapboxDraw.js +1 -1
- package/plugins/beta/draw-ml/src/mapboxSnap.js +17 -18
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +31 -31
- package/plugins/beta/draw-ml/src/modes/disabledMode.js +1 -1
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +11 -11
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +8 -8
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +7 -7
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +32 -24
- package/plugins/beta/draw-ml/src/reducer.js +1 -1
- package/plugins/beta/draw-ml/src/undoStack.js +4 -4
- package/plugins/beta/draw-ml/src/utils/snapHelpers.js +12 -12
- package/plugins/beta/draw-ml/src/utils/spatial.js +11 -11
- package/plugins/beta/frame/dist/esm/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/dist/umd/im-frame-plugin.js +1 -1
- package/plugins/beta/frame/src/Frame.jsx +9 -9
- package/plugins/beta/frame/src/FrameInit.jsx +4 -4
- package/plugins/beta/frame/src/api/addFrame.js +1 -1
- package/plugins/beta/frame/src/api/editFeature.js +1 -1
- package/plugins/beta/frame/src/config.js +1 -1
- package/plugins/beta/frame/src/manifest.js +3 -3
- package/plugins/beta/frame/src/reducer.js +1 -1
- package/plugins/beta/frame/src/utils.js +1 -1
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/src/MapStyles.jsx +18 -18
- package/plugins/beta/map-styles/src/manifest.js +1 -1
- package/plugins/beta/scale-bar/dist/css/index.css +1 -1
- package/plugins/beta/scale-bar/dist/esm/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/dist/umd/im-scale-bar-plugin.js +1 -1
- package/plugins/beta/scale-bar/src/ScaleBar.jsx +5 -5
- package/plugins/beta/scale-bar/src/index.test.js +3 -3
- package/plugins/beta/scale-bar/src/manifest.js +3 -3
- package/plugins/beta/scale-bar/src/scaleBar.scss +2 -1
- package/plugins/beta/use-location/src/UseLocation.jsx +1 -1
- package/plugins/beta/use-location/src/defaults.js +1 -1
- package/plugins/beta/use-location/src/events.js +3 -3
- package/plugins/interact/dist/css/index.css +1 -1
- package/plugins/interact/dist/esm/im-interact-plugin.js +1 -1
- package/plugins/interact/dist/umd/im-interact-plugin.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +1 -2
- package/plugins/interact/src/api/enable.js +8 -5
- package/plugins/interact/src/api/enable.test.js +2 -2
- package/plugins/interact/src/api/selectFeature.js +4 -4
- package/plugins/interact/src/api/unselectFeature.js +5 -5
- package/plugins/interact/src/defaults.js +0 -1
- package/plugins/interact/src/events.test.js +15 -15
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +2 -2
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +5 -5
- package/plugins/interact/src/interact.scss +0 -7
- package/plugins/interact/src/manifest.js +15 -19
- package/plugins/interact/src/manifest.test.js +6 -5
- package/plugins/interact/src/reducer.js +3 -3
- package/plugins/interact/src/reducer.test.js +0 -1
- package/plugins/interact/src/utils/spatial.js +10 -10
- package/plugins/interact/src/utils/spatial.test.js +14 -14
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +7 -6
- package/plugins/search/src/Search.test.jsx +23 -23
- package/plugins/search/src/components/CloseButton/CloseButton.jsx +15 -15
- package/plugins/search/src/components/CloseButton/CloseButton.test.jsx +2 -2
- package/plugins/search/src/components/Form/Form.jsx +14 -14
- package/plugins/search/src/components/Form/Form.module.scss +2 -1
- package/plugins/search/src/components/Form/Form.test.jsx +11 -11
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +16 -15
- package/plugins/search/src/components/OpenButton/OpenButton.test.jsx +6 -2
- package/plugins/search/src/components/SubmitButton/SubmitButton.jsx +15 -15
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +6 -6
- package/plugins/search/src/components/Suggestions/Suggestions.test.jsx +4 -4
- package/plugins/search/src/datasets.js +12 -13
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +1 -1
- package/plugins/search/src/events/fetchSuggestions.js +3 -3
- package/plugins/search/src/events/fetchSuggestions.test.js +1 -1
- package/plugins/search/src/events/formHandlers.js +3 -3
- package/plugins/search/src/events/formHandlers.test.js +1 -1
- package/plugins/search/src/events/index.js +2 -2
- package/plugins/search/src/events/index.test.js +2 -2
- package/plugins/search/src/events/inputHandlers.js +4 -4
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -2
- package/plugins/search/src/events/suggestionHandlers.test.js +1 -1
- package/plugins/search/src/index.js +2 -1
- package/plugins/search/src/index.test.js +3 -3
- package/plugins/search/src/manifest.js +6 -4
- package/plugins/search/src/reducer.js +1 -2
- package/plugins/search/src/reducer.test.js +2 -2
- package/plugins/search/src/search.scss +10 -3
- package/plugins/search/src/utils/parseOsNamesResults.js +1 -2
- package/plugins/search/src/utils/parseOsNamesResults.test.js +2 -2
- package/plugins/search/src/utils/updateMap.js +1 -1
- package/plugins/search/src/utils/updateMap.test.js +5 -5
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +5 -5
- package/providers/beta/esri/src/utils/coords.js +1 -1
- package/providers/beta/esri/src/utils/esriFixes.js +1 -1
- package/providers/beta/esri/src/utils/query.js +4 -4
- package/providers/beta/esri/src/utils/spatial.js +1 -2
- package/providers/beta/esri/src/utils/spatial.test.js +4 -1
- package/providers/beta/open-names/src/utils/mapToLocationModel.test.js +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js.LICENSE.txt +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/src/appEvents.test.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +3 -5
- package/providers/maplibre/src/mapEvents.test.js +15 -5
- package/providers/maplibre/src/maplibreProvider.test.js +6 -2
- package/providers/maplibre/src/utils/calculateLinearTextSize.js +4 -4
- package/providers/maplibre/src/utils/calculateLinearTextSize.test.js +3 -3
- package/providers/maplibre/src/utils/detectWebgl.test.js +1 -1
- package/providers/maplibre/src/utils/highlightFeatures.js +3 -2
- package/providers/maplibre/src/utils/highlightFeatures.test.js +13 -6
- package/providers/maplibre/src/utils/labels.js +19 -20
- package/providers/maplibre/src/utils/labels.test.js +15 -13
- package/providers/maplibre/src/utils/maplibreFixes.test.js +1 -1
- package/providers/maplibre/src/utils/queryFeatures.js +6 -6
- package/providers/maplibre/src/utils/queryFeatures.test.js +13 -13
- package/providers/maplibre/src/utils/spatial.js +0 -1
- package/providers/maplibre/src/utils/spatial.test.js +26 -27
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +0 -7
- package/src/App/components/Actions/Actions.test.jsx +1 -1
- package/src/App/components/Icon/Icon.jsx +3 -2
- package/src/App/components/Icon/Icon.module.scss +4 -0
- package/src/App/components/Icon/Icon.test.jsx +43 -4
- package/src/App/components/MapButton/MapButton.jsx +42 -17
- package/src/App/components/MapButton/MapButton.module.scss +4 -13
- package/src/App/components/MapButton/MapButton.test.jsx +27 -3
- package/src/App/components/PopupMenu/PopupMenu.jsx +51 -274
- package/src/App/components/PopupMenu/PopupMenu.module.scss +14 -7
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +70 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +258 -0
- package/src/App/hooks/useButtonStateEvaluator.js +12 -2
- package/src/App/hooks/useButtonStateEvaluator.test.js +38 -4
- package/src/App/hooks/useInterfaceAPI.js +6 -0
- package/src/App/hooks/useLayoutMeasurements.js +84 -18
- package/src/App/hooks/useLayoutMeasurements.test.js +124 -17
- package/src/App/layout/Layout.jsx +12 -7
- package/src/App/layout/Layout.test.jsx +2 -2
- package/src/App/layout/layout.module.scss +67 -29
- package/src/App/registry/pluginRegistry.js +17 -0
- package/src/App/registry/pluginRegistry.test.js +33 -0
- package/src/App/renderer/HtmlElementHost.jsx +2 -1
- package/src/App/renderer/HtmlElementHost.test.jsx +7 -7
- package/src/App/renderer/mapButtons.js +3 -2
- package/src/App/renderer/mapPanels.test.js +2 -2
- package/src/App/renderer/slotHelpers.js +2 -2
- package/src/App/renderer/slotHelpers.test.js +5 -5
- package/src/App/renderer/slots.js +9 -5
- package/src/App/store/AppProvider.jsx +3 -1
- package/src/App/store/AppProvider.test.jsx +1 -1
- package/src/App/store/ServiceProvider.jsx +3 -1
- package/src/App/store/appActionsMap.js +16 -0
- package/src/App/store/appActionsMap.test.js +27 -0
- package/src/App/store/appDispatchMiddleware.js +33 -1
- package/src/App/store/appDispatchMiddleware.test.js +250 -222
- package/src/App/store/appReducer.js +2 -0
- package/src/InteractiveMap/InteractiveMap.js +4 -0
- package/src/config/appConfig.js +7 -4
- package/src/config/events.js +28 -0
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_dimensions.scss +0 -1
- package/src/services/logger.js +6 -0
- package/src/services/logger.test.js +32 -0
- package/src/utils/getSafeZoneInset.js +9 -7
- package/src/utils/getSafeZoneInset.test.js +10 -10
- package/webpack.dev.mjs +23 -19
- package/docs/govuk-prototype.md +0 -23
- package/docs/index.md +0 -19
- package/plugins/beta/datasets/src/api/hideDataset.js +0 -14
- package/plugins/beta/datasets/src/api/hideFeatures.js +0 -41
- package/plugins/beta/datasets/src/api/showDataset.js +0 -14
- package/plugins/beta/datasets/src/api/showFeatures.js +0 -44
- package/plugins/beta/datasets/src/handleSetMapStyle.js +0 -54
- package/plugins/beta/datasets/src/mapLayers.js +0 -165
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { getValueForStyle } from '../../../../../src/utils/getValueForStyle.js'
|
|
2
|
+
|
|
3
|
+
// ─── Built-in pattern library ────────────────────────────────────────────────
|
|
4
|
+
// Each value is the inner SVG content (paths only, no wrapper).
|
|
5
|
+
// Paths are authored in a 16×16 coordinate space (power-of-two, tiles seamlessly).
|
|
6
|
+
// Use {{foreground}} and {{background}} tokens for colours.
|
|
7
|
+
|
|
8
|
+
const BUILT_IN_PATTERNS = {
|
|
9
|
+
'cross-hatch': '<path d="M0 4.486V3.485h3.5V.001h1v3.484h7.002V.001h1v3.484h3.5v1.001h-3.5v7h3.5v.999h-3.5v3.516h-1v-3.516H4.499v3.516h-1v-3.516H0v-.999h3.5v-7H0zm11.501 0H4.499v7h7.002v-7z" fill="{{foreground}}"/>',
|
|
10
|
+
'diagonal-cross-hatch': '<path d="M0 8.707V7.293L7.293 0h1.414L16 7.293v1.414L8.707 16H7.293L0 8.707zM.707 8L8 15.293 15.293 8 8 .707.707 8z" fill="{{foreground}}"/>',
|
|
11
|
+
'forward-diagonal-hatch': '<path d="M16 8.707V7.293L7.293 16h1.414L16 8.707zm-16 0L8.707 0H7.293L0 7.293v1.414z" fill="{{foreground}}"/>',
|
|
12
|
+
'backward-diagonal-hatch': '<path d="M0 8.707V7.293L8.707 16H7.293L0 8.707zm16 0L7.293 0h1.414L16 7.293v1.414z" fill="{{foreground}}"/>',
|
|
13
|
+
'horizontal-hatch': '<path d="M0 4.5V3.499h15.999V4.5H0zm0 7h15.999V12.5H0v-1.001z" fill="{{foreground}}"/>',
|
|
14
|
+
'vertical-hatch': '<path d="M3.501 16.001V0h1v16.001h-1zm7.998 0V0h1v16.001h-1z" fill="{{foreground}}"/>',
|
|
15
|
+
dot: '<path d="M3.999 2A2 2 0 0 1 6 3.999C6 5.103 5.103 6 3.999 6a2 2 0 0 1-1.999-2.001A2 2 0 0 1 3.999 2zm0 7.999C5.103 10 6 10.897 6 12.001A2 2 0 0 1 3.999 14a2 2 0 0 1-1.999-1.999A2 2 0 0 1 3.999 10zM11.999 2A2 2 0 0 1 14 3.999C14 5.103 13.103 6 11.999 6S10 5.103 10 3.999A2 2 0 0 1 11.999 2zm0 7.999c1.104 0 2.001.897 2.001 2.001A2 2 0 0 1 11.999 14 2 2 0 0 1 10 12.001c0-1.104.897-2.001 1.999-2.001z" fill="{{foreground}}"/>',
|
|
16
|
+
diamond: '<path d="M4 .465L7.535 4 4 7.535.465 4 4 .465zm0 7.999l3.535 3.535L4 15.535.465 11.999 4 8.464zm8-8l3.535 3.535-3.536 3.536L8.464 4 12 .464zm0 8.001L15.536 12 12 15.536 8.465 12 12 8.465z" fill="{{foreground}}"/>'
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Plugin-controlled border path used in the key symbol (20×20 coordinate space).
|
|
20
|
+
// This is always rendered as the first element, before the user-supplied content.
|
|
21
|
+
const KEY_BORDER_PATH = '<path d="M19 2.862v14.275c0 1.028-.835 1.862-1.862 1.862H2.863c-1.028 0-1.862-.835-1.862-1.862V2.862C1.001 1.834 1.836 1 2.863 1h14.275C18.166 1 19 1.835 19 2.862z" fill="{{background}}" stroke="{{foreground}}" stroke-width="2"/>'
|
|
22
|
+
|
|
23
|
+
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
|
24
|
+
|
|
25
|
+
export const hashString = (str) => {
|
|
26
|
+
let hash = 0
|
|
27
|
+
for (const ch of str) {
|
|
28
|
+
hash = ((hash << 5) - hash) + ch.codePointAt(0)
|
|
29
|
+
hash = hash & hash
|
|
30
|
+
}
|
|
31
|
+
return Math.abs(hash).toString(36) // NOSONAR: base36 encoding for compact alphanumeric hash string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const injectColors = (content, foreground, background) =>
|
|
35
|
+
content
|
|
36
|
+
.replace(/\{\{foreground\}\}/g, foreground || 'black')
|
|
37
|
+
.replace(/\{\{background\}\}/g, background || 'transparent')
|
|
38
|
+
|
|
39
|
+
// ─── Public API ───────────────────────────────────────────────────────────────
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns true if a dataset has a fill pattern configured.
|
|
43
|
+
* @param {Object} dataset
|
|
44
|
+
* @returns {boolean}
|
|
45
|
+
*/
|
|
46
|
+
export const hasPattern = (dataset) => !!(dataset.fillPattern || dataset.fillPatternSvgContent)
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Returns the raw (un-coloured) inner SVG content for a dataset's pattern.
|
|
50
|
+
* Custom fillPatternSvgContent takes precedence over built-in fillPattern ids.
|
|
51
|
+
* @param {Object} dataset
|
|
52
|
+
* @returns {string|null}
|
|
53
|
+
*/
|
|
54
|
+
export const getPatternInnerContent = (dataset) => {
|
|
55
|
+
if (dataset.fillPatternSvgContent) {
|
|
56
|
+
return dataset.fillPatternSvgContent
|
|
57
|
+
}
|
|
58
|
+
if (dataset.fillPattern && BUILT_IN_PATTERNS[dataset.fillPattern]) {
|
|
59
|
+
return BUILT_IN_PATTERNS[dataset.fillPattern]
|
|
60
|
+
}
|
|
61
|
+
return null
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns a deterministic image ID for a pattern + resolved colour combination.
|
|
66
|
+
* @param {Object} dataset
|
|
67
|
+
* @param {string} mapStyleId
|
|
68
|
+
* @returns {string|null}
|
|
69
|
+
*/
|
|
70
|
+
export const getPatternImageId = (dataset, mapStyleId) => {
|
|
71
|
+
const innerContent = getPatternInnerContent(dataset)
|
|
72
|
+
if (!innerContent) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
76
|
+
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
77
|
+
return `pattern-${hashString(innerContent + fg + bg)}`
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Returns colour-injected inner SVG path content for use in the Key symbol.
|
|
82
|
+
* The caller is responsible for wrapping this in the SVG element and border path.
|
|
83
|
+
* @param {Object} dataset
|
|
84
|
+
* @param {string} mapStyleId
|
|
85
|
+
* @returns {{ border: string, content: string }|null}
|
|
86
|
+
*/
|
|
87
|
+
export const getKeyPatternPaths = (dataset, mapStyleId) => {
|
|
88
|
+
const innerContent = getPatternInnerContent(dataset)
|
|
89
|
+
if (!innerContent) {
|
|
90
|
+
return null
|
|
91
|
+
}
|
|
92
|
+
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
93
|
+
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
94
|
+
const borderStroke = getValueForStyle(dataset.stroke, mapStyleId) || fg
|
|
95
|
+
return {
|
|
96
|
+
border: injectColors(KEY_BORDER_PATH, borderStroke, bg),
|
|
97
|
+
content: injectColors(innerContent, fg, bg)
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// ─── Rasterisation ────────────────────────────────────────────────────────────
|
|
102
|
+
|
|
103
|
+
// Module-level cache: imageId → ImageData. Avoids re-rasterising identical patterns.
|
|
104
|
+
const imageDataCache = new Map()
|
|
105
|
+
|
|
106
|
+
const rasteriseToImageData = (svgString, width, height) =>
|
|
107
|
+
new Promise((resolve, reject) => {
|
|
108
|
+
const blob = new Blob([svgString], { type: 'image/svg+xml' })
|
|
109
|
+
const url = URL.createObjectURL(blob)
|
|
110
|
+
const img = new Image(width, height)
|
|
111
|
+
img.onload = () => {
|
|
112
|
+
const canvas = document.createElement('canvas')
|
|
113
|
+
canvas.width = width
|
|
114
|
+
canvas.height = height
|
|
115
|
+
const ctx = canvas.getContext('2d')
|
|
116
|
+
ctx.drawImage(img, 0, 0, width, height)
|
|
117
|
+
URL.revokeObjectURL(url)
|
|
118
|
+
resolve(ctx.getImageData(0, 0, width, height))
|
|
119
|
+
}
|
|
120
|
+
img.onerror = () => {
|
|
121
|
+
URL.revokeObjectURL(url)
|
|
122
|
+
reject(new Error(`Failed to rasterise pattern SVG: ${svgString.slice(0, 80)}`))
|
|
123
|
+
}
|
|
124
|
+
img.src = url
|
|
125
|
+
})
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Rasterises a dataset's pattern SVG to ImageData, using an in-memory cache
|
|
129
|
+
* to avoid re-rasterising identical patterns. Framework-agnostic — callers
|
|
130
|
+
* are responsible for registering the result with their map framework.
|
|
131
|
+
*
|
|
132
|
+
* @param {Object} dataset
|
|
133
|
+
* @param {string} mapStyleId
|
|
134
|
+
* @returns {Promise<{ imageId: string, imageData: ImageData }|null>}
|
|
135
|
+
*/
|
|
136
|
+
export const rasterisePattern = async (dataset, mapStyleId) => {
|
|
137
|
+
const innerContent = getPatternInnerContent(dataset)
|
|
138
|
+
if (!innerContent) {
|
|
139
|
+
return null
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
143
|
+
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
144
|
+
const imageId = `pattern-${hashString(innerContent + fg + bg)}`
|
|
145
|
+
|
|
146
|
+
let imageData = imageDataCache.get(imageId)
|
|
147
|
+
if (!imageData) {
|
|
148
|
+
const colored = injectColors(innerContent, fg, bg)
|
|
149
|
+
const bgRect = `<rect width="16" height="16" fill="${bg}"/>`
|
|
150
|
+
// pixelRatio: 2 means the map treats this as an 8×8 logical tile — crisp on retina screens.
|
|
151
|
+
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 16 16">${bgRect}${colored}</svg>`
|
|
152
|
+
imageData = await rasteriseToImageData(svgString, 16, 16)
|
|
153
|
+
imageDataCache.set(imageId, imageData)
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return { imageId, imageData }
|
|
157
|
+
}
|
|
@@ -22,7 +22,7 @@ export const bboxContains = (outer, inner) => {
|
|
|
22
22
|
inner[0] >= outer[0] && // west
|
|
23
23
|
inner[1] >= outer[1] && // south
|
|
24
24
|
inner[2] <= outer[2] && // east
|
|
25
|
-
inner[3] <= outer[3]
|
|
25
|
+
inner[3] <= outer[3] // NOSONAR, north
|
|
26
26
|
)
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -40,7 +40,7 @@ export const expandBbox = (existing, addition) => {
|
|
|
40
40
|
Math.min(existing[0], addition[0]), // west
|
|
41
41
|
Math.min(existing[1], addition[1]), // south
|
|
42
42
|
Math.max(existing[2], addition[2]), // east
|
|
43
|
-
Math.max(existing[3], addition[3])
|
|
43
|
+
Math.max(existing[3], addition[3]) // NOSONAR, north
|
|
44
44
|
]
|
|
45
45
|
}
|
|
46
46
|
|
|
@@ -57,8 +57,8 @@ export const bboxIntersects = (a, b) => {
|
|
|
57
57
|
return !(
|
|
58
58
|
a[2] < b[0] || // a is left of b
|
|
59
59
|
a[0] > b[2] || // a is right of b
|
|
60
|
-
a[3] < b[1] || // a is below b
|
|
61
|
-
a[1] > b[3]
|
|
60
|
+
a[3] < b[1] || // NOSONAR a is below b
|
|
61
|
+
a[1] > b[3] // NOSONAR a is above b
|
|
62
62
|
)
|
|
63
63
|
}
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ export const bboxIntersects = (a, b) => {
|
|
|
68
68
|
* @returns {number[]} bbox as [west, south, east, north]
|
|
69
69
|
*/
|
|
70
70
|
export const getGeometryBbox = (geometry) => {
|
|
71
|
-
let minX = Infinity
|
|
71
|
+
let minX = Infinity; let minY = Infinity; let maxX = -Infinity; let maxY = -Infinity
|
|
72
72
|
|
|
73
73
|
const processCoord = (coord) => {
|
|
74
74
|
minX = Math.min(minX, coord[0])
|
|
@@ -98,7 +98,7 @@ export const getGeometryBbox = (geometry) => {
|
|
|
98
98
|
processCoords(geometry.coordinates, 2)
|
|
99
99
|
break
|
|
100
100
|
case 'MultiPolygon':
|
|
101
|
-
processCoords(geometry.coordinates, 3)
|
|
101
|
+
processCoords(geometry.coordinates, 3) // NOSONAR: 3 = coordinate nesting depth for MultiPolygon ([polygons][rings][points])
|
|
102
102
|
break
|
|
103
103
|
case 'GeometryCollection':
|
|
104
104
|
geometry.geometries.forEach(g => {
|
|
@@ -109,6 +109,8 @@ export const getGeometryBbox = (geometry) => {
|
|
|
109
109
|
maxY = Math.max(maxY, b[3])
|
|
110
110
|
})
|
|
111
111
|
break
|
|
112
|
+
default:
|
|
113
|
+
throw new Error(`Unsupported geometry type: ${geometry.type}`)
|
|
112
114
|
}
|
|
113
115
|
|
|
114
116
|
return [minX, minY, maxX, maxY]
|
|
@@ -8,8 +8,11 @@ export const buildExclusionFilter = (originalFilter, idProperty, excludeIds) =>
|
|
|
8
8
|
}
|
|
9
9
|
|
|
10
10
|
// Coerce both sides to strings to handle number/string type mismatches
|
|
11
|
-
|
|
12
|
-
const
|
|
11
|
+
// When no idProperty, use feature-level id via ['id'] (GeoJSON feature.id)
|
|
12
|
+
const idExpr = idProperty ? ['to-string', ['get', idProperty]] : ['to-string', ['id']]
|
|
13
|
+
// Convert all IDs to strings; map passes each element as the first argument to String
|
|
14
|
+
const stringIds = excludeIds.map(String)
|
|
15
|
+
const exclusionFilter = ['!', ['in', idExpr, ['literal', stringIds]]]
|
|
13
16
|
|
|
14
17
|
if (!originalFilter) {
|
|
15
18
|
return exclusionFilter
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import { hasCustomVisualStyle } from '../defaults.js'
|
|
2
|
+
|
|
3
|
+
const getFillProps = (dataset, sublayerStyle) => {
|
|
4
|
+
if (sublayerStyle.fillPattern || sublayerStyle.fillPatternSvgContent) {
|
|
5
|
+
return {
|
|
6
|
+
fillPattern: sublayerStyle.fillPattern,
|
|
7
|
+
fillPatternSvgContent: sublayerStyle.fillPatternSvgContent,
|
|
8
|
+
fillPatternForegroundColor: sublayerStyle.fillPatternForegroundColor ?? dataset.fillPatternForegroundColor,
|
|
9
|
+
fillPatternBackgroundColor: sublayerStyle.fillPatternBackgroundColor ?? dataset.fillPatternBackgroundColor
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
if ('fill' in sublayerStyle) {
|
|
13
|
+
// Sublayer explicitly sets a plain fill — do not inherit any parent pattern
|
|
14
|
+
return { fill: sublayerStyle.fill }
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
fill: dataset.fill,
|
|
18
|
+
fillPattern: dataset.fillPattern,
|
|
19
|
+
fillPatternSvgContent: dataset.fillPatternSvgContent,
|
|
20
|
+
fillPatternForegroundColor: dataset.fillPatternForegroundColor,
|
|
21
|
+
fillPatternBackgroundColor: dataset.fillPatternBackgroundColor
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const getCombinedFilter = (datasetFilter, sublayerFilter) => {
|
|
26
|
+
if (datasetFilter && sublayerFilter) {
|
|
27
|
+
return ['all', datasetFilter, sublayerFilter]
|
|
28
|
+
}
|
|
29
|
+
return sublayerFilter || datasetFilter || null
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const getSymbolDescription = (dataset, sublayerStyle) => {
|
|
33
|
+
if ('symbolDescription' in sublayerStyle) {
|
|
34
|
+
return sublayerStyle.symbolDescription
|
|
35
|
+
}
|
|
36
|
+
if (hasCustomVisualStyle(sublayerStyle)) {
|
|
37
|
+
return undefined
|
|
38
|
+
}
|
|
39
|
+
return dataset.symbolDescription
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Merge a sublayer with its parent dataset, producing a flat style
|
|
44
|
+
* object suitable for layer creation and key symbol rendering.
|
|
45
|
+
*
|
|
46
|
+
* The sublayer's nested `style` object is flattened before merging.
|
|
47
|
+
*
|
|
48
|
+
* Fill precedence (highest to lowest):
|
|
49
|
+
* 1. Sublayer's own fillPattern
|
|
50
|
+
* 2. Sublayer's own fill (explicit, even if transparent — clears any parent pattern)
|
|
51
|
+
* 3. Parent's fillPattern
|
|
52
|
+
* 4. Parent's fill
|
|
53
|
+
*
|
|
54
|
+
* symbolDescription is only inherited from the parent when the sublayer has no
|
|
55
|
+
* custom visual styles of its own. If the sublayer overrides stroke/fill/pattern
|
|
56
|
+
* without setting symbolDescription explicitly, no description is shown.
|
|
57
|
+
*/
|
|
58
|
+
export const mergeSublayer = (dataset, sublayer) => {
|
|
59
|
+
const sublayerStyle = sublayer.style || {}
|
|
60
|
+
const combinedFilter = getCombinedFilter(dataset.filter, sublayer.filter)
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
id: sublayer.id,
|
|
64
|
+
label: sublayer.label,
|
|
65
|
+
stroke: sublayerStyle.stroke ?? dataset.stroke,
|
|
66
|
+
strokeWidth: sublayerStyle.strokeWidth ?? dataset.strokeWidth,
|
|
67
|
+
strokeDashArray: sublayerStyle.strokeDashArray ?? dataset.strokeDashArray,
|
|
68
|
+
opacity: sublayerStyle.opacity ?? dataset.opacity,
|
|
69
|
+
keySymbolShape: sublayerStyle.keySymbolShape ?? dataset.keySymbolShape,
|
|
70
|
+
symbolDescription: getSymbolDescription(dataset, sublayerStyle),
|
|
71
|
+
showInKey: sublayer.showInKey ?? dataset.showInKey,
|
|
72
|
+
toggleVisibility: sublayer.toggleVisibility ?? false,
|
|
73
|
+
filter: combinedFilter,
|
|
74
|
+
minZoom: dataset.minZoom,
|
|
75
|
+
maxZoom: dataset.maxZoom,
|
|
76
|
+
...getFillProps(dataset, sublayerStyle)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
import e from"@babel/runtime/helpers/defineProperty";import{useEffect as t}from"preact/compat";import r from"@arcgis/core/widgets/Sketch/SketchViewModel.js";import o from"@arcgis/core/layers/GraphicsLayer.js";import a from"@babel/runtime/helpers/asyncToGenerator";import*as n from"@arcgis/core/geometry/operators/simplifyOperator.js";import i from"@arcgis/core/Graphic.js";function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function p(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?l(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):l(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function
|
|
1
|
+
import e from"@babel/runtime/helpers/defineProperty";import{useEffect as t}from"preact/compat";import r from"@arcgis/core/widgets/Sketch/SketchViewModel.js";import o from"@arcgis/core/layers/GraphicsLayer.js";import a from"@babel/runtime/helpers/asyncToGenerator";import*as n from"@arcgis/core/geometry/operators/simplifyOperator.js";import i from"@arcgis/core/Graphic.js";function l(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function p(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?l(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):l(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function c(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function u(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?c(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):c(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}function s(e){return{type:"simple-fill",color:[0,120,255,.2],outline:{color:"dark"===e?"#ffffff":"#d4351c",width:2}}}function d(e,t,r){return new i({geometry:{type:"polygon",rings:t,spatialReference:27700},attributes:{id:e},symbol:s(r)})}function y(e){if(null==e||!e.geometry)throw new Error("Invalid graphic");var{geometry:t,attributes:r={}}=e;switch(t.type){case"point":return{type:"Feature",geometry:{type:"Point",coordinates:[t.x,t.y]},properties:u({},r)};case"polyline":return{type:"Feature",geometry:{type:"LineString",coordinates:t.paths[0]},properties:u({},r)};case"polygon":return{type:"Feature",geometry:{type:"Polygon",coordinates:t.rings},properties:u({},r)};default:throw new Error("Unsupported geometry type: ".concat(t.type))}}function m(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter(function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable})),r.push.apply(r,o)}return r}function v(t){for(var r=1;r<arguments.length;r++){var o=null!=arguments[r]?arguments[r]:{};r%2?m(Object(o),!0).forEach(function(r){e(t,r,o[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):m(Object(o)).forEach(function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))})}return t}var f={mobile:{slot:"actions",showLabel:!0},tablet:{slot:"actions",showLabel:!0},desktop:{slot:"actions",showLabel:!0}},h={reducer:{initialState:{mode:null,feature:null,tempFeature:null},actions:{SET_MODE:(e,t)=>p(p({},e),{},{mode:t}),SET_FEATURE:(e,t)=>p(p({},e),{},{feature:void 0===t.feature?e.feature:t.feature,tempFeature:void 0===t.tempFeature?e.tempFeature:t.tempFeature})}},InitComponent:e=>{var i,l,p,c,{appState:u,mapState:m,pluginConfig:v,pluginState:f,services:h,mapProvider:g,buttonConfig:b}=e,{events:O,eventBus:w}=h,{mapColorScheme:S}=m.mapStyle||{},E=null===(i=null===(l=v.includeModes)||void 0===l?void 0:l.includes(u.mode))||void 0===i||i,k=null!==(p=null===(c=v.excludeModes)||void 0===c?void 0:c.includes(u.mode))&&void 0!==p&&p,P=m.isMapReady&&E&&!k;t(()=>{if(P&&!g.sketchViewModel){var{sketchViewModel:e,sketchLayer:t,emptySketchLayer:a}=(e=>{var{mapProvider:t}=e,{view:a}=t,n=new o({id:"sketchLayer"});a.map.add(n);var i=new o({id:"emptySketchLayer"});return a.map.add(i),{sketchViewModel:new r({view:a,layer:i,defaultUpdateOptions:{tool:"reshape",updateOnGraphicClick:!1,multipleSelectionEnabled:!1,toggleToolOnClick:!1,highlightOptions:{enabled:!1}}}),emptySketchLayer:i,sketchLayer:n}})({mapProvider:g});return g.sketchViewModel=e,g.sketchLayer=t,g.emptySketchLayer=a,w.emit("draw:ready"),()=>{g.sketchViewModel=null,g.sketchLayer=null,g.emptySketchLayer=null}}},[m.isMapReady,u.mode]),t(()=>{if(P&&g.sketchViewModel){var e=function(e){var{pluginState:t,mapProvider:r,events:o,eventBus:i,buttonConfig:l,mapColorScheme:p}=e,{view:c,sketchViewModel:u,sketchLayer:m,emptySketchLayer:v}=r;if(!u)return null;var{drawDone:f,drawCancel:h}=l,{dispatch:g,mode:b,feature:O}=t,w=function(){var e=a(function*(){var e,r=null===(e=t.feature)||void 0===e||null===(e=e.properties)||void 0===e?void 0:e.id,o=null,a="active"===u.state&&!r;if("active"===u.state&&r&&(u.cancel(),yield new Promise(e=>setTimeout(e,50))),u.polygonSymbol=s(p),null==m||m.graphics.items.forEach(e=>{var t=d(e.attributes.id,e.geometry.rings,p);e.symbol=t.symbol,r===e.attributes.id&&(o=e)}),o&&!a&&u.layer===m)try{yield u.update(o,{tool:"reshape",toggleToolOnClick:!1})}catch(e){"AbortError"!==e.name&&console.error("Error updating sketch:",e)}});return function(){return e.apply(this,arguments)}}(),S=()=>{var e,t,r=null!==(e=null===(t=m.graphics)||void 0===t||null===(t=t.items)||void 0===t?void 0:t[0])&&void 0!==e?e:null;r&&setTimeout(()=>u.update(r),0)},E=()=>w(),k=e=>{if(e){var t=y(e);i.emit("draw:updated",t),g({type:"SET_FEATURE",payload:{tempFeature:t}})}},P=function(){var e=a(function*(){b&&((u.updateGraphics||[]).length||S())});return function(){return e.apply(this,arguments)}}(),j=function(){var e=a(function*(e){var{toolEventInfo:t}=e,r=null==e?void 0:e.graphic;if(r&&"vertex-add"===(null==t?void 0:t.type)){var o,a,n=null===(o=r.geometry)||void 0===o?void 0:o.rings;(null==n?void 0:n.length)>1?setTimeout(()=>u.undo(),0):(null==n||null===(a=n[0])||void 0===a?void 0:a.length)>3&&k(r)}});return function(t){return e.apply(this,arguments)}}(),T=function(){var e=a(function*(e){var t,r=null==e||null===(t=e.graphics)||void 0===t?void 0:t[0];k(r)});return function(t){return e.apply(this,arguments)}}();i.on(o.MAP_STYLE_CHANGE,E);var C=u.on("update",e=>{var t,r=null===(t=e.toolEventInfo)||void 0===t?void 0:t.type,o=e.graphics[0];"move-start"===r&&(u.cancel(),S()),"reshape"===r&&(n.isSimple(o.geometry)||u.undo()),"reshape-stop"!==r&&"vertex-remove"!==r||k(o)}),F=c.on("click",P),L=u.on("create",j),M=u.on("undo",T),D=f.onClick,_=h.onClick;return f.onClick=()=>{u.cancel(),u.layer=v,g({type:"SET_MODE",payload:null}),g({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),i.emit("draw:done",{newFeature:t.tempFeature})},h.onClick=()=>{if(u.cancel(),m.removeAll(),O){var e=d(O.id||O.properties.id,O.geometry.coordinates,p);m.add(e)}u.layer=v,g({type:"SET_MODE",payload:null}),i.emit("draw:cancelled")},()=>{i.off(o.MAP_STYLE_CHANGE,E),C.remove(),F.remove(),L.remove(),M.remove(),f.onClick=D,h.onClick=_}}({pluginState:f,mapProvider:g,events:O,eventBus:w,buttonConfig:b,mapColorScheme:S});return()=>{e()}}},[P,S,f])},buttons:[v({id:"drawDone",label:"Done",variant:"primary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode},enableWhen:e=>{var{pluginState:t}=e;return!!t.tempFeature}},f),v({id:"drawCancel",label:"Cancel",variant:"tertiary",hiddenWhen:e=>{var{pluginState:t}=e;return!t.mode}},f)],api:{newPolygon:(e,t)=>{var{mapState:r,pluginState:o,mapProvider:a,services:n}=e,{dispatch:i}=o,{sketchViewModel:l,sketchLayer:p}=a,{eventBus:c}=n;l.layer=p;var u=l.on("create",e=>{if("complete"===e.state){e.graphic.attributes={id:t},requestAnimationFrame(()=>{l.update(e.graphic,{tool:"reshape",toggleToolOnClick:!1})});var r=y(e.graphic);c.emit("draw:created",r),i({type:"SET_FEATURE",payload:{tempFeature:r}}),u.remove()}});l.polygonSymbol=s(r.mapStyle.mapColorScheme),l.create("polygon"),i({type:"SET_MODE",payload:"new-polygon"})},editFeature:(e,t)=>{var{pluginState:r,mapProvider:o}=e,{dispatch:a}=r,{sketchViewModel:n,sketchLayer:i}=o,l=i.graphics.items.find(e=>e.attributes.id===t),p=l.geometry.extent,c=[p.xmin,p.ymin,p.xmax,p.ymax];o.fitToBounds(c),n.layer=i,n.update(l,{tool:"reshape",toggleToolOnClick:!1,enableRotation:!1,enableScaling:!1}),a({type:"SET_FEATURE",payload:{feature:y(l)}}),a({type:"SET_MODE",payload:"edit-feature"})},addFeature:(e,t)=>{var{pluginState:r,mapState:o,mapProvider:a,services:n}=e,{dispatch:i}=r,{mapStyle:l}=o,{sketchViewModel:p,sketchLayer:c,emptySketchLayer:u}=a,{eventBus:s}=n,y=d(t.id,t.geometry.coordinates,l.mapColorScheme);c.add(y),p.layer=u,i({type:"SET_FEATURE",payload:{feature:t}}),s.emit("draw:add",t)},deleteFeature:(e,t)=>{var{pluginState:r,mapProvider:o,services:a}=e,{dispatch:n}=r,{sketchViewModel:i,sketchLayer:l,emptySketchLayer:p}=o,{eventBus:c}=a,u=l.graphics.items.find(e=>e.attributes.id===t);i.cancel(),l.remove(u),i.layer=p,n({type:"SET_FEATURE",payload:{feature:null,tempFeature:null}}),c.emit("draw:delete",{featureId:t}),n({type:"SET_MODE",payload:null})}}};export{h as manifest};
|
|
@@ -2,14 +2,14 @@ import { useEffect } from 'react'
|
|
|
2
2
|
import { createSketchViewModel } from './sketchViewModel.js'
|
|
3
3
|
import { attachEvents } from './events.js'
|
|
4
4
|
|
|
5
|
-
export const DrawInit = ({
|
|
6
|
-
appState,
|
|
7
|
-
mapState,
|
|
8
|
-
pluginConfig,
|
|
9
|
-
pluginState,
|
|
10
|
-
services,
|
|
11
|
-
mapProvider,
|
|
12
|
-
buttonConfig
|
|
5
|
+
export const DrawInit = ({
|
|
6
|
+
appState,
|
|
7
|
+
mapState,
|
|
8
|
+
pluginConfig,
|
|
9
|
+
pluginState,
|
|
10
|
+
services,
|
|
11
|
+
mapProvider,
|
|
12
|
+
buttonConfig
|
|
13
13
|
}) => {
|
|
14
14
|
const { events, eventBus } = services
|
|
15
15
|
const { mapColorScheme } = mapState.mapStyle || {}
|
|
@@ -22,9 +22,9 @@ export const DrawInit = ({
|
|
|
22
22
|
// Initialize sketch components once
|
|
23
23
|
useEffect(() => {
|
|
24
24
|
if (!isActive || mapProvider.sketchViewModel) {
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
return
|
|
26
|
+
}
|
|
27
|
+
|
|
28
28
|
const { sketchViewModel, sketchLayer, emptySketchLayer } = createSketchViewModel({
|
|
29
29
|
pluginState,
|
|
30
30
|
mapProvider,
|
|
@@ -33,21 +33,21 @@ export const DrawInit = ({
|
|
|
33
33
|
|
|
34
34
|
mapProvider.sketchViewModel = sketchViewModel
|
|
35
35
|
mapProvider.sketchLayer = sketchLayer
|
|
36
|
-
|
|
36
|
+
mapProvider.emptySketchLayer = emptySketchLayer
|
|
37
37
|
eventBus.emit('draw:ready')
|
|
38
38
|
|
|
39
39
|
return () => {
|
|
40
40
|
mapProvider.sketchViewModel = null
|
|
41
41
|
mapProvider.sketchLayer = null
|
|
42
|
-
|
|
42
|
+
mapProvider.emptySketchLayer = null
|
|
43
43
|
}
|
|
44
44
|
}, [mapState.isMapReady, appState.mode])
|
|
45
45
|
|
|
46
46
|
// Attach/detach events
|
|
47
47
|
useEffect(() => {
|
|
48
48
|
if (!isActive || !mapProvider.sketchViewModel) {
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
return
|
|
50
|
+
}
|
|
51
51
|
|
|
52
52
|
const cleanup = attachEvents({
|
|
53
53
|
pluginState,
|
|
@@ -62,4 +62,4 @@ export const DrawInit = ({
|
|
|
62
62
|
cleanup()
|
|
63
63
|
}
|
|
64
64
|
}, [isActive, mapColorScheme, pluginState])
|
|
65
|
-
}
|
|
65
|
+
}
|
|
@@ -7,7 +7,7 @@ export const addFeature = ({ pluginState, mapState, mapProvider, services }, fea
|
|
|
7
7
|
const { eventBus } = services
|
|
8
8
|
|
|
9
9
|
const graphic = createGraphic(feature.id, feature.geometry.coordinates, mapStyle.mapColorScheme)
|
|
10
|
-
|
|
10
|
+
|
|
11
11
|
// Add the graphic to the layer
|
|
12
12
|
sketchLayer.add(graphic)
|
|
13
13
|
|
|
@@ -15,7 +15,7 @@ export const addFeature = ({ pluginState, mapState, mapProvider, services }, fea
|
|
|
15
15
|
sketchViewModel.layer = emptySketchLayer
|
|
16
16
|
|
|
17
17
|
// Store initial feature in plugin state
|
|
18
|
-
dispatch({ type: 'SET_FEATURE', payload: { feature }})
|
|
18
|
+
dispatch({ type: 'SET_FEATURE', payload: { feature } })
|
|
19
19
|
|
|
20
20
|
eventBus.emit('draw:add', feature)
|
|
21
|
-
}
|
|
21
|
+
}
|
|
@@ -10,13 +10,13 @@ export const deleteFeature = ({ pluginState, mapProvider, services }, featureId)
|
|
|
10
10
|
sketchViewModel.cancel()
|
|
11
11
|
sketchLayer.remove(graphic)
|
|
12
12
|
sketchViewModel.layer = emptySketchLayer
|
|
13
|
-
|
|
13
|
+
|
|
14
14
|
// Reset state
|
|
15
|
-
dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
|
|
15
|
+
dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null } })
|
|
16
16
|
|
|
17
17
|
// Emit event
|
|
18
18
|
eventBus.emit('draw:delete', { featureId })
|
|
19
19
|
|
|
20
20
|
// Clear mode
|
|
21
21
|
dispatch({ type: 'SET_MODE', payload: null })
|
|
22
|
-
}
|
|
22
|
+
}
|
|
@@ -18,12 +18,12 @@ export const editFeature = ({ pluginState, mapProvider }, featureId) => {
|
|
|
18
18
|
tool: 'reshape',
|
|
19
19
|
toggleToolOnClick: false,
|
|
20
20
|
enableRotation: false,
|
|
21
|
-
enableScaling: false
|
|
21
|
+
enableScaling: false
|
|
22
22
|
})
|
|
23
23
|
|
|
24
24
|
// Set original feature
|
|
25
25
|
const feature = graphicToGeoJSON(graphic)
|
|
26
|
-
dispatch({ type: 'SET_FEATURE', payload: { feature }})
|
|
26
|
+
dispatch({ type: 'SET_FEATURE', payload: { feature } })
|
|
27
27
|
|
|
28
28
|
dispatch({ type: 'SET_MODE', payload: 'edit-feature' })
|
|
29
|
-
}
|
|
29
|
+
}
|
|
@@ -12,7 +12,7 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
|
|
|
12
12
|
const handleCreateComplete = sketchViewModel.on('create', (e) => {
|
|
13
13
|
if (e.state === 'complete') {
|
|
14
14
|
e.graphic.attributes = { id: featureId }
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
// Fix: to address calling some sketchViewModel methods syncronously
|
|
17
17
|
requestAnimationFrame(() => {
|
|
18
18
|
sketchViewModel.update(e.graphic, {
|
|
@@ -24,7 +24,7 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
|
|
|
24
24
|
// Store temp feature in state and emit create
|
|
25
25
|
const tempFeature = graphicToGeoJSON(e.graphic)
|
|
26
26
|
eventBus.emit('draw:created', tempFeature)
|
|
27
|
-
dispatch({ type: 'SET_FEATURE', payload: { tempFeature }})
|
|
27
|
+
dispatch({ type: 'SET_FEATURE', payload: { tempFeature } })
|
|
28
28
|
|
|
29
29
|
handleCreateComplete.remove()
|
|
30
30
|
}
|
|
@@ -34,4 +34,4 @@ export const newPolygon = ({ mapState, pluginState, mapProvider, services }, fea
|
|
|
34
34
|
sketchViewModel.create('polygon')
|
|
35
35
|
|
|
36
36
|
dispatch({ type: 'SET_MODE', payload: 'new-polygon' })
|
|
37
|
-
}
|
|
37
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import * as simplifyOperator from
|
|
1
|
+
import * as simplifyOperator from '@arcgis/core/geometry/operators/simplifyOperator.js'
|
|
2
2
|
import { createGraphic, createSymbol, graphicToGeoJSON } from './graphic.js'
|
|
3
3
|
|
|
4
4
|
const MODE_CHANGE_DELAY = 50
|
|
5
5
|
|
|
6
|
-
export function attachEvents({ pluginState, mapProvider, events, eventBus, buttonConfig, mapColorScheme }) {
|
|
6
|
+
export function attachEvents ({ pluginState, mapProvider, events, eventBus, buttonConfig, mapColorScheme }) {
|
|
7
7
|
const { view, sketchViewModel, sketchLayer, emptySketchLayer } = mapProvider
|
|
8
8
|
|
|
9
9
|
if (!sketchViewModel) {
|
|
@@ -12,36 +12,36 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
12
12
|
|
|
13
13
|
const { drawDone, drawCancel } = buttonConfig
|
|
14
14
|
const { dispatch, mode, feature } = pluginState
|
|
15
|
-
|
|
15
|
+
|
|
16
16
|
// Re-colour graphics when map style changes
|
|
17
17
|
const reColour = async () => {
|
|
18
18
|
const activeGraphicId = pluginState.feature?.properties?.id
|
|
19
19
|
let activeGraphic = null
|
|
20
20
|
const isCreating = sketchViewModel.state === 'active' && !activeGraphicId
|
|
21
|
-
|
|
21
|
+
|
|
22
22
|
// Cancel and wait, but only if we're in update mode (not create mode)
|
|
23
23
|
if (sketchViewModel.state === 'active' && activeGraphicId) {
|
|
24
24
|
sketchViewModel.cancel()
|
|
25
25
|
await new Promise(resolve => setTimeout(resolve, MODE_CHANGE_DELAY))
|
|
26
26
|
}
|
|
27
|
-
|
|
27
|
+
|
|
28
28
|
// Update the default symbol for new polygons
|
|
29
29
|
sketchViewModel.polygonSymbol = createSymbol(mapColorScheme)
|
|
30
|
-
|
|
30
|
+
|
|
31
31
|
// Update existing graphics
|
|
32
32
|
sketchLayer?.graphics.items.forEach(graphic => {
|
|
33
33
|
const newGraphic = createGraphic(
|
|
34
|
-
graphic.attributes.id,
|
|
35
|
-
graphic.geometry.rings,
|
|
34
|
+
graphic.attributes.id,
|
|
35
|
+
graphic.geometry.rings,
|
|
36
36
|
mapColorScheme
|
|
37
37
|
)
|
|
38
38
|
graphic.symbol = newGraphic.symbol
|
|
39
|
-
|
|
39
|
+
|
|
40
40
|
if (activeGraphicId === graphic.attributes.id) {
|
|
41
41
|
activeGraphic = graphic
|
|
42
42
|
}
|
|
43
43
|
})
|
|
44
|
-
|
|
44
|
+
|
|
45
45
|
// Re-enter update mode only if we were editing (not creating)
|
|
46
46
|
if (activeGraphic && !isCreating && sketchViewModel.layer === sketchLayer) {
|
|
47
47
|
try {
|
|
@@ -68,10 +68,19 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
68
68
|
// Event handlers
|
|
69
69
|
const handleMapStyleChange = () => reColour()
|
|
70
70
|
|
|
71
|
+
const onGraphicChanged = (graphic) => {
|
|
72
|
+
if (!graphic) {
|
|
73
|
+
return
|
|
74
|
+
}
|
|
75
|
+
const tempFeature = graphicToGeoJSON(graphic)
|
|
76
|
+
eventBus.emit('draw:updated', tempFeature)
|
|
77
|
+
dispatch({ type: 'SET_FEATURE', payload: { tempFeature } })
|
|
78
|
+
}
|
|
79
|
+
|
|
71
80
|
const handleSketchUpdate = (e) => {
|
|
72
81
|
const toolInfoType = e.toolEventInfo?.type
|
|
73
82
|
const graphic = e.graphics[0]
|
|
74
|
-
|
|
83
|
+
|
|
75
84
|
// Prevent polygon move
|
|
76
85
|
if (toolInfoType === 'move-start') {
|
|
77
86
|
sketchViewModel.cancel()
|
|
@@ -87,10 +96,8 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
87
96
|
}
|
|
88
97
|
|
|
89
98
|
// Emit event on update
|
|
90
|
-
if (toolInfoType === 'reshape-stop') {
|
|
91
|
-
|
|
92
|
-
eventBus.emit('draw:updated', tempFeature)
|
|
93
|
-
dispatch({ type: 'SET_FEATURE', payload: { tempFeature }})
|
|
99
|
+
if (toolInfoType === 'reshape-stop' || toolInfoType === 'vertex-remove') {
|
|
100
|
+
onGraphicChanged(graphic)
|
|
94
101
|
}
|
|
95
102
|
}
|
|
96
103
|
|
|
@@ -108,17 +115,38 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
108
115
|
updateGraphic()
|
|
109
116
|
}
|
|
110
117
|
|
|
118
|
+
const handleCreate = async (event) => {
|
|
119
|
+
const { toolEventInfo } = event
|
|
120
|
+
const graphic = event?.graphic
|
|
121
|
+
if (graphic && toolEventInfo?.type === 'vertex-add') {
|
|
122
|
+
const rings = graphic.geometry?.rings
|
|
123
|
+
// rings.length is > 1 occurs when the shape becomes complex (ie self intersects)
|
|
124
|
+
// setTimeout is required to cause the undo to be called after handleCreate completes
|
|
125
|
+
// otherwise the previous change, rather than this one, is undone
|
|
126
|
+
if (rings?.length > 1) {
|
|
127
|
+
setTimeout(() => sketchViewModel.undo(), 0)
|
|
128
|
+
} else if (rings?.[0]?.length > 3) {
|
|
129
|
+
onGraphicChanged(graphic) // emit a graphic update on draw, once the graphic is 2D
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const handleUndo = async (event) => {
|
|
135
|
+
const graphic = event?.graphics?.[0]
|
|
136
|
+
onGraphicChanged(graphic)
|
|
137
|
+
}
|
|
138
|
+
|
|
111
139
|
const handleDone = () => {
|
|
112
140
|
sketchViewModel.cancel()
|
|
113
141
|
sketchViewModel.layer = emptySketchLayer
|
|
114
142
|
dispatch({ type: 'SET_MODE', payload: null })
|
|
115
|
-
dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null }})
|
|
143
|
+
dispatch({ type: 'SET_FEATURE', payload: { feature: null, tempFeature: null } })
|
|
116
144
|
eventBus.emit('draw:done', { newFeature: pluginState.tempFeature })
|
|
117
145
|
}
|
|
118
146
|
|
|
119
147
|
const handleCancel = () => {
|
|
120
148
|
sketchViewModel.cancel()
|
|
121
|
-
|
|
149
|
+
|
|
122
150
|
// Clear all graphics
|
|
123
151
|
sketchLayer.removeAll()
|
|
124
152
|
|
|
@@ -143,10 +171,12 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
143
171
|
eventBus.on(events.MAP_STYLE_CHANGE, handleMapStyleChange)
|
|
144
172
|
const sketchUpdateHandler = sketchViewModel.on('update', handleSketchUpdate)
|
|
145
173
|
const viewClickHandler = view.on('click', handleViewClick)
|
|
146
|
-
|
|
174
|
+
const createHandler = sketchViewModel.on('create', handleCreate)
|
|
175
|
+
const undoHandler = sketchViewModel.on('undo', handleUndo)
|
|
176
|
+
|
|
147
177
|
const prevDoneClick = drawDone.onClick
|
|
148
178
|
const prevCancelClick = drawCancel.onClick
|
|
149
|
-
|
|
179
|
+
|
|
150
180
|
drawDone.onClick = handleDone
|
|
151
181
|
drawCancel.onClick = handleCancel
|
|
152
182
|
|
|
@@ -155,7 +185,9 @@ export function attachEvents({ pluginState, mapProvider, events, eventBus, butto
|
|
|
155
185
|
eventBus.off(events.MAP_STYLE_CHANGE, handleMapStyleChange)
|
|
156
186
|
sketchUpdateHandler.remove()
|
|
157
187
|
viewClickHandler.remove()
|
|
188
|
+
createHandler.remove()
|
|
189
|
+
undoHandler.remove()
|
|
158
190
|
drawDone.onClick = prevDoneClick
|
|
159
191
|
drawCancel.onClick = prevCancelClick
|
|
160
192
|
}
|
|
161
|
-
}
|
|
193
|
+
}
|