@defra/interactive-map 0.0.4-alpha → 0.0.6-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/dist/css/index.css +1 -1
- package/dist/esm/im-core.js +1 -1
- package/dist/esm/index.js +1 -1
- package/dist/umd/im-core.js +1 -1
- package/dist/umd/index.js +1 -1
- package/docs/api.md +8 -0
- package/package.json +3 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js +2 -1
- package/plugins/beta/datasets/dist/umd/im-datasets-plugin.js.LICENSE.txt +1 -0
- package/plugins/beta/datasets/dist/umd/index.js +1 -1
- package/plugins/beta/datasets/src/datasets.js +55 -2
- package/plugins/beta/datasets/src/fetch/createDynamicSource.js +206 -0
- package/plugins/beta/datasets/src/fetch/fetchGeoJSON.js +38 -0
- package/plugins/beta/datasets/src/handleSetMapStyle.js +8 -1
- package/plugins/beta/datasets/src/mapLayers.js +41 -3
- package/plugins/beta/datasets/src/utils/bbox.js +113 -2
- 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/dist/umd/index.js +1 -1
- package/plugins/beta/draw-ml/src/api/addFeature.js +15 -3
- package/plugins/beta/draw-ml/src/api/editFeature.js +16 -5
- package/plugins/beta/draw-ml/src/api/newLine.js +32 -7
- package/plugins/beta/draw-ml/src/api/newPolygon.js +33 -8
- package/plugins/beta/draw-ml/src/defaults.js +1 -0
- package/plugins/beta/draw-ml/src/draw.scss +23 -0
- package/plugins/beta/draw-ml/src/events.js +55 -113
- package/plugins/beta/draw-ml/src/manifest.js +64 -38
- package/plugins/beta/draw-ml/src/mapboxSnap.js +16 -12
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +4 -3
- package/plugins/beta/draw-ml/src/modes/editVertex/geometryHelpers.js +135 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/helpers.js +2 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +134 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/undoHandlers.js +133 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexOperations.js +141 -0
- package/plugins/beta/draw-ml/src/modes/editVertex/vertexQueries.js +121 -0
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +51 -406
- package/plugins/beta/draw-ml/src/utils/flattenStyleProperties.js +49 -0
- 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 +1 -1
- package/plugins/beta/map-styles/dist/css/index.css +1 -1
- package/plugins/beta/map-styles/dist/esm/index.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/map-styles/src/mapStyles.scss +4 -1
- 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/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +1 -1
- package/plugins/interact/src/defaults.js +1 -0
- package/plugins/interact/src/events.js +37 -52
- package/plugins/interact/src/events.test.js +35 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.js +80 -48
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +64 -8
- package/plugins/interact/src/interact.scss +6 -0
- package/plugins/interact/src/reducer.js +25 -27
- package/plugins/interact/src/reducer.test.js +19 -6
- package/plugins/interact/src/utils/featureQueries.js +2 -2
- package/plugins/interact/src/utils/spatial.js +99 -0
- package/plugins/interact/src/utils/spatial.test.js +100 -0
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.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 +2 -2
- package/plugins/search/src/components/Form/Form.jsx +1 -1
- package/plugins/search/src/components/Suggestions/Suggestions.jsx +5 -5
- package/plugins/search/src/datasets.js +1 -1
- package/plugins/search/src/defaults.js +3 -0
- package/plugins/search/src/events/fetchSuggestions.js +48 -53
- package/plugins/search/src/events/formHandlers.js +3 -2
- package/plugins/search/src/events/index.js +1 -1
- package/plugins/search/src/events/inputHandlers.js +3 -1
- package/plugins/search/src/events/suggestionHandlers.js +12 -3
- package/plugins/search/src/search.scss +4 -1
- package/plugins/search/src/utils/parseOsNamesResults.js +12 -12
- package/providers/beta/esri/src/esriProvider.js +1 -1
- package/providers/beta/esri/src/index.js +1 -1
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-framework.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/defaults.js +3 -2
- package/providers/maplibre/src/index.js +22 -19
- package/providers/maplibre/src/maplibreProvider.js +13 -11
- package/providers/maplibre/src/utils/detectWebgl.js +1 -2
- package/providers/maplibre/src/utils/highlightFeatures.js +78 -67
- package/providers/maplibre/src/utils/labels.js +174 -120
- package/providers/maplibre/src/utils/maplibreFixes.js +2 -2
- package/providers/maplibre/src/utils/queryFeatures.js +146 -0
- package/providers/maplibre/src/utils/spatial.js +14 -17
- package/sonar-project.properties +39 -2
- package/src/App/components/Actions/Actions.jsx +2 -2
- package/src/App/components/Actions/Actions.module.scss +2 -2
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +3 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +9 -0
- package/src/App/components/Logo/Logo.jsx +1 -1
- package/src/App/components/MapButton/MapButton.jsx +217 -41
- package/src/App/components/MapButton/MapButton.module.scss +32 -21
- package/src/App/components/MapButton/MapButton.test.jsx +128 -84
- package/src/App/components/Markers/Markers.jsx +1 -1
- package/src/App/components/Markers/Markers.module.scss +0 -5
- package/src/App/components/Panel/Panel.jsx +75 -45
- package/src/App/components/Panel/Panel.module.scss +18 -24
- package/src/App/components/Panel/Panel.test.jsx +18 -0
- package/src/App/components/PopupMenu/PopupMenu.jsx +246 -0
- package/src/App/components/PopupMenu/PopupMenu.module.scss +70 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +304 -0
- package/src/App/components/Tooltip/Tooltip.jsx +4 -2
- package/src/App/components/Tooltip/getTooltipPosition.js +1 -1
- package/src/App/components/Viewport/MapController.jsx +1 -1
- package/src/App/components/Viewport/MapStatus.jsx +1 -1
- package/src/App/components/Viewport/Viewport.jsx +2 -2
- package/src/App/components/Viewport/Viewport.module.scss +2 -2
- package/src/App/controls/keyboardActions.js +3 -2
- package/src/App/controls/keyboardShortcuts.js +4 -2
- package/src/App/hooks/useButtonStateEvaluator.js +58 -45
- package/src/App/hooks/useButtonStateEvaluator.test.js +36 -3
- package/src/App/hooks/useCrossHairAPI.js +49 -47
- package/src/App/hooks/useFocusVisible.js +2 -2
- package/src/App/hooks/useInterfaceAPI.js +16 -4
- package/src/App/hooks/useKeyboardHint.js +1 -1
- package/src/App/hooks/useKeyboardShortcuts.js +4 -2
- package/src/App/hooks/useMapAnnouncements.js +34 -32
- package/src/App/hooks/useMapEvents.js +12 -1
- package/src/App/hooks/useMapProviderOverrides.js +3 -3
- package/src/App/hooks/useMapStateSync.js +7 -1
- package/src/App/hooks/useMapURLSync.js +6 -1
- package/src/App/hooks/useMarkersAPI.js +88 -50
- package/src/App/hooks/useMediaQueryDispatch.test.js +48 -0
- package/src/App/hooks/useModalPanelBehaviour.js +46 -40
- package/src/App/hooks/useResizeObserver.js +2 -2
- package/src/App/initialiseApp.js +45 -38
- package/src/App/layout/Layout.jsx +6 -4
- package/src/App/layout/layout.module.scss +13 -17
- package/src/App/registry/keyboardShortcutRegistry.js +12 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +28 -0
- package/src/App/registry/mergeManifests.js +1 -1
- package/src/App/registry/panelRegistry.js +1 -1
- package/src/App/registry/panelRegistry.test.js +30 -1
- package/src/App/registry/pluginRegistry.js +14 -51
- package/src/App/registry/pluginRegistry.test.js +54 -6
- package/src/App/renderer/HtmlElementHost.jsx +186 -0
- package/src/App/renderer/HtmlElementHost.test.jsx +231 -0
- package/src/App/renderer/mapButtons.js +11 -8
- package/src/App/renderer/mapButtons.test.js +6 -4
- package/src/App/renderer/mapControls.js +6 -0
- package/src/App/renderer/mapControls.test.js +10 -2
- package/src/App/renderer/mapPanels.js +39 -26
- package/src/App/renderer/mapPanels.test.js +29 -0
- package/src/App/renderer/pluginWrapper.test.js +28 -0
- package/src/App/renderer/slotHelpers.js +60 -0
- package/src/App/renderer/slotHelpers.test.js +82 -0
- package/src/App/store/AppProvider.jsx +21 -18
- package/src/App/store/AppProvider.test.jsx +40 -0
- package/src/App/store/PluginProvider.jsx +7 -5
- package/src/App/store/appActionsMap.js +28 -1
- package/src/App/store/appActionsMap.test.js +26 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +12 -24
- package/src/App/store/mapReducer.js +1 -1
- package/src/InteractiveMap/InteractiveMap.js +79 -6
- package/src/InteractiveMap/InteractiveMap.test.js +164 -2
- package/src/InteractiveMap/behaviourController.js +12 -2
- package/src/InteractiveMap/behaviourController.test.js +82 -1
- package/src/InteractiveMap/deviceChecker.js +1 -1
- package/src/InteractiveMap/deviceChecker.test.js +2 -2
- package/src/InteractiveMap/historyManager.js +41 -5
- package/src/InteractiveMap/historyManager.test.js +37 -6
- package/src/InteractiveMap/polyfills.js +39 -0
- package/src/InteractiveMap/polyfills.test.js +127 -0
- package/src/config/appConfig.js +5 -5
- package/src/config/appConfig.test.js +107 -42
- package/src/config/defaults.js +1 -0
- package/src/index.umd.js +1 -1
- package/src/scss/main.scss +1 -0
- package/src/scss/settings/_colors.scss +1 -1
- package/src/scss/settings/_dimensions.scss +4 -0
- package/src/services/eventBus.test.js +24 -1
- package/src/test-utils.js +1 -0
- package/src/types.js +5 -0
- package/src/utils/detectBreakpoint.js +78 -77
- package/src/utils/detectBreakpoint.test.js +50 -4
- package/src/utils/detectInterfaceType.js +6 -5
- package/src/utils/getIsFullscreen.js +3 -1
- package/src/utils/getSafeZoneInset.js +23 -7
- package/src/utils/getSafeZoneInset.test.js +18 -0
- package/src/utils/mapStateSync.js +3 -3
- package/src/utils/queryString.js +1 -1
- package/src/utils/stringToKebab.js +1 -1
- package/src/utils/toggleInertElements.js +37 -12
- package/webpack.dev.mjs +3 -4
- package/plugins/interact/src/utils/turfHelpers.js +0 -30
- package/plugins/interact/src/utils/turfHelpers.test.js +0 -30
- package/plugins/search/src/utils/fetchDataset.js +0 -23
- package/providers/maplibre/dist/esm/im-maplibre-legacy-framework.js +0 -1
- package/providers/maplibre/dist/umd/im-maplibre-legacy-framework.js +0 -1
|
@@ -24,51 +24,74 @@ export const manifest = {
|
|
|
24
24
|
InitComponent: DrawInit,
|
|
25
25
|
|
|
26
26
|
buttons: [{
|
|
27
|
-
id: 'drawDone',
|
|
28
|
-
label: ({ pluginState }) => pluginState.action ? pluginState.action.charAt(0).toUpperCase() + pluginState.action.slice(1) : 'Done',
|
|
29
|
-
hiddenWhen: ({ appState, pluginState }) => !pluginState.mode || appState.interfaceType !== 'mouse' && pluginState.mode !== 'edit_vertex',
|
|
30
|
-
enableWhen: ({ pluginState }) => pluginState.action ? pluginState.actionValid : !!pluginState.tempFeature,
|
|
31
|
-
...createButtonSlots(true),
|
|
32
|
-
variant: 'primary',
|
|
33
|
-
},{
|
|
34
27
|
id: 'drawAddPoint',
|
|
35
28
|
label: 'Add point',
|
|
36
29
|
variant: 'primary',
|
|
37
30
|
hiddenWhen: ({ appState, pluginState }) => !['draw_polygon', 'draw_line'].includes(pluginState.mode) || appState.interfaceType === 'mouse',
|
|
38
31
|
...createButtonSlots(true)
|
|
39
32
|
},{
|
|
40
|
-
id: '
|
|
41
|
-
label: '
|
|
42
|
-
|
|
43
|
-
variant: 'tertiary',
|
|
33
|
+
id: 'drawDone',
|
|
34
|
+
label: 'Done',
|
|
35
|
+
variant: 'primary',
|
|
44
36
|
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
|
|
45
|
-
enableWhen: ({ pluginState }) => pluginState.undoStackLength > 0,
|
|
46
|
-
...createButtonSlots(false)
|
|
47
|
-
},{
|
|
48
|
-
id: 'drawFinish',
|
|
49
|
-
label: 'Finish shape',
|
|
50
|
-
iconId: 'check',
|
|
51
|
-
variant: 'tertiary',
|
|
52
|
-
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line'].includes(pluginState.mode),
|
|
53
37
|
enableWhen: ({ pluginState }) => pluginState.numVertecies >= (pluginState.mode === 'draw_polygon' ? 3 : 2),
|
|
54
|
-
...createButtonSlots(
|
|
55
|
-
},{
|
|
56
|
-
id: 'drawDeletePoint',
|
|
57
|
-
label: 'Delete point',
|
|
58
|
-
iconId: 'close',
|
|
59
|
-
variant: 'tertiary',
|
|
60
|
-
enableWhen: ({ pluginState }) => pluginState.selectedVertexIndex >= 0 && pluginState.numVertecies > (pluginState.tempFeature?.geometry?.type === 'Polygon' ? 3 : 2),
|
|
61
|
-
hiddenWhen: ({ pluginState }) => !(['simple_select', 'edit_vertex'].includes(pluginState.mode)),
|
|
62
|
-
...createButtonSlots(false)
|
|
38
|
+
...createButtonSlots(true)
|
|
63
39
|
},{
|
|
64
|
-
id: '
|
|
65
|
-
label: '
|
|
66
|
-
iconId: '
|
|
40
|
+
id: 'drawMenu',
|
|
41
|
+
label: 'Menu',
|
|
42
|
+
iconId: 'menu',
|
|
67
43
|
variant: 'tertiary',
|
|
68
|
-
hiddenWhen: ({ pluginState }) => !
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
44
|
+
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
|
|
45
|
+
menuItems: [{
|
|
46
|
+
id: 'drawUndo',
|
|
47
|
+
label: 'Undo',
|
|
48
|
+
iconId: 'undo',
|
|
49
|
+
hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
|
|
50
|
+
enableWhen: ({ pluginState }) => pluginState.undoStackLength > 0,
|
|
51
|
+
},{
|
|
52
|
+
id: 'drawSnap',
|
|
53
|
+
label: 'Snap to feature',
|
|
54
|
+
iconId: 'magnet',
|
|
55
|
+
hiddenWhen: ({ pluginState }) => !pluginState.mode || !pluginState.hasSnapLayers,
|
|
56
|
+
pressedWhen: ({ pluginState }) => !!pluginState.snap,
|
|
57
|
+
},{
|
|
58
|
+
id: 'drawDeletePoint',
|
|
59
|
+
label: 'Delete point',
|
|
60
|
+
iconId: 'trash',
|
|
61
|
+
enableWhen: ({ pluginState }) => pluginState.selectedVertexIndex >= 0 && pluginState.numVertecies > (pluginState.tempFeature?.geometry?.type === 'Polygon' ? 3 : 2),
|
|
62
|
+
hiddenWhen: ({ pluginState }) => !(['simple_select', 'edit_vertex'].includes(pluginState.mode)),
|
|
63
|
+
}],
|
|
64
|
+
...createButtonSlots(true)
|
|
65
|
+
},
|
|
66
|
+
// {
|
|
67
|
+
// id: 'drawUndo',
|
|
68
|
+
// label: 'Undo',
|
|
69
|
+
// iconId: 'undo',
|
|
70
|
+
// variant: 'tertiary',
|
|
71
|
+
// hiddenWhen: ({ pluginState }) => !['draw_polygon', 'draw_line', 'edit_vertex'].includes(pluginState.mode),
|
|
72
|
+
// enableWhen: ({ pluginState }) => pluginState.undoStackLength > 0,
|
|
73
|
+
// excludeWhen: () => true,
|
|
74
|
+
// ...createButtonSlots(false)
|
|
75
|
+
// },{
|
|
76
|
+
// id: 'drawDeletePoint',
|
|
77
|
+
// label: 'Delete point',
|
|
78
|
+
// iconId: 'trash',
|
|
79
|
+
// variant: 'tertiary',
|
|
80
|
+
// enableWhen: ({ pluginState }) => pluginState.selectedVertexIndex >= 0 && pluginState.numVertecies > (pluginState.tempFeature?.geometry?.type === 'Polygon' ? 3 : 2),
|
|
81
|
+
// hiddenWhen: ({ pluginState }) => !(['simple_select', 'edit_vertex'].includes(pluginState.mode)),
|
|
82
|
+
// excludeWhen: () => true,
|
|
83
|
+
// ...createButtonSlots(false)
|
|
84
|
+
// },{
|
|
85
|
+
// id: 'drawSnap',
|
|
86
|
+
// label: 'Snap to line',
|
|
87
|
+
// iconId: 'magnet',
|
|
88
|
+
// variant: 'tertiary',
|
|
89
|
+
// hiddenWhen: ({ pluginState }) => !pluginState.mode || !pluginState.hasSnapLayers,
|
|
90
|
+
// pressedWhen: ({ pluginState }) => !!pluginState.snap,
|
|
91
|
+
// excludeWhen: () => true,
|
|
92
|
+
// ...createButtonSlots(false)
|
|
93
|
+
// },
|
|
94
|
+
{
|
|
72
95
|
id: 'drawCancel',
|
|
73
96
|
label: 'Cancel',
|
|
74
97
|
variant: 'tertiary',
|
|
@@ -84,14 +107,17 @@ export const manifest = {
|
|
|
84
107
|
}],
|
|
85
108
|
|
|
86
109
|
icons: [{
|
|
87
|
-
id: '
|
|
88
|
-
svgContent: '<path d="
|
|
110
|
+
id: 'menu',
|
|
111
|
+
svgContent: '<circle cx="12" cy="12" r="10"/><path d="m16 10-4 4-4-4"/>'
|
|
89
112
|
},{
|
|
90
113
|
id: 'undo',
|
|
91
|
-
svgContent: '<path d="
|
|
114
|
+
svgContent: '<path d="M9 14 4 9l5-5"/><path d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5a5.5 5.5 0 0 1-5.5 5.5H11"/>'
|
|
92
115
|
},{
|
|
93
116
|
id: 'magnet',
|
|
94
117
|
svgContent: '<path d="m12 15 4 4"/><path d="M2.352 10.648a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.029-6.029a1 1 0 1 1 3 3l-6.029 6.029a1.205 1.205 0 0 0 0 1.704l2.296 2.296a1.205 1.205 0 0 0 1.704 0l6.365-6.367A1 1 0 0 0 8.716 4.282z"/><path d="m5 8 4 4"/>'
|
|
118
|
+
},{
|
|
119
|
+
id: 'trash',
|
|
120
|
+
svgContent: '<path d="M10 11v6"/><path d="M14 11v6"/><path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"/><path d="M3 6h18"/><path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"/>'
|
|
95
121
|
}],
|
|
96
122
|
|
|
97
123
|
api: {
|
|
@@ -112,9 +112,9 @@ function applyMapboxSnapPatches(colors) {
|
|
|
112
112
|
return r
|
|
113
113
|
}
|
|
114
114
|
|
|
115
|
-
// Skip when disabled, clean up internal arrays to prevent memory accumulation
|
|
115
|
+
// Skip when disabled or zooming, clean up internal arrays to prevent memory accumulation
|
|
116
116
|
proto.snapToClosestPoint = function(e) {
|
|
117
|
-
if (!this.status) {
|
|
117
|
+
if (!this.status || this.map?._isZooming) {
|
|
118
118
|
return
|
|
119
119
|
}
|
|
120
120
|
try {
|
|
@@ -140,6 +140,8 @@ function applyMapboxSnapPatches(colors) {
|
|
|
140
140
|
function pollUntil(checkFn, onSuccess) {
|
|
141
141
|
(function poll() {
|
|
142
142
|
const result = checkFn()
|
|
143
|
+
// null signals to stop polling, falsy continues polling
|
|
144
|
+
if (result === null) return
|
|
143
145
|
result ? onSuccess(result) : requestAnimationFrame(poll)
|
|
144
146
|
})()
|
|
145
147
|
}
|
|
@@ -267,7 +269,7 @@ export function initMapLibreSnap(map, draw, snapOptions = {}) {
|
|
|
267
269
|
// Handle style changes - re-patch source and ensure snap layer exists
|
|
268
270
|
map.on('style.load', () => {
|
|
269
271
|
pollUntil(
|
|
270
|
-
() => map.getSource('mapbox-gl-draw-hot'),
|
|
272
|
+
() => map._removed ? null : map.getSource('mapbox-gl-draw-hot'),
|
|
271
273
|
(source) => {
|
|
272
274
|
patchSourceData(source)
|
|
273
275
|
|
|
@@ -295,24 +297,26 @@ export function initMapLibreSnap(map, draw, snapOptions = {}) {
|
|
|
295
297
|
)
|
|
296
298
|
})
|
|
297
299
|
|
|
298
|
-
//
|
|
300
|
+
// Suppress snap processing during zoom (indicator freezes in place)
|
|
299
301
|
map.on('zoomstart', () => {
|
|
300
|
-
|
|
301
|
-
map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', 'none')
|
|
302
|
-
}
|
|
302
|
+
map._isZooming = true
|
|
303
303
|
})
|
|
304
304
|
|
|
305
305
|
map.on('zoomend', () => {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
if (map.getLayer(SNAP_HELPER_LAYER)
|
|
309
|
-
map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', '
|
|
306
|
+
map._isZooming = false
|
|
307
|
+
// Force hide then re-show to reset indicator at new zoom level (Safari fix)
|
|
308
|
+
if (map.getLayer(SNAP_HELPER_LAYER)) {
|
|
309
|
+
map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', 'none')
|
|
310
|
+
const snap = map._snapInstance
|
|
311
|
+
if (snap?.status) {
|
|
312
|
+
map.setLayoutProperty(SNAP_HELPER_LAYER, 'visibility', 'visible')
|
|
313
|
+
}
|
|
310
314
|
}
|
|
311
315
|
})
|
|
312
316
|
|
|
313
317
|
// Initial setup - poll until draw source exists
|
|
314
318
|
pollUntil(
|
|
315
|
-
() => map.getSource('mapbox-gl-draw-hot'),
|
|
319
|
+
() => map._removed ? null : map.getSource('mapbox-gl-draw-hot'),
|
|
316
320
|
createSnap
|
|
317
321
|
)
|
|
318
322
|
}
|
|
@@ -226,7 +226,8 @@ export const createDrawMode = (ParentMode, config) => {
|
|
|
226
226
|
interfaceType: state.interfaceType,
|
|
227
227
|
vertexMarkerId: state.vertexMarkerId,
|
|
228
228
|
addVertexButtonId: state.addVertexButtonId,
|
|
229
|
-
getSnapEnabled: state.getSnapEnabled
|
|
229
|
+
getSnapEnabled: state.getSnapEnabled,
|
|
230
|
+
properties: state.properties
|
|
230
231
|
})
|
|
231
232
|
return true
|
|
232
233
|
}
|
|
@@ -236,7 +237,7 @@ export const createDrawMode = (ParentMode, config) => {
|
|
|
236
237
|
const initialCoords = [[center.lng, center.lat], [center.lng, center.lat]]
|
|
237
238
|
const newFeature = this.newFeature({
|
|
238
239
|
type: 'Feature',
|
|
239
|
-
properties: {},
|
|
240
|
+
properties: state.properties || {},
|
|
240
241
|
geometry: {
|
|
241
242
|
type: geometryType,
|
|
242
243
|
coordinates: [initialCoords]
|
|
@@ -343,7 +344,7 @@ export const createDrawMode = (ParentMode, config) => {
|
|
|
343
344
|
const feature = e.features[0]
|
|
344
345
|
draw.delete(feature.id)
|
|
345
346
|
feature.id = state.featureId
|
|
346
|
-
draw.add(feature)
|
|
347
|
+
draw.add(feature, { userProperties: true })
|
|
347
348
|
},
|
|
348
349
|
|
|
349
350
|
onVertexButtonClick(state, e) {
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure geometry helper functions for multi-ring/multi-part geometry support.
|
|
3
|
+
* These functions handle coordinate transformations between flat arrays and
|
|
4
|
+
* hierarchical GeoJSON structures for Polygon, MultiPolygon, LineString, and MultiLineString.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Get flat coordinates array from feature for all geometry types.
|
|
9
|
+
* Flattens all rings/parts into a single array for unified vertex navigation.
|
|
10
|
+
*
|
|
11
|
+
* @param {Object} feature - GeoJSON geometry object
|
|
12
|
+
* @returns {Array<[number, number]>} Flat array of all coordinates
|
|
13
|
+
*/
|
|
14
|
+
export const getCoords = (feature) => {
|
|
15
|
+
if (!feature?.coordinates) return []
|
|
16
|
+
switch (feature.type) {
|
|
17
|
+
case 'LineString':
|
|
18
|
+
return feature.coordinates
|
|
19
|
+
case 'Polygon':
|
|
20
|
+
return feature.coordinates.flat(1)
|
|
21
|
+
case 'MultiLineString':
|
|
22
|
+
return feature.coordinates.flat(1)
|
|
23
|
+
case 'MultiPolygon':
|
|
24
|
+
return feature.coordinates.flat(2)
|
|
25
|
+
default:
|
|
26
|
+
return []
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get segment metadata for multi-ring/multi-part geometries.
|
|
32
|
+
* Each segment represents a ring (for Polygon) or part (for Multi*).
|
|
33
|
+
*
|
|
34
|
+
* @param {Object} feature - GeoJSON geometry object
|
|
35
|
+
* @returns {Array<{start: number, length: number, path: number[], closed: boolean}>}
|
|
36
|
+
* Array of segment metadata objects where:
|
|
37
|
+
* - start: Starting index in flat coordinates array
|
|
38
|
+
* - length: Number of coordinates in this segment
|
|
39
|
+
* - path: Hierarchical path indices to reach this segment in GeoJSON
|
|
40
|
+
* - closed: Whether this segment is closed (true for Polygon rings)
|
|
41
|
+
*/
|
|
42
|
+
export const getRingSegments = (feature) => {
|
|
43
|
+
if (!feature?.coordinates) return []
|
|
44
|
+
const segments = []
|
|
45
|
+
let start = 0
|
|
46
|
+
|
|
47
|
+
switch (feature.type) {
|
|
48
|
+
case 'LineString':
|
|
49
|
+
segments.push({ start: 0, length: feature.coordinates.length, path: [], closed: false })
|
|
50
|
+
break
|
|
51
|
+
case 'Polygon':
|
|
52
|
+
feature.coordinates.forEach((ring, ringIdx) => {
|
|
53
|
+
segments.push({ start, length: ring.length, path: [ringIdx], closed: true })
|
|
54
|
+
start += ring.length
|
|
55
|
+
})
|
|
56
|
+
break
|
|
57
|
+
case 'MultiLineString':
|
|
58
|
+
feature.coordinates.forEach((line, lineIdx) => {
|
|
59
|
+
segments.push({ start, length: line.length, path: [lineIdx], closed: false })
|
|
60
|
+
start += line.length
|
|
61
|
+
})
|
|
62
|
+
break
|
|
63
|
+
case 'MultiPolygon':
|
|
64
|
+
feature.coordinates.forEach((polygon, polyIdx) => {
|
|
65
|
+
polygon.forEach((ring, ringIdx) => {
|
|
66
|
+
segments.push({ start, length: ring.length, path: [polyIdx, ringIdx], closed: true })
|
|
67
|
+
start += ring.length
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
break
|
|
71
|
+
default:
|
|
72
|
+
break
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return segments
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Find which segment a flat vertex index belongs to.
|
|
80
|
+
*
|
|
81
|
+
* @param {Array} segments - Array of segment metadata from getRingSegments
|
|
82
|
+
* @param {number} flatIdx - Flat vertex index
|
|
83
|
+
* @returns {{segment: Object, localIdx: number}|null}
|
|
84
|
+
* Object with segment metadata and local index within that segment, or null if not found
|
|
85
|
+
*/
|
|
86
|
+
export const getSegmentForIndex = (segments, flatIdx) => {
|
|
87
|
+
for (const seg of segments) {
|
|
88
|
+
if (flatIdx >= seg.start && flatIdx < seg.start + seg.length) {
|
|
89
|
+
return { segment: seg, localIdx: flatIdx - seg.start }
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
return null
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Get modifiable coordinate array at a specific hierarchical path.
|
|
97
|
+
* Returns a reference to the actual coordinate array in the GeoJSON structure.
|
|
98
|
+
*
|
|
99
|
+
* @param {Object} geojson - Full GeoJSON feature object
|
|
100
|
+
* @param {number[]} path - Hierarchical path indices (e.g., [0] for first ring, [1, 0] for second polygon's first ring)
|
|
101
|
+
* @returns {Array<[number, number]>} Reference to coordinate array at path
|
|
102
|
+
*/
|
|
103
|
+
export const getModifiableCoords = (geojson, path) => {
|
|
104
|
+
let coords = geojson.geometry.coordinates
|
|
105
|
+
for (const idx of path) {
|
|
106
|
+
coords = coords[idx]
|
|
107
|
+
}
|
|
108
|
+
return coords
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* Convert mapbox-gl-draw coord_path string to flat vertex index.
|
|
113
|
+
* coord_path format: "ringIdx.vertexIdx" for Polygon, "polyIdx.ringIdx.vertexIdx" for MultiPolygon, etc.
|
|
114
|
+
*
|
|
115
|
+
* @param {Object} feature - GeoJSON geometry object
|
|
116
|
+
* @param {string} coordPath - coord_path string from mapbox-gl-draw
|
|
117
|
+
* @returns {number} Flat vertex index in the unified coordinate array
|
|
118
|
+
*/
|
|
119
|
+
export const coordPathToFlatIndex = (feature, coordPath) => {
|
|
120
|
+
const parts = coordPath.split('.').map(Number)
|
|
121
|
+
const segments = getRingSegments(feature)
|
|
122
|
+
|
|
123
|
+
// Match coord_path to segment
|
|
124
|
+
for (const seg of segments) {
|
|
125
|
+
// Check if path matches (compare all but last element which is the local vertex index)
|
|
126
|
+
const pathMatches = seg.path.every((val, idx) => val === parts[idx])
|
|
127
|
+
if (pathMatches && parts.length === seg.path.length + 1) {
|
|
128
|
+
const localIdx = parts[parts.length - 1]
|
|
129
|
+
return seg.start + localIdx
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Fallback: just use the last number (works for simple geometries)
|
|
134
|
+
return parts[parts.length - 1]
|
|
135
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getSnapInstance, isSnapEnabled, triggerSnapAtPoint, getSnapLngLat,
|
|
3
|
+
clearSnapState, clearSnapIndicator
|
|
4
|
+
} from '../../utils/snapHelpers.js'
|
|
5
|
+
import { coordPathToFlatIndex } from './geometryHelpers.js'
|
|
6
|
+
import { isOnSVG } from './helpers.js'
|
|
7
|
+
|
|
8
|
+
const touchVertexTarget = `
|
|
9
|
+
<svg width='48' height='48' viewBox='0 0 48 48' fill-rule='evenodd' style='display:none;position:absolute;top:50%;left:50%;margin:24px 0 0 -24px' class='touch-vertex-target' data-touch-vertex-target>
|
|
10
|
+
<circle cx='24' cy='24' r='24' fill='currentColor'/>
|
|
11
|
+
<path d="M37.543 25H34a1 1 0 1 1 0-2h3.629l-.836-.837a1 1 0 0 1 1.414-1.414l2.5 2.501A1 1 0 0 1 41 24a1 1 0 0 1-.487.858l-2.306 2.306a1 1 0 0 1-1.414-1.414l.75-.75zM23 10.414l-.793.793a1 1 0 0 1-1.414-1.414l2.5-2.5C23.481 7.105 23.734 7 24 7s.519.105.707.293l2.5 2.5a1 1 0 0 1-1.414 1.414L25 10.414V14a1 1 0 1 1-2 0v-3.586zM7 24a1 1 0 0 1 .293-.75l2.5-2.501a1 1 0 0 1 1.414 1.414l-.836.837H14a1 1 0 1 1 0 2h-3.543l.75.75a1 1 0 0 1-1.414 1.414l-2.306-2.306A1 1 0 0 1 7 24zm16.293 16.707l-2.5-2.5a1 1 0 0 1 1.414-1.414l.793.793V34a1 1 0 1 1 2 0v3.586l.793-.793a1 1 0 0 1 1.414 1.414l-2.5 2.5c-.188.188-.441.293-.707.293s-.519-.105-.707-.293zM24 20c2.208 0 4 1.792 4 4s-1.792 4-4 4-4-1.792-4-4 1.792-4 4-4z" fill="#fff"/>
|
|
12
|
+
</svg>
|
|
13
|
+
`
|
|
14
|
+
|
|
15
|
+
export const touchHandlers = {
|
|
16
|
+
addTouchVertexTarget(state) {
|
|
17
|
+
let el = state.container.querySelector('[data-touch-vertex-target]')
|
|
18
|
+
if (!el) {
|
|
19
|
+
state.container.insertAdjacentHTML('beforeend', touchVertexTarget)
|
|
20
|
+
el = state.container.querySelector('[data-touch-vertex-target]')
|
|
21
|
+
}
|
|
22
|
+
state.touchVertexTarget = el
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
updateTouchVertexTarget(state, point) {
|
|
26
|
+
if (point && state.interfaceType === 'touch' && state.selectedVertexIndex >= 0) {
|
|
27
|
+
Object.assign(state.touchVertexTarget.style, { display: 'block', top: `${point.y}px`, left: `${point.x}px` })
|
|
28
|
+
} else {
|
|
29
|
+
state.touchVertexTarget.style.display = 'none'
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
hideTouchVertexIndicator(state) {
|
|
34
|
+
state.touchVertexTarget.style.display = 'none'
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
onPointerevent(state, e) {
|
|
38
|
+
state.interfaceType = e.pointerType === 'touch' ? 'touch' : 'pointer'
|
|
39
|
+
state.isPanEnabled = true
|
|
40
|
+
if (e.pointerType === 'touch' && e.type === 'pointermove' && !isOnSVG(e.target.parentNode) && !state._ignorePointermoveDeselect) {
|
|
41
|
+
this.changeMode(state, { selectedVertexIndex: -1, selectedVertexType: null, coordPath: null })
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
// Empty stubs required by DirectSelect
|
|
46
|
+
onTouchStart() {},
|
|
47
|
+
onTouchMove() {},
|
|
48
|
+
onTouchEnd() {},
|
|
49
|
+
|
|
50
|
+
onTouchend(state) {
|
|
51
|
+
clearSnapState(getSnapInstance(this.map))
|
|
52
|
+
if (state?.featureId) {
|
|
53
|
+
this.syncVertices(state)
|
|
54
|
+
|
|
55
|
+
// Push undo for the move if touch actually moved
|
|
56
|
+
if (state._touchMoved && state._moveStartPosition && state._moveStartIndex !== undefined) {
|
|
57
|
+
this.pushUndo({
|
|
58
|
+
type: 'move_vertex',
|
|
59
|
+
featureId: state.featureId,
|
|
60
|
+
vertexIndex: state._moveStartIndex,
|
|
61
|
+
previousPosition: state._moveStartPosition
|
|
62
|
+
})
|
|
63
|
+
}
|
|
64
|
+
state._moveStartPosition = null
|
|
65
|
+
state._moveStartIndex = undefined
|
|
66
|
+
state._touchMoved = false
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
onTap(state, e) {
|
|
71
|
+
// Hide snap indicator on any tap
|
|
72
|
+
const snap = getSnapInstance(this.map)
|
|
73
|
+
if (snap) {
|
|
74
|
+
clearSnapIndicator(snap, this.map)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const meta = e.featureTarget?.properties.meta
|
|
78
|
+
const coordPath = e.featureTarget?.properties.coord_path
|
|
79
|
+
|
|
80
|
+
if (meta === 'vertex') {
|
|
81
|
+
const feature = this.getFeature(state.featureId)
|
|
82
|
+
const idx = coordPathToFlatIndex(feature, coordPath)
|
|
83
|
+
this.changeMode(state, {
|
|
84
|
+
selectedVertexIndex: idx,
|
|
85
|
+
selectedVertexType: 'vertex',
|
|
86
|
+
coordPath
|
|
87
|
+
})
|
|
88
|
+
} else if (meta === 'midpoint') {
|
|
89
|
+
this.insertVertex({ ...state, selectedVertexIndex: this.getVertexIndexFromMidpoint(state, coordPath), selectedVertexType: 'midpoint' })
|
|
90
|
+
} else {
|
|
91
|
+
this.clickNoTarget(state)
|
|
92
|
+
}
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
onTouchstart(state, e) {
|
|
96
|
+
clearSnapState(getSnapInstance(this.map))
|
|
97
|
+
const vertex = state.vertecies?.[state.selectedVertexIndex]
|
|
98
|
+
if (!vertex || !isOnSVG(e.target.parentNode)) {
|
|
99
|
+
return
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Save starting position for undo
|
|
103
|
+
state._moveStartPosition = [...vertex]
|
|
104
|
+
state._moveStartIndex = state.selectedVertexIndex
|
|
105
|
+
state._touchMoved = false
|
|
106
|
+
|
|
107
|
+
const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
|
|
108
|
+
const style = window.getComputedStyle(state.touchVertexTarget)
|
|
109
|
+
state.deltaTarget = { x: touch.x - Number.parseFloat(style.left), y: touch.y - Number.parseFloat(style.top) }
|
|
110
|
+
const vertexPt = this.map.project(vertex)
|
|
111
|
+
state.deltaVertex = { x: (touch.x / state.scale) - vertexPt.x, y: (touch.y / state.scale) - vertexPt.y }
|
|
112
|
+
},
|
|
113
|
+
|
|
114
|
+
onTouchmove(state, e) {
|
|
115
|
+
if (state.selectedVertexIndex < 0 || !isOnSVG(e.target.parentNode)) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
state._touchMoved = true
|
|
120
|
+
|
|
121
|
+
const touch = { x: e.touches[0].clientX, y: e.touches[0].clientY }
|
|
122
|
+
const screenPt = { x: (touch.x / state.scale) - state.deltaVertex.x, y: (touch.y / state.scale) - state.deltaVertex.y }
|
|
123
|
+
|
|
124
|
+
let finalCoord = this.map.unproject(screenPt)
|
|
125
|
+
if (isSnapEnabled(state)) {
|
|
126
|
+
const snap = getSnapInstance(this.map)
|
|
127
|
+
triggerSnapAtPoint(snap, this.map, screenPt)
|
|
128
|
+
finalCoord = getSnapLngLat(snap) || finalCoord
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.moveVertex(state, finalCoord)
|
|
132
|
+
this.updateTouchVertexTarget(state, { x: touch.x - state.deltaTarget.x, y: touch.y - state.deltaTarget.y })
|
|
133
|
+
}
|
|
134
|
+
}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import {
|
|
2
|
+
getRingSegments,
|
|
3
|
+
getSegmentForIndex,
|
|
4
|
+
getModifiableCoords
|
|
5
|
+
} from './geometryHelpers.js'
|
|
6
|
+
import { scalePoint } from './helpers.js'
|
|
7
|
+
|
|
8
|
+
export const undoHandlers = {
|
|
9
|
+
// Fire geometry change event (for external listeners)
|
|
10
|
+
fireGeometryChange(state) {
|
|
11
|
+
const feature = this.getFeature(state.featureId)
|
|
12
|
+
if (feature) {
|
|
13
|
+
this.map.fire('draw.update', {
|
|
14
|
+
features: [feature.toGeoJSON()],
|
|
15
|
+
action: 'change_coordinates'
|
|
16
|
+
})
|
|
17
|
+
}
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
// Undo support
|
|
21
|
+
pushUndo(operation) {
|
|
22
|
+
const undoStack = this.map._undoStack
|
|
23
|
+
if (!undoStack) {
|
|
24
|
+
return
|
|
25
|
+
}
|
|
26
|
+
undoStack.push(operation)
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
handleUndo(state) {
|
|
30
|
+
const undoStack = this.map._undoStack
|
|
31
|
+
if (!undoStack || undoStack.length === 0) {
|
|
32
|
+
return
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const op = undoStack.pop()
|
|
36
|
+
|
|
37
|
+
if (op.type === 'move_vertex') {
|
|
38
|
+
this.undoMoveVertex(state, op)
|
|
39
|
+
} else if (op.type === 'insert_vertex') {
|
|
40
|
+
this.undoInsertVertex(state, op)
|
|
41
|
+
} else if (op.type === 'delete_vertex') {
|
|
42
|
+
this.undoDeleteVertex(state, op)
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
|
|
46
|
+
undoMoveVertex(state, op) {
|
|
47
|
+
const { vertexIndex, previousPosition, featureId } = op
|
|
48
|
+
const feature = this.getFeature(featureId)
|
|
49
|
+
if (!feature) return
|
|
50
|
+
|
|
51
|
+
const geojson = feature.toGeoJSON()
|
|
52
|
+
const segments = getRingSegments(feature)
|
|
53
|
+
const result = getSegmentForIndex(segments, vertexIndex)
|
|
54
|
+
if (!result) return
|
|
55
|
+
|
|
56
|
+
const coords = getModifiableCoords(geojson, result.segment.path)
|
|
57
|
+
coords[result.localIdx] = previousPosition
|
|
58
|
+
this._applyUndoAndSync(state, geojson, featureId)
|
|
59
|
+
|
|
60
|
+
// Update touch vertex target position
|
|
61
|
+
const vertex = state.vertecies[state.selectedVertexIndex]
|
|
62
|
+
if (vertex) {
|
|
63
|
+
this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
|
|
64
|
+
}
|
|
65
|
+
},
|
|
66
|
+
|
|
67
|
+
undoInsertVertex(state, op) {
|
|
68
|
+
const { vertexIndex, featureId } = op
|
|
69
|
+
const feature = this.getFeature(featureId)
|
|
70
|
+
if (!feature) return
|
|
71
|
+
|
|
72
|
+
const geojson = feature.toGeoJSON()
|
|
73
|
+
const segments = getRingSegments(feature)
|
|
74
|
+
const result = getSegmentForIndex(segments, vertexIndex)
|
|
75
|
+
if (!result) return
|
|
76
|
+
|
|
77
|
+
const coords = getModifiableCoords(geojson, result.segment.path)
|
|
78
|
+
coords.splice(result.localIdx, 1)
|
|
79
|
+
this._applyUndoAndSync(state, geojson, featureId)
|
|
80
|
+
|
|
81
|
+
// Clear DirectSelect's coordinate selection
|
|
82
|
+
this.clearSelectedCoordinates()
|
|
83
|
+
this.hideTouchVertexIndicator(state)
|
|
84
|
+
this.changeMode(state, { selectedVertexIndex: -1, selectedVertexType: null })
|
|
85
|
+
},
|
|
86
|
+
|
|
87
|
+
undoDeleteVertex(state, op) {
|
|
88
|
+
const { vertexIndex, position, featureId } = op
|
|
89
|
+
const feature = this.getFeature(featureId)
|
|
90
|
+
if (!feature) {
|
|
91
|
+
return
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const geojson = feature.toGeoJSON()
|
|
95
|
+
const segments = getRingSegments(feature)
|
|
96
|
+
|
|
97
|
+
// Try to find segment containing vertexIndex
|
|
98
|
+
let result = getSegmentForIndex(segments, vertexIndex)
|
|
99
|
+
|
|
100
|
+
// If not found, vertex might be at segment boundary
|
|
101
|
+
if (!result) {
|
|
102
|
+
for (const seg of segments) {
|
|
103
|
+
if (vertexIndex === seg.start + seg.length) {
|
|
104
|
+
result = { segment: seg, localIdx: seg.length }
|
|
105
|
+
break
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!result) {
|
|
111
|
+
return
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
const coords = getModifiableCoords(geojson, result.segment.path)
|
|
115
|
+
coords.splice(result.localIdx, 0, position)
|
|
116
|
+
this._applyUndoAndSync(state, geojson, featureId)
|
|
117
|
+
|
|
118
|
+
// Update touch vertex target to restored vertex position
|
|
119
|
+
const vertex = state.vertecies[vertexIndex]
|
|
120
|
+
if (vertex) {
|
|
121
|
+
this.updateTouchVertexTarget(state, scalePoint(this.map.project(vertex), state.scale))
|
|
122
|
+
}
|
|
123
|
+
this.changeMode(state, { selectedVertexIndex: vertexIndex, selectedVertexType: 'vertex', coordPath: this.getCoordPath(state, vertexIndex) })
|
|
124
|
+
},
|
|
125
|
+
|
|
126
|
+
_applyUndoAndSync(state, geojson, featureId) {
|
|
127
|
+
this._ctx.api.add(geojson)
|
|
128
|
+
state.vertecies = this.getVerticies(featureId)
|
|
129
|
+
state.midpoints = this.getMidpoints(featureId)
|
|
130
|
+
this._ctx.store.render()
|
|
131
|
+
this.fireGeometryChange(state)
|
|
132
|
+
}
|
|
133
|
+
}
|