@defra/interactive-map 0.0.36-alpha → 0.0.38-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/templates/draw-tools.njk +4 -3
- 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.md +32 -0
- package/docs/examples/draw-tools.mdx +0 -4
- package/docs/plugins/draw.md +482 -0
- package/docs/plugins.md +2 -6
- package/govuk-prototype-kit.config.json +3 -2
- package/package.json +2 -4
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-es/src/events.js +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-ol/dist/esm/im-draw-ol-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/css/index.css +1 -97
- package/plugins/datasets/dist/esm/esriLayerAdapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-esri-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/src/adapters/esri/esriLayerAdapter.js +13 -0
- package/plugins/draw/dist/esm/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-ol-adapter.js +1 -0
- package/plugins/draw/dist/esm/im-draw-plugin.js +1 -0
- package/plugins/draw/dist/esm/index.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ml-adapter.js +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +2 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js +2 -0
- package/plugins/draw/dist/umd/im-draw-plugin.js.LICENSE.txt +1 -0
- package/plugins/draw/dist/umd/index.js +2 -0
- package/plugins/draw/dist/umd/index.js.LICENSE.txt +1 -0
- package/plugins/draw/src/DrawInit.jsx +89 -0
- package/plugins/draw/src/DrawInit.test.jsx +184 -0
- package/plugins/draw/src/adapterEvents.js +127 -0
- package/plugins/draw/src/adapterEvents.test.js +20 -0
- package/plugins/draw/src/adapters/adapterContract.test.js +48 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.js +14 -0
- package/plugins/draw/src/adapters/loadDrawAdapter.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.js +367 -0
- package/plugins/draw/src/adapters/maplibre/MaplibreDrawAdapter.test.js +799 -0
- package/plugins/draw/src/adapters/maplibre/defaults.js +1 -0
- package/plugins/draw/src/adapters/maplibre/drawEvents.js +35 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.js +126 -0
- package/plugins/draw/src/adapters/maplibre/mapboxDraw.test.js +268 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.js +39 -0
- package/plugins/draw/src/adapters/maplibre/mapboxSnap.test.js +91 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.js +58 -0
- package/plugins/draw/src/adapters/maplibre/modes/createDrawMode.test.js +53 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.js +23 -0
- package/plugins/draw/src/adapters/maplibre/modes/disabledMode.test.js +21 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.js +15 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawLineMode.test.js +37 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/__helpers__/harness.js +115 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/clickHandlers.test.js +241 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/keyboardHandlers.test.js +75 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.js +78 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/lifecycle.test.js +48 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.js +103 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/pointerHandlers.test.js +127 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.js +59 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/renderHelpers.test.js +34 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.js +193 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawMode/undoHandlers.test.js +195 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.js +16 -0
- package/plugins/draw/src/adapters/maplibre/modes/drawPolygonMode.test.js +27 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/__helpers__/harness.js +107 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.js +139 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/geometryHelpers.test.js +46 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.js +2 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/helpers.test.js +17 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.js +151 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/keyboardHandlers.test.js +123 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.js +161 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/pointerHandlers.test.js +98 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/touchHandlers.test.js +105 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.js +166 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/undoHandlers.test.js +133 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.js +210 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexOperations.test.js +167 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.js +121 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode/vertexQueries.test.js +73 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.js +249 -0
- package/plugins/draw/src/adapters/maplibre/modes/editVertexMode.test.js +153 -0
- package/plugins/draw/src/adapters/maplibre/snap/constants.js +2 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.js +39 -0
- package/plugins/draw/src/adapters/maplibre/snap/mapHandlers.test.js +106 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.js +148 -0
- package/plugins/draw/src/adapters/maplibre/snap/prototypePatches.test.js +238 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.js +115 -0
- package/plugins/draw/src/adapters/maplibre/snap/snapInstance.test.js +151 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.js +34 -0
- package/plugins/draw/src/adapters/maplibre/snap/sourceData.test.js +62 -0
- package/plugins/draw/src/adapters/maplibre/styles.js +207 -0
- package/plugins/draw/src/adapters/maplibre/styles.test.js +155 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.js +202 -0
- package/plugins/draw/src/adapters/maplibre/utils/snapHelpers.test.js +253 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.js +57 -0
- package/plugins/draw/src/adapters/maplibre/utils/touchClickWorkaround.test.js +106 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.js +117 -0
- package/plugins/draw/src/adapters/openlayers/OLDrawAdapter.test.js +159 -0
- package/plugins/draw/src/adapters/openlayers/__helpers__/harness.js +90 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.js +178 -0
- package/plugins/draw/src/adapters/openlayers/core/OLDrawManager.test.js +153 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.js +69 -0
- package/plugins/draw/src/adapters/openlayers/core/featureStore.test.js +58 -0
- package/plugins/draw/src/adapters/openlayers/core/internalEvents.js +8 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.js +155 -0
- package/plugins/draw/src/adapters/openlayers/core/styles.test.js +143 -0
- package/plugins/draw/src/adapters/openlayers/defaults.js +1 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.js +272 -0
- package/plugins/draw/src/adapters/openlayers/draw/DrawMode.test.js +431 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.js +139 -0
- package/plugins/draw/src/adapters/openlayers/draw/drawInput.test.js +140 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.js +144 -0
- package/plugins/draw/src/adapters/openlayers/draw/vertexPlacement.test.js +196 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.js +385 -0
- package/plugins/draw/src/adapters/openlayers/edit/EditMode.test.js +303 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.js +55 -0
- package/plugins/draw/src/adapters/openlayers/edit/activeVertexLayer.test.js +53 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.js +157 -0
- package/plugins/draw/src/adapters/openlayers/edit/keyboardHandler.test.js +195 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.js +57 -0
- package/plugins/draw/src/adapters/openlayers/edit/midpointLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.js +83 -0
- package/plugins/draw/src/adapters/openlayers/edit/modifyInteraction.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.js +118 -0
- package/plugins/draw/src/adapters/openlayers/edit/nudge.test.js +165 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.js +79 -0
- package/plugins/draw/src/adapters/openlayers/edit/pointerHandlers.test.js +78 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.js +116 -0
- package/plugins/draw/src/adapters/openlayers/edit/selectionState.test.js +109 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.js +184 -0
- package/plugins/draw/src/adapters/openlayers/edit/touchHandler.test.js +133 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.js +94 -0
- package/plugins/draw/src/adapters/openlayers/edit/undoOps.test.js +64 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.js +74 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexHitTest.test.js +28 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.js +49 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexLayer.test.js +42 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.js +100 -0
- package/plugins/draw/src/adapters/openlayers/edit/vertexOps.test.js +80 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.js +40 -0
- package/plugins/draw/src/adapters/openlayers/olDraw.test.js +70 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.js +220 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapEngine.test.js +232 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.js +198 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapGeometry.test.js +136 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.js +81 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapIndicator.test.js +72 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.js +57 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapInteraction.test.js +68 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.js +99 -0
- package/plugins/draw/src/adapters/openlayers/snap/snapManager.test.js +101 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.js +103 -0
- package/plugins/draw/src/adapters/openlayers/utils/geometryHelpers.test.js +76 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.js +35 -0
- package/plugins/draw/src/adapters/openlayers/utils/olCoords.test.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.js +22 -0
- package/plugins/draw/src/adapters/openlayers/utils/sketchHelpers.test.js +18 -0
- package/plugins/draw/src/adapters/openlayers/utils/touchTarget.js +8 -0
- package/plugins/draw/src/api/addFeature.js +22 -0
- package/plugins/draw/src/api/addFeature.test.js +41 -0
- package/plugins/draw/src/api/deleteFeature.js +11 -0
- package/plugins/draw/src/api/deleteFeature.test.js +24 -0
- package/plugins/draw/src/api/editFeature.js +54 -0
- package/plugins/draw/src/api/editFeature.test.js +134 -0
- package/plugins/draw/src/api/merge.js +30 -0
- package/plugins/draw/src/api/merge.test.js +57 -0
- package/plugins/draw/src/api/newLine.js +41 -0
- package/plugins/draw/src/api/newLine.test.js +93 -0
- package/plugins/draw/src/api/newPolygon.js +41 -0
- package/plugins/draw/src/api/newPolygon.test.js +93 -0
- package/plugins/draw/src/api/split.js +117 -0
- package/plugins/draw/src/api/split.test.js +204 -0
- package/plugins/draw/src/defaults.js +52 -0
- package/plugins/draw/src/defaults.test.js +18 -0
- package/plugins/draw/src/draw.scss +43 -0
- package/plugins/draw/src/events.js +237 -0
- package/plugins/draw/src/events.test.js +369 -0
- package/plugins/draw/src/index.js +10 -0
- package/plugins/draw/src/index.test.js +24 -0
- package/plugins/draw/src/manifest.js +170 -0
- package/plugins/draw/src/manifest.test.js +147 -0
- package/plugins/draw/src/reducer.js +69 -0
- package/plugins/draw/src/reducer.test.js +85 -0
- package/plugins/draw/src/utils/debounce.js +16 -0
- package/plugins/draw/src/utils/debounce.test.js +40 -0
- package/plugins/draw/src/utils/eventBus.js +36 -0
- package/plugins/draw/src/utils/eventBus.test.js +47 -0
- package/plugins/draw/src/utils/flattenStyleProperties.js +25 -0
- package/plugins/draw/src/utils/flattenStyleProperties.test.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.js +33 -0
- package/plugins/draw/src/utils/getValueForStyle.test.js +32 -0
- package/plugins/draw/src/utils/resolveColors.js +39 -0
- package/plugins/draw/src/utils/resolveColors.test.js +34 -0
- package/plugins/draw/src/utils/spatial.js +243 -0
- package/plugins/draw/src/utils/spatial.test.js +243 -0
- package/plugins/draw/src/utils/touchTarget.js +95 -0
- package/plugins/draw/src/utils/touchTarget.test.js +107 -0
- package/plugins/draw/src/utils/undoStack.js +31 -0
- package/plugins/draw/src/utils/undoStack.test.js +49 -0
- package/plugins/draw/src/validation/liveDrawChecks.js +120 -0
- package/plugins/draw/src/validation/liveDrawChecks.test.js +159 -0
- package/plugins/draw/src/validation/liveStroke.js +85 -0
- package/plugins/draw/src/validation/liveStroke.test.js +150 -0
- package/plugins/draw/src/validation/rules.areaError.test.js +12 -0
- package/plugins/draw/src/validation/rules.js +186 -0
- package/plugins/draw/src/validation/rules.test.js +120 -0
- package/plugins/draw/src/validation/validateGeometry.js +119 -0
- package/plugins/draw/src/validation/validateGeometry.test.js +200 -0
- 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/hooks/useInteractionHandlers.js +1 -1
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +1 -1
- package/plugins/interact/src/utils/buildStylesMap.js +1 -0
- package/plugins/search/dist/css/index.css +1 -317
- package/rollup.esm.mjs +35 -28
- package/sonar-project.properties +2 -2
- package/src/App/components/Attributions/Attributions.module.scss +1 -0
- package/src/App/components/Hints/Hints.module.scss +5 -4
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +1 -1
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +11 -0
- package/src/App/components/MoveControl/MoveControl.jsx +73 -9
- package/src/App/components/MoveControl/MoveControl.module.scss +12 -2
- package/src/App/components/MoveControl/MoveControl.test.jsx +136 -0
- package/src/App/hooks/useHintsAPI.js +67 -0
- package/src/App/hooks/useHintsAPI.test.js +145 -0
- package/src/App/renderer/PluginInits.jsx +4 -0
- package/src/App/renderer/PluginInits.test.jsx +7 -1
- package/src/InteractiveMap/InteractiveMap.js +20 -0
- package/src/InteractiveMap/InteractiveMap.test.js +7 -0
- package/src/config/events.js +4 -0
- package/src/scss/settings/_dimensions.scss +3 -0
- package/src/services/announcer.js +38 -3
- package/src/services/announcer.test.js +133 -1
- package/src/services/hints.js +3 -0
- package/src/services/hints.test.js +19 -0
- package/src/types.js +12 -0
- package/src/utils/detectInterfaceType.js +5 -1
- package/src/utils/isMac.js +15 -0
- package/src/utils/isMac.test.js +37 -0
- package/webpack.umd.mjs +1 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import Style from 'ol/style/Style.js'
|
|
2
|
+
import Fill from 'ol/style/Fill.js'
|
|
3
|
+
import Stroke from 'ol/style/Stroke.js'
|
|
4
|
+
import CircleStyle from 'ol/style/Circle.js'
|
|
5
|
+
import MultiPoint from 'ol/geom/MultiPoint.js'
|
|
6
|
+
import { SIZES } from '../defaults.js'
|
|
7
|
+
import { getPlacedSketchCoords } from '../utils/sketchHelpers.js'
|
|
8
|
+
|
|
9
|
+
const HALO_RADIUS_OFFSET = 3
|
|
10
|
+
|
|
11
|
+
const selectedVertexRadii = { outer: SIZES.vertexHaloRadius + HALO_RADIUS_OFFSET, mid: SIZES.vertexHaloRadius, inner: SIZES.vertexRadius }
|
|
12
|
+
const selectedMidpointRadii = { outer: SIZES.midpointHaloRadius + HALO_RADIUS_OFFSET, mid: SIZES.midpointHaloRadius, inner: SIZES.midpointRadius }
|
|
13
|
+
|
|
14
|
+
const fillArc = (ctx, cx, cy, radius, fillStyle) => {
|
|
15
|
+
ctx.beginPath()
|
|
16
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2)
|
|
17
|
+
ctx.fillStyle = fillStyle
|
|
18
|
+
ctx.fill()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Custom renderer draws all arcs at the same (cx,cy) so concentric rings never
|
|
22
|
+
// drift at fractional CSS scales (e.g. 1.5×) the way separate drawImage calls can.
|
|
23
|
+
const makeRingRenderer = ({ outer, mid, inner }, colors, innerKey) => (pixelCoordinates, state) => {
|
|
24
|
+
const ctx = state.context
|
|
25
|
+
const pr = state.pixelRatio
|
|
26
|
+
const [cx, cy] = /** @type {number[]} */ (pixelCoordinates)
|
|
27
|
+
ctx.save()
|
|
28
|
+
fillArc(ctx, cx, cy, outer * pr, colors.editActive)
|
|
29
|
+
fillArc(ctx, cx, cy, mid * pr, colors.editHalo)
|
|
30
|
+
fillArc(ctx, cx, cy, inner * pr, colors[innerKey])
|
|
31
|
+
ctx.restore()
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1)
|
|
35
|
+
|
|
36
|
+
// Shared by edit-mode vertices and in-progress sketch vertices so they always look the same
|
|
37
|
+
const createVertexStyles = (colors) => {
|
|
38
|
+
const vertexImage = new CircleStyle({
|
|
39
|
+
radius: SIZES.vertexRadius,
|
|
40
|
+
fill: new Fill({ color: colors.editVertex })
|
|
41
|
+
})
|
|
42
|
+
return {
|
|
43
|
+
vertexImage,
|
|
44
|
+
vertexStyle: new Style({ image: vertexImage }),
|
|
45
|
+
selectedVertexStyle: new Style({ renderer: makeRingRenderer(selectedVertexRadii, colors, 'editVertex') })
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const createMidpointStyles = (colors) => ({
|
|
50
|
+
midpointStyle: new Style({
|
|
51
|
+
image: new CircleStyle({
|
|
52
|
+
radius: SIZES.midpointRadius,
|
|
53
|
+
fill: new Fill({ color: colors.editMidpoint })
|
|
54
|
+
})
|
|
55
|
+
}),
|
|
56
|
+
selectedMidpointStyle: new Style({ renderer: makeRingRenderer(selectedMidpointRadii, colors, 'editMidpoint') })
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
// Split-line preview colours: valid is solid, invalid is dashed — matching
|
|
60
|
+
// ML's stroke-valid-splitter / stroke-invalid-splitter layers.
|
|
61
|
+
const createSketchLineStyles = (colors) => ({
|
|
62
|
+
valid: new Style({
|
|
63
|
+
stroke: new Stroke({ color: colors.editStroke, width: 2 }),
|
|
64
|
+
fill: new Fill({ color: colors.editFill })
|
|
65
|
+
}),
|
|
66
|
+
invalid: new Style({
|
|
67
|
+
stroke: new Stroke({ color: colors.invalidStroke, width: 2, lineDash: [2, 4] })
|
|
68
|
+
}),
|
|
69
|
+
splitValid: new Style({
|
|
70
|
+
stroke: new Stroke({ color: colors.splitValid, width: 2 })
|
|
71
|
+
}),
|
|
72
|
+
splitInvalid: new Style({
|
|
73
|
+
stroke: new Stroke({ color: colors.splitInvalid, width: 2, lineDash: [2, 4] })
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Create all draw-ol style instances for the given resolved color set.
|
|
79
|
+
*
|
|
80
|
+
* @param {object} colors - Output of resolveColors()
|
|
81
|
+
* @returns {{ vertexStyle, selectedVertexStyle, midpointStyle, selectedMidpointStyle,
|
|
82
|
+
* editFeatureStyle, createSketchStyle, createFeatureStyle }}
|
|
83
|
+
*/
|
|
84
|
+
export const createStyles = (colors) => {
|
|
85
|
+
const { vertexImage, vertexStyle, selectedVertexStyle } = createVertexStyles(colors)
|
|
86
|
+
const { midpointStyle, selectedMidpointStyle } = createMidpointStyles(colors)
|
|
87
|
+
|
|
88
|
+
const editFeatureStyle = new Style({
|
|
89
|
+
stroke: new Stroke({ color: colors.editStroke, width: 2 }),
|
|
90
|
+
fill: new Fill({ color: colors.editFill })
|
|
91
|
+
})
|
|
92
|
+
|
|
93
|
+
// Dashed variant shown while the edited/drawn shape is invalid — no fill, so an
|
|
94
|
+
// invalid shape reads as an outline only.
|
|
95
|
+
const editFeatureStyleInvalid = new Style({
|
|
96
|
+
stroke: new Stroke({ color: colors.invalidStroke, width: 2, lineDash: [2, 4] })
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
const sketchLineStyles = createSketchLineStyles(colors)
|
|
100
|
+
|
|
101
|
+
// Reused across renders — the geometry function runs every frame while sketching,
|
|
102
|
+
// so mutate one MultiPoint (setCoordinates bumps its revision, keeping OL's
|
|
103
|
+
// render caches correct) instead of allocating a new one per frame
|
|
104
|
+
const sketchVertices = new MultiPoint([])
|
|
105
|
+
const sketchVertexStyle = new Style({
|
|
106
|
+
image: vertexImage,
|
|
107
|
+
geometry: (feature) => {
|
|
108
|
+
const coords = getPlacedSketchCoords(feature.getGeometry())
|
|
109
|
+
if (!coords.length) {
|
|
110
|
+
return null
|
|
111
|
+
}
|
|
112
|
+
sketchVertices.setCoordinates(coords)
|
|
113
|
+
return sketchVertices
|
|
114
|
+
}
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
// No style for the Point sketch (cursor-following ghost marker); placed
|
|
118
|
+
// vertices get markers on the sketch feature instead. geometryType filters
|
|
119
|
+
// out the extra LineString sketch OL renders alongside a Polygon sketch,
|
|
120
|
+
// so vertices aren't drawn twice. `invalid` swaps to the dashed line style.
|
|
121
|
+
// A 'splitter' property on the feature (set via setDrawingPreviewProperty)
|
|
122
|
+
// overrides both with the split-specific valid/invalid colours.
|
|
123
|
+
const createSketchStyle = (geometryType, invalid = false) => (feature) => {
|
|
124
|
+
const type = feature.getGeometry().getType()
|
|
125
|
+
if (type === 'Point') { return [] }
|
|
126
|
+
const splitter = feature.get('splitter')
|
|
127
|
+
let lineStyle = invalid ? sketchLineStyles.invalid : sketchLineStyles.valid
|
|
128
|
+
if (splitter === 'valid') { lineStyle = sketchLineStyles.splitValid }
|
|
129
|
+
if (splitter === 'invalid') { lineStyle = sketchLineStyles.splitInvalid }
|
|
130
|
+
return type === geometryType ? [lineStyle, sketchVertexStyle] : [lineStyle]
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const createFeatureStyle = () => (feature) => {
|
|
134
|
+
const p = feature.getProperties()
|
|
135
|
+
const id = colors.mapStyleId
|
|
136
|
+
const stroke = (id && p[`stroke${capitalize(id)}`]) || p.stroke || colors.shapeStroke
|
|
137
|
+
const fill = (id && p[`fill${capitalize(id)}`]) || p.fill || colors.shapeFill
|
|
138
|
+
const strokeWidth = p.strokeWidth || colors.strokeWidth
|
|
139
|
+
return [new Style({
|
|
140
|
+
stroke: new Stroke({ color: stroke, width: strokeWidth }),
|
|
141
|
+
fill: new Fill({ color: fill })
|
|
142
|
+
})]
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
return {
|
|
146
|
+
vertexStyle,
|
|
147
|
+
selectedVertexStyle,
|
|
148
|
+
midpointStyle,
|
|
149
|
+
selectedMidpointStyle,
|
|
150
|
+
editFeatureStyle,
|
|
151
|
+
editFeatureStyleInvalid,
|
|
152
|
+
createSketchStyle,
|
|
153
|
+
createFeatureStyle
|
|
154
|
+
}
|
|
155
|
+
}
|
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import Point from 'ol/geom/Point.js'
|
|
2
|
+
import LineString from 'ol/geom/LineString.js'
|
|
3
|
+
import Feature from 'ol/Feature.js'
|
|
4
|
+
import { createStyles } from './styles.js'
|
|
5
|
+
import { SIZES } from '../defaults.js'
|
|
6
|
+
import { polygonFeature, lineFeature } from '../__helpers__/harness.js'
|
|
7
|
+
|
|
8
|
+
const colors = {
|
|
9
|
+
editStroke: '#e5',
|
|
10
|
+
editFill: '#ef',
|
|
11
|
+
editVertex: '#ev',
|
|
12
|
+
editMidpoint: '#em',
|
|
13
|
+
editActive: '#ea',
|
|
14
|
+
editHalo: '#eh',
|
|
15
|
+
invalidStroke: '#is',
|
|
16
|
+
splitValid: '#sv',
|
|
17
|
+
splitInvalid: '#si',
|
|
18
|
+
shapeStroke: '#ss',
|
|
19
|
+
shapeFill: '#sf',
|
|
20
|
+
strokeWidth: 3,
|
|
21
|
+
mapStyleId: 'road'
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const styles = createStyles(colors)
|
|
25
|
+
|
|
26
|
+
// Records each canvas fill as { radius, fillStyle } so ring order/colours can be asserted
|
|
27
|
+
const fakeCanvas = () => {
|
|
28
|
+
const fills = []
|
|
29
|
+
const ctx = {
|
|
30
|
+
fillStyle: null,
|
|
31
|
+
save: jest.fn(),
|
|
32
|
+
restore: jest.fn(),
|
|
33
|
+
beginPath: jest.fn(),
|
|
34
|
+
arc: jest.fn((cx, cy, radius) => { ctx.lastArc = { cx, cy, radius } }),
|
|
35
|
+
fill: jest.fn(() => fills.push({ ...ctx.lastArc, fillStyle: ctx.fillStyle }))
|
|
36
|
+
}
|
|
37
|
+
return { ctx, fills }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
describe('handle styles', () => {
|
|
41
|
+
test('vertex and midpoint handles use the configured radii and colours', () => {
|
|
42
|
+
expect(styles.vertexStyle.getImage().getRadius()).toBe(SIZES.vertexRadius)
|
|
43
|
+
expect(styles.vertexStyle.getImage().getFill().getColor()).toBe(colors.editVertex)
|
|
44
|
+
expect(styles.midpointStyle.getImage().getRadius()).toBe(SIZES.midpointRadius)
|
|
45
|
+
expect(styles.midpointStyle.getImage().getFill().getColor()).toBe(colors.editMidpoint)
|
|
46
|
+
})
|
|
47
|
+
|
|
48
|
+
test('selected handles render three concentric rings in one canvas pass, scaled by pixelRatio', () => {
|
|
49
|
+
const { ctx, fills } = fakeCanvas()
|
|
50
|
+
styles.selectedVertexStyle.getRenderer()([10, 20], { context: ctx, pixelRatio: 2 })
|
|
51
|
+
expect(fills).toEqual([
|
|
52
|
+
{ cx: 10, cy: 20, radius: (SIZES.vertexHaloRadius + 3) * 2, fillStyle: colors.editActive },
|
|
53
|
+
{ cx: 10, cy: 20, radius: SIZES.vertexHaloRadius * 2, fillStyle: colors.editHalo },
|
|
54
|
+
{ cx: 10, cy: 20, radius: SIZES.vertexRadius * 2, fillStyle: colors.editVertex }
|
|
55
|
+
])
|
|
56
|
+
|
|
57
|
+
const midpoint = fakeCanvas()
|
|
58
|
+
styles.selectedMidpointStyle.getRenderer()([0, 0], { context: midpoint.ctx, pixelRatio: 1 })
|
|
59
|
+
expect(midpoint.fills.map(f => f.radius)).toEqual(
|
|
60
|
+
[SIZES.midpointHaloRadius + 3, SIZES.midpointHaloRadius, SIZES.midpointRadius])
|
|
61
|
+
expect(midpoint.fills[2].fillStyle).toBe(colors.editMidpoint)
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
test('the edited feature gets the edit stroke and fill', () => {
|
|
65
|
+
expect(styles.editFeatureStyle.getStroke().getColor()).toBe(colors.editStroke)
|
|
66
|
+
expect(styles.editFeatureStyle.getFill().getColor()).toBe(colors.editFill)
|
|
67
|
+
})
|
|
68
|
+
|
|
69
|
+
test('the invalid edited feature gets a dashed stroke and no fill', () => {
|
|
70
|
+
expect(styles.editFeatureStyleInvalid.getStroke().getColor()).toBe(colors.invalidStroke)
|
|
71
|
+
expect(styles.editFeatureStyleInvalid.getStroke().getLineDash()).toEqual([2, 4])
|
|
72
|
+
expect(styles.editFeatureStyleInvalid.getFill()).toBeNull()
|
|
73
|
+
})
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
describe('sketch styles while drawing', () => {
|
|
77
|
+
const polygonStyleFn = styles.createSketchStyle('Polygon')
|
|
78
|
+
|
|
79
|
+
test('the cursor-follow point renders nothing; the companion line gets stroke only', () => {
|
|
80
|
+
expect(polygonStyleFn(new Feature(new Point([0, 0])))).toEqual([])
|
|
81
|
+
expect(polygonStyleFn(new Feature(new LineString([[0, 0], [1, 1]])))).toHaveLength(1)
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
test('the invalid sketch renders a dashed line in the invalid colour with no fill', () => {
|
|
85
|
+
const invalidStyleFn = styles.createSketchStyle('Polygon', true)
|
|
86
|
+
const [lineStyle] = invalidStyleFn(new Feature(new LineString([[0, 0], [1, 1]])))
|
|
87
|
+
expect(lineStyle.getStroke().getColor()).toBe(colors.invalidStroke)
|
|
88
|
+
expect(lineStyle.getStroke().getLineDash()).toEqual([2, 4])
|
|
89
|
+
expect(lineStyle.getFill()).toBeNull()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('a splitter-tagged sketch renders the split colours (valid solid, invalid dashed), overriding invalid', () => {
|
|
93
|
+
const valid = lineFeature([[0, 0], [1, 1]])
|
|
94
|
+
valid.set('splitter', 'valid')
|
|
95
|
+
const [validStyle] = styles.createSketchStyle('LineString')(valid)
|
|
96
|
+
expect(validStyle.getStroke().getColor()).toBe(colors.splitValid)
|
|
97
|
+
expect(validStyle.getStroke().getLineDash()).toBeNull()
|
|
98
|
+
|
|
99
|
+
const invalid = lineFeature([[0, 0], [1, 1]])
|
|
100
|
+
invalid.set('splitter', 'invalid')
|
|
101
|
+
const [invalidStyle] = styles.createSketchStyle('LineString', true)(invalid)
|
|
102
|
+
expect(invalidStyle.getStroke().getColor()).toBe(colors.splitInvalid)
|
|
103
|
+
expect(invalidStyle.getStroke().getLineDash()).toEqual([2, 4])
|
|
104
|
+
})
|
|
105
|
+
|
|
106
|
+
test('placed vertices render with the shared vertex image on a reused MultiPoint', () => {
|
|
107
|
+
const sketch = polygonFeature([[0, 0], [10, 0], [5, 5], [0, 0]]) // 2 placed + rubber + closing
|
|
108
|
+
const [, vertexStyle] = polygonStyleFn(sketch)
|
|
109
|
+
expect(vertexStyle.getImage()).toBe(styles.vertexStyle.getImage())
|
|
110
|
+
const geometry = vertexStyle.getGeometry()(sketch)
|
|
111
|
+
expect(geometry.getCoordinates()).toEqual([[0, 0], [10, 0]])
|
|
112
|
+
expect(vertexStyle.getGeometry()(sketch)).toBe(geometry) // instance reused per render
|
|
113
|
+
|
|
114
|
+
const empty = lineFeature([[9, 9]]) // rubber band only — nothing placed
|
|
115
|
+
expect(styles.createSketchStyle('LineString')(empty)[1].getGeometry()(empty)).toBeNull()
|
|
116
|
+
})
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
describe('createFeatureStyle (inactive shapes)', () => {
|
|
120
|
+
const styleFor = (properties) => styles.createFeatureStyle()({ getProperties: () => properties })[0]
|
|
121
|
+
|
|
122
|
+
test('falls back to the configured shape colours', () => {
|
|
123
|
+
const style = styleFor({})
|
|
124
|
+
expect(style.getStroke().getColor()).toBe(colors.shapeStroke)
|
|
125
|
+
expect(style.getStroke().getWidth()).toBe(colors.strokeWidth)
|
|
126
|
+
expect(style.getFill().getColor()).toBe(colors.shapeFill)
|
|
127
|
+
})
|
|
128
|
+
|
|
129
|
+
test('per-feature properties override, and map-style-specific properties win overall', () => {
|
|
130
|
+
const style = styleFor({ stroke: '#f1', fill: '#f2', strokeWidth: 7, strokeRoad: '#r1', fillRoad: '#r2' })
|
|
131
|
+
expect(style.getStroke().getColor()).toBe('#r1')
|
|
132
|
+
expect(style.getFill().getColor()).toBe('#r2')
|
|
133
|
+
expect(style.getStroke().getWidth()).toBe(7)
|
|
134
|
+
|
|
135
|
+
expect(styleFor({ stroke: '#f1' }).getStroke().getColor()).toBe('#f1')
|
|
136
|
+
})
|
|
137
|
+
|
|
138
|
+
test('without a map style id only the generic properties apply', () => {
|
|
139
|
+
const plain = createStyles({ ...colors, mapStyleId: null })
|
|
140
|
+
const style = plain.createFeatureStyle()({ getProperties: () => ({ strokeRoad: '#r1' }) })[0]
|
|
141
|
+
expect(style.getStroke().getColor()).toBe(colors.shapeStroke)
|
|
142
|
+
})
|
|
143
|
+
})
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { COLORS, SIZES, TOLERANCES, KEYBOARD, MAP_SIZE_SCALES } from '../../defaults.js'
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
import Draw from 'ol/interaction/Draw.js'
|
|
2
|
+
import { noModifierKeys } from 'ol/events/condition.js'
|
|
3
|
+
import { createDrawInput } from './drawInput.js'
|
|
4
|
+
import { getPlacedSketchCoords, getLastPlacedSketchCoord } from '../utils/sketchHelpers.js'
|
|
5
|
+
import { TOLERANCES } from '../defaults.js'
|
|
6
|
+
import { ADAPTER_EVENTS } from '../../../adapterEvents.js'
|
|
7
|
+
import { STYLES_CHANGED_EVENT } from '../core/internalEvents.js'
|
|
8
|
+
import { attemptPlacement, MODE_BY_GEOMETRY } from '../../../validation/validateGeometry.js'
|
|
9
|
+
import { MIN_VERTICES } from '../../../validation/rules.js'
|
|
10
|
+
import { createLiveStroke } from '../../../validation/liveStroke.js'
|
|
11
|
+
import { createLiveDrawChecks } from '../../../validation/liveDrawChecks.js'
|
|
12
|
+
|
|
13
|
+
const canFinish = (geometryType, sketchFeature) => {
|
|
14
|
+
if (!sketchFeature) { return false }
|
|
15
|
+
return getPlacedSketchCoords(sketchFeature.getGeometry()).length >= MIN_VERTICES[geometryType]
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const DUPLICATE_TOLERANCE_PX = 2
|
|
19
|
+
|
|
20
|
+
// Blocks clicks with modifier keys, vetoed placements (the vertex never appears —
|
|
21
|
+
// see canPlaceVertex in createDrawMode), and duplicate clicks on the last placed
|
|
22
|
+
// vertex while the shape is not yet finishable (once finishable, clicking the last
|
|
23
|
+
// vertex is OL's finish gesture).
|
|
24
|
+
export const buildCondition = (map, geometryType, getSketchFeature, canPlaceVertex) => (e) => {
|
|
25
|
+
if (!noModifierKeys(e)) { return false }
|
|
26
|
+
const sf = getSketchFeature()
|
|
27
|
+
if (!canPlaceVertex(e.coordinate)) { return false }
|
|
28
|
+
if (!sf || canFinish(geometryType, sf)) { return true }
|
|
29
|
+
const prev = getLastPlacedSketchCoord(sf.getGeometry())
|
|
30
|
+
if (!prev) { return true }
|
|
31
|
+
const pp = map.getPixelFromCoordinate(prev)
|
|
32
|
+
if (!pp) { return true }
|
|
33
|
+
const dx = e.pixel[0] - pp[0]; const dy = e.pixel[1] - pp[1]
|
|
34
|
+
return dx * dx + dy * dy > DUPLICATE_TOLERANCE_PX * DUPLICATE_TOLERANCE_PX
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// During drawing the sketch has trailing rubber-band (+ closing, for polygons) coords.
|
|
38
|
+
const TRAILING_COORDS = { Polygon: 2, LineString: 1 }
|
|
39
|
+
|
|
40
|
+
// The placed-only GeoJSON of an in-progress sketch (lon/lat), dropping the trailing
|
|
41
|
+
// cursor-tracking coords so validation tests just the committed vertices.
|
|
42
|
+
const placedFeatureGeoJSON = (store, sketchFeature) => {
|
|
43
|
+
const gj = store.toGeoJSON(sketchFeature)
|
|
44
|
+
const type = gj.geometry.type
|
|
45
|
+
const trailing = TRAILING_COORDS[type]
|
|
46
|
+
const ring = type === 'Polygon' ? gj.geometry.coordinates[0] : gj.geometry.coordinates
|
|
47
|
+
const placed = ring.slice(0, -trailing)
|
|
48
|
+
const geometry = type === 'Polygon'
|
|
49
|
+
? { type: 'Polygon', coordinates: [placed] }
|
|
50
|
+
: { type: 'LineString', coordinates: placed }
|
|
51
|
+
return { type: 'Feature', geometry, properties: gj.properties }
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Attempt a placement (mouse click, crosshair tap, Enter) through the shared
|
|
55
|
+
// hard rules + user callback. On a veto the vertex never appears and a
|
|
56
|
+
// PLACEMENT_BLOCKED event carries the reason.
|
|
57
|
+
export const buildCanPlaceVertex = ({ manager, geometryType, getSketch }) => (coordinate) => {
|
|
58
|
+
const sketch = getSketch()
|
|
59
|
+
const result = attemptPlacement({
|
|
60
|
+
placed: sketch ? getPlacedSketchCoords(sketch.getGeometry()) : [],
|
|
61
|
+
point: coordinate,
|
|
62
|
+
geometryType,
|
|
63
|
+
onGeometryChange: manager._geometryValidator
|
|
64
|
+
})
|
|
65
|
+
if (!result.valid) {
|
|
66
|
+
manager.emit(ADAPTER_EVENTS.PLACEMENT_BLOCKED, result.blocked)
|
|
67
|
+
}
|
|
68
|
+
return result.valid
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Build the displayed-geometry payload (placed vertices + cursor) for the live
|
|
72
|
+
// stroke check from the current sketch, plus the placed-vertex count for the
|
|
73
|
+
// minimum-vertices threshold.
|
|
74
|
+
const displayedSketch = (geometryType, sketch) => {
|
|
75
|
+
const geom = sketch.getGeometry()
|
|
76
|
+
const coords = geom.getCoordinates()
|
|
77
|
+
const ring = geometryType === 'Polygon' ? (coords[0] ?? []) : coords
|
|
78
|
+
const geometry = geometryType === 'Polygon'
|
|
79
|
+
? { type: 'Polygon', coordinates: [ring] }
|
|
80
|
+
: { type: 'LineString', coordinates: ring }
|
|
81
|
+
return { feature: { type: 'Feature', geometry }, numVertices: getPlacedSketchCoords(geom).length }
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Commit a finished sketch to the store under the requested id and emit CREATE.
|
|
85
|
+
const finalizeDrawnFeature = (manager, olFeature, featureId, properties) => {
|
|
86
|
+
olFeature.setId(String(featureId))
|
|
87
|
+
olFeature.setProperties(properties)
|
|
88
|
+
manager.store.source.addFeature(olFeature)
|
|
89
|
+
manager.emit(ADAPTER_EVENTS.CREATE, manager.store.toGeoJSON(olFeature))
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Wire the OL Draw interaction's lifecycle: track the sketch, react to every sketch
|
|
93
|
+
// geometry change (vertex count + live validity), finalise on end, and report a
|
|
94
|
+
// cancel on abort.
|
|
95
|
+
const attachDrawListeners = (drawInteraction, { manager, featureId, properties, onStart, onSketchChange }) => {
|
|
96
|
+
drawInteraction.on('drawstart', (e) => {
|
|
97
|
+
onStart(e.feature)
|
|
98
|
+
e.feature.getGeometry().on('change', onSketchChange)
|
|
99
|
+
})
|
|
100
|
+
drawInteraction.on('drawend', (e) => finalizeDrawnFeature(manager, e.feature, featureId, properties))
|
|
101
|
+
drawInteraction.on('drawabort', () => { manager.emit(ADAPTER_EVENTS.CANCEL) })
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Tracks placed-vertex count and emits VERTEX_CHANGE plus, on each genuine placement,
|
|
105
|
+
// a commit-level GEOMETRY_CHANGE ('commit-add') for validation. Deferred a tick so a
|
|
106
|
+
// rejection's revert runs after the current click settles.
|
|
107
|
+
const createVertexTracker = (manager, getSketch) => {
|
|
108
|
+
let lastPlacedCount = 0
|
|
109
|
+
const emit = (phase, vertexIndex) => {
|
|
110
|
+
setTimeout(() => {
|
|
111
|
+
const sketch = getSketch()
|
|
112
|
+
if (!sketch) { return }
|
|
113
|
+
manager.emit(ADAPTER_EVENTS.GEOMETRY_CHANGE, { feature: placedFeatureGeoJSON(manager.store, sketch), phase, vertexIndex })
|
|
114
|
+
}, 0)
|
|
115
|
+
}
|
|
116
|
+
return {
|
|
117
|
+
resetCount: () => { lastPlacedCount = 0 },
|
|
118
|
+
updateVertexCount: () => {
|
|
119
|
+
const sketch = getSketch()
|
|
120
|
+
if (!sketch) { return }
|
|
121
|
+
const placed = getPlacedSketchCoords(sketch.getGeometry()).length
|
|
122
|
+
manager.emit(ADAPTER_EVENTS.VERTEX_CHANGE, { numVertices: placed })
|
|
123
|
+
if (placed > lastPlacedCount) { emit('commit-add', placed - 1) }
|
|
124
|
+
lastPlacedCount = placed
|
|
125
|
+
},
|
|
126
|
+
// An undo commits a vertex removal, so it must re-validate like any other
|
|
127
|
+
// commit — otherwise the Done gate goes stale.
|
|
128
|
+
emitUndoValidation: () => {
|
|
129
|
+
const sketch = getSketch()
|
|
130
|
+
if (!sketch) { return }
|
|
131
|
+
emit('commit-delete', getPlacedSketchCoords(sketch.getGeometry()).length)
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// The mode interface consumed by OLDrawManager.
|
|
137
|
+
const buildDrawModeApi = ({ map, manager, drawInteraction, input, geometryType, getSketch, updateVertexCount, emitUndoValidation, onStylesChanged, clearSketch, setInvalid, liveStroke, liveDrawChecks }) => ({
|
|
138
|
+
done () {
|
|
139
|
+
if (canFinish(geometryType, getSketch())) { drawInteraction.finishDrawing() }
|
|
140
|
+
},
|
|
141
|
+
cancel () { drawInteraction.abortDrawing() },
|
|
142
|
+
undo () { drawInteraction.removeLastPoint(); updateVertexCount(); emitUndoValidation() },
|
|
143
|
+
setInvalid,
|
|
144
|
+
setInterfaceType (type) { input.setInterfaceType(type) },
|
|
145
|
+
// Tags the sketch feature so createSketchStyle can pick the split-specific
|
|
146
|
+
// colour (see styles.js); no-op before any sketch exists.
|
|
147
|
+
setDrawingPreviewProperty (property, value) {
|
|
148
|
+
const sketch = getSketch()
|
|
149
|
+
if (!sketch) { return }
|
|
150
|
+
sketch.set(property, value)
|
|
151
|
+
drawInteraction.overlay_.changed()
|
|
152
|
+
},
|
|
153
|
+
destroy () {
|
|
154
|
+
liveStroke.destroy()
|
|
155
|
+
liveDrawChecks.destroy()
|
|
156
|
+
manager.off(STYLES_CHANGED_EVENT, onStylesChanged)
|
|
157
|
+
// Emit the final interfaceType so crosshair visibility is correct on exit.
|
|
158
|
+
manager.emit(ADAPTER_EVENTS.INTERFACE_TYPE_CHANGE, { interfaceType: input.getInterfaceType() })
|
|
159
|
+
input.destroy()
|
|
160
|
+
map.removeInteraction(drawInteraction)
|
|
161
|
+
clearSketch()
|
|
162
|
+
}
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
// Build the touch/keyboard draw input for the interaction.
|
|
166
|
+
const buildDrawInput = ({ drawInteraction, options, geometryType, getSketch, updateVertexCount, emitUndoValidation, canPlaceVertex }) =>
|
|
167
|
+
createDrawInput({
|
|
168
|
+
drawInteraction,
|
|
169
|
+
options: {
|
|
170
|
+
container: options.container,
|
|
171
|
+
interfaceType: options.interfaceType,
|
|
172
|
+
addVertexButtonId: options.addVertexButtonId,
|
|
173
|
+
mapProvider: options.mapProvider,
|
|
174
|
+
snap: options.snap,
|
|
175
|
+
onUndo: () => { drawInteraction.removeLastPoint(); updateVertexCount(); emitUndoValidation() },
|
|
176
|
+
canFinish: () => canFinish(geometryType, getSketch()),
|
|
177
|
+
canPlace: canPlaceVertex
|
|
178
|
+
}
|
|
179
|
+
})
|
|
180
|
+
|
|
181
|
+
export const createDrawMode = ({ map, manager, options }) => {
|
|
182
|
+
const { geometryType, featureId, properties = {} } = options
|
|
183
|
+
|
|
184
|
+
let sketchFeature = null
|
|
185
|
+
let invalid = false
|
|
186
|
+
let currentSketchStyle = manager.styles.createSketchStyle(geometryType)
|
|
187
|
+
const { updateVertexCount, resetCount, emitUndoValidation } = createVertexTracker(manager, () => sketchFeature)
|
|
188
|
+
|
|
189
|
+
const canPlaceVertex = buildCanPlaceVertex({ manager, geometryType, getSketch: () => sketchFeature })
|
|
190
|
+
|
|
191
|
+
const drawInteraction = new Draw({
|
|
192
|
+
type: geometryType,
|
|
193
|
+
style: (feature) => currentSketchStyle(feature),
|
|
194
|
+
stopClick: true,
|
|
195
|
+
snapTolerance: TOLERANCES.snapRadius,
|
|
196
|
+
condition: buildCondition(map, geometryType, () => sketchFeature, canPlaceVertex),
|
|
197
|
+
// Block finish gestures (double-click / click-to-close) while the shape is invalid.
|
|
198
|
+
finishCondition: () => manager._geometryValid !== false
|
|
199
|
+
})
|
|
200
|
+
map.addInteraction(drawInteraction)
|
|
201
|
+
|
|
202
|
+
const setSketchInvalid = (next) => {
|
|
203
|
+
invalid = next
|
|
204
|
+
currentSketchStyle = manager.styles.createSketchStyle(geometryType, invalid)
|
|
205
|
+
drawInteraction.overlay_.changed()
|
|
206
|
+
}
|
|
207
|
+
// liveStroke stays a real createLiveStroke instance (not just a set() sink)
|
|
208
|
+
// since edit mode drives its own separate instance the same way — kept here
|
|
209
|
+
// for consistency with that pattern, even though draw mode only ever calls
|
|
210
|
+
// set() on it (see liveDrawChecks.js, which is flip-guarded itself). There's
|
|
211
|
+
// no equivalent cross-mode instance for Add-point, so that verdict emits directly.
|
|
212
|
+
const liveStroke = createLiveStroke({ onChange: setSketchInvalid })
|
|
213
|
+
// Computes both verdicts from a SINGLE throttled call to the user's callback
|
|
214
|
+
// per sketch change (see liveDrawChecks.js for why that matters).
|
|
215
|
+
const liveDrawChecks = createLiveDrawChecks({
|
|
216
|
+
onStrokeChange: (nextInvalid, reason) => liveStroke.set(nextInvalid, reason),
|
|
217
|
+
onPlaceChange: (vetoed, reason) => manager.emit(ADAPTER_EVENTS.CAN_PLACE_CHANGE, { canPlace: !vetoed, reason })
|
|
218
|
+
})
|
|
219
|
+
const liveMode = MODE_BY_GEOMETRY[geometryType]
|
|
220
|
+
const updateLiveValidity = () => {
|
|
221
|
+
if (!sketchFeature) { return }
|
|
222
|
+
const { feature, numVertices } = displayedSketch(geometryType, sketchFeature)
|
|
223
|
+
liveDrawChecks.update({ feature, context: { mode: liveMode }, numVertices, onGeometryChange: manager._geometryValidator })
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
// Update sketch style when map style changes
|
|
227
|
+
const onStylesChanged = () => {
|
|
228
|
+
currentSketchStyle = manager.styles.createSketchStyle(geometryType, invalid)
|
|
229
|
+
drawInteraction.overlay_.changed()
|
|
230
|
+
}
|
|
231
|
+
manager.on(STYLES_CHANGED_EVENT, onStylesChanged)
|
|
232
|
+
// OL internal: overlay_ is the private VectorLayer used for the sketch geometry.
|
|
233
|
+
// updateWhileAnimating_ forces per-frame redraws during view animations (keyboard pan).
|
|
234
|
+
// Without this, geom.setCoordinates() calls are ignored while the ANIMATING hint is set.
|
|
235
|
+
// Check ol/interaction/Draw.js and ol/layer/BaseVector.js if this breaks after an OL upgrade.
|
|
236
|
+
drawInteraction.overlay_.updateWhileAnimating_ = true
|
|
237
|
+
|
|
238
|
+
attachDrawListeners(drawInteraction, {
|
|
239
|
+
manager,
|
|
240
|
+
featureId,
|
|
241
|
+
properties,
|
|
242
|
+
onStart: (f) => { sketchFeature = f; resetCount() },
|
|
243
|
+
onSketchChange: () => { updateVertexCount(); updateLiveValidity() }
|
|
244
|
+
})
|
|
245
|
+
|
|
246
|
+
const input = buildDrawInput({
|
|
247
|
+
drawInteraction,
|
|
248
|
+
options,
|
|
249
|
+
geometryType,
|
|
250
|
+
getSketch: () => sketchFeature,
|
|
251
|
+
updateVertexCount,
|
|
252
|
+
emitUndoValidation,
|
|
253
|
+
canPlaceVertex
|
|
254
|
+
})
|
|
255
|
+
|
|
256
|
+
return buildDrawModeApi({
|
|
257
|
+
map,
|
|
258
|
+
manager,
|
|
259
|
+
drawInteraction,
|
|
260
|
+
input,
|
|
261
|
+
geometryType,
|
|
262
|
+
getSketch: () => sketchFeature,
|
|
263
|
+
updateVertexCount,
|
|
264
|
+
emitUndoValidation,
|
|
265
|
+
onStylesChanged,
|
|
266
|
+
clearSketch: () => { sketchFeature = null },
|
|
267
|
+
// External writes go through the controller so its cache mirrors the style.
|
|
268
|
+
setInvalid: (next) => liveStroke.set(next),
|
|
269
|
+
liveStroke,
|
|
270
|
+
liveDrawChecks
|
|
271
|
+
})
|
|
272
|
+
}
|