@defra/interactive-map 0.0.25-alpha → 0.0.27-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/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/map-style-config.md +30 -0
- package/docs/assets/css/docusaurus.scss +4 -0
- package/package.json +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/defaults.js +14 -14
- package/plugins/beta/draw-ml/src/mapboxDraw.js +5 -0
- package/plugins/beta/draw-ml/src/mapboxSnap.js +1 -1
- package/plugins/beta/draw-ml/src/modes/editVertex/touchHandlers.js +10 -2
- package/plugins/beta/draw-ml/src/modes/editVertexMode.js +2 -0
- package/plugins/beta/draw-ml/src/reducer.js +4 -1
- package/plugins/beta/draw-ml/src/styles.js +42 -39
- package/plugins/beta/draw-ol/dist/css/index.css +13 -0
- package/plugins/beta/draw-ol/dist/esm/DrawMode.js +1 -0
- package/plugins/beta/draw-ol/dist/esm/EditMode.js +1 -0
- package/plugins/beta/draw-ol/dist/esm/geometryHelpers.js +1 -0
- package/plugins/beta/draw-ol/dist/esm/im-draw-ol-plugin.js +1 -0
- package/plugins/beta/draw-ol/dist/esm/index.js +1 -0
- package/plugins/beta/draw-ol/src/DrawInit.jsx +67 -0
- package/plugins/beta/draw-ol/src/api/addFeature.js +29 -0
- package/plugins/beta/draw-ol/src/api/deleteFeature.js +11 -0
- package/plugins/beta/draw-ol/src/api/editFeature.js +50 -0
- package/plugins/beta/draw-ol/src/api/newLine.js +44 -0
- package/plugins/beta/draw-ol/src/api/newPolygon.js +44 -0
- package/plugins/beta/draw-ol/src/core/OLDrawManager.js +150 -0
- package/plugins/beta/draw-ol/src/core/featureStore.js +65 -0
- package/plugins/beta/draw-ol/src/core/styles.js +98 -0
- package/plugins/beta/draw-ol/src/core/undoStack.js +31 -0
- package/plugins/beta/draw-ol/src/defaults.js +16 -0
- package/plugins/beta/draw-ol/src/draw/DrawMode.js +113 -0
- package/plugins/beta/draw-ol/src/draw/drawInput.js +199 -0
- package/plugins/beta/draw-ol/src/draw.scss +17 -0
- package/plugins/beta/draw-ol/src/edit/EditMode.js +424 -0
- package/plugins/beta/draw-ol/src/edit/keyboardHandler.js +227 -0
- package/plugins/beta/draw-ol/src/edit/midpointLayer.js +57 -0
- package/plugins/beta/draw-ol/src/edit/touchHandler.js +171 -0
- package/plugins/beta/draw-ol/src/edit/undoOps.js +95 -0
- package/plugins/beta/draw-ol/src/edit/vertexHitTest.js +72 -0
- package/plugins/beta/draw-ol/src/edit/vertexLayer.js +49 -0
- package/plugins/beta/draw-ol/src/edit/vertexOps.js +99 -0
- package/plugins/beta/draw-ol/src/events.js +114 -0
- package/plugins/beta/draw-ol/src/index.js +12 -0
- package/plugins/beta/draw-ol/src/manifest.js +121 -0
- package/plugins/beta/draw-ol/src/olDraw.js +38 -0
- package/plugins/beta/draw-ol/src/reducer.js +41 -0
- package/plugins/beta/draw-ol/src/snap/snapEngine.js +109 -0
- package/plugins/beta/draw-ol/src/snap/snapGeometry.js +200 -0
- package/plugins/beta/draw-ol/src/snap/snapIndicator.js +81 -0
- package/plugins/beta/draw-ol/src/snap/snapInteraction.js +56 -0
- package/plugins/beta/draw-ol/src/snap/snapManager.js +90 -0
- package/plugins/beta/draw-ol/src/utils/flattenStyleProperties.js +47 -0
- package/plugins/beta/draw-ol/src/utils/geometryHelpers.js +97 -0
- package/plugins/beta/draw-ol/src/utils/olCoords.js +35 -0
- package/plugins/beta/draw-ol/src/utils/resolveColors.js +39 -0
- package/plugins/beta/draw-ol/src/utils/spatial.js +28 -0
- package/plugins/beta/draw-ol/src/utils/touchTarget.js +61 -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/defaults.js +1 -0
- package/plugins/interact/src/hooks/useInteractionHandlers.js +150 -43
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +240 -8
- package/plugins/interact/src/reducer.js +7 -0
- package/plugins/interact/src/reducer.test.js +16 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
- package/providers/beta/openlayers/dist/umd/index.js +1 -1
- package/providers/beta/openlayers/src/appEvents.js +9 -13
- package/providers/beta/openlayers/src/appEvents.test.js +27 -18
- package/providers/beta/openlayers/src/mapEvents.js +34 -20
- package/providers/beta/openlayers/src/mapEvents.test.js +65 -6
- package/providers/beta/openlayers/src/openlayersProvider.js +45 -26
- package/providers/beta/openlayers/src/openlayersProvider.test.js +12 -34
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +245 -0
- package/providers/beta/openlayers/src/utils/hoverCursor.js +65 -0
- package/providers/beta/openlayers/src/utils/hoverCursor.test.js +235 -0
- package/providers/beta/openlayers/src/utils/openLayersFixes.js +11 -0
- package/providers/beta/openlayers/src/utils/openLayersFixes.test.js +53 -0
- package/providers/beta/openlayers/src/utils/queryFeatures.js +77 -0
- package/providers/beta/openlayers/src/utils/queryFeatures.test.js +181 -0
- package/providers/beta/openlayers/src/utils/tileLayers.js +23 -5
- package/providers/beta/openlayers/src/utils/tileLayers.test.js +81 -5
- package/providers/beta/openlayers/src/utils/vtTileFragments.js +86 -0
- package/rollup.esm.mjs +6 -0
- package/src/App/controls/keyboardActions.js +2 -2
- package/src/InteractiveMap/InteractiveMap.js +38 -10
- package/src/InteractiveMap/InteractiveMap.test.js +282 -356
- package/src/InteractiveMap/historyManager.js +7 -0
- package/src/InteractiveMap/historyManager.test.js +11 -0
- package/webpack.dev.mjs +3 -1
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Programmatically start drawing a new line.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} context - plugin context
|
|
7
|
+
* @param {string} featureId - unique ID for this feature
|
|
8
|
+
* @param {object} options - { snapLayers, stroke, fill, strokeWidth, properties }
|
|
9
|
+
*/
|
|
10
|
+
export const newLine = (
|
|
11
|
+
{ appState, appConfig, mapState, pluginState, mapProvider, services },
|
|
12
|
+
featureId,
|
|
13
|
+
options = {}
|
|
14
|
+
) => {
|
|
15
|
+
const { dispatch } = pluginState
|
|
16
|
+
const { draw } = mapProvider
|
|
17
|
+
const { eventBus } = services
|
|
18
|
+
|
|
19
|
+
if (!draw) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
eventBus.emit('draw:started', { mode: 'draw_line' })
|
|
24
|
+
|
|
25
|
+
const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
|
|
26
|
+
const properties = {
|
|
27
|
+
...customProperties,
|
|
28
|
+
...flattenStyleProperties({ stroke, fill, strokeWidth })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
draw.changeMode('draw_line', {
|
|
32
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
33
|
+
interfaceType: appState.interfaceType,
|
|
34
|
+
addVertexButtonId: `${appConfig.id}-draw-add-point`,
|
|
35
|
+
featureId,
|
|
36
|
+
geometryType: 'LineString',
|
|
37
|
+
properties,
|
|
38
|
+
mapProvider,
|
|
39
|
+
crossHair: mapState.crossHair,
|
|
40
|
+
...modeOptions
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
dispatch({ type: 'SET_MODE', payload: 'draw_line' })
|
|
44
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { flattenStyleProperties } from '../utils/flattenStyleProperties.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Programmatically start drawing a new polygon.
|
|
5
|
+
*
|
|
6
|
+
* @param {object} context - plugin context
|
|
7
|
+
* @param {string} featureId - unique ID for this feature
|
|
8
|
+
* @param {object} options - { snapLayers, stroke, fill, strokeWidth, properties }
|
|
9
|
+
*/
|
|
10
|
+
export const newPolygon = (
|
|
11
|
+
{ appState, appConfig, mapState, pluginState, mapProvider, services },
|
|
12
|
+
featureId,
|
|
13
|
+
options = {}
|
|
14
|
+
) => {
|
|
15
|
+
const { dispatch } = pluginState
|
|
16
|
+
const { draw } = mapProvider
|
|
17
|
+
const { eventBus } = services
|
|
18
|
+
|
|
19
|
+
if (!draw) {
|
|
20
|
+
return
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
eventBus.emit('draw:started', { mode: 'draw_polygon' })
|
|
24
|
+
|
|
25
|
+
const { stroke, fill, strokeWidth, properties: customProperties, ...modeOptions } = options
|
|
26
|
+
const properties = {
|
|
27
|
+
...customProperties,
|
|
28
|
+
...flattenStyleProperties({ stroke, fill, strokeWidth })
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
draw.changeMode('draw_polygon', {
|
|
32
|
+
container: appState.layoutRefs.viewportRef.current,
|
|
33
|
+
interfaceType: appState.interfaceType,
|
|
34
|
+
addVertexButtonId: `${appConfig.id}-draw-add-point`,
|
|
35
|
+
featureId,
|
|
36
|
+
geometryType: 'Polygon',
|
|
37
|
+
properties,
|
|
38
|
+
mapProvider,
|
|
39
|
+
crossHair: mapState.crossHair,
|
|
40
|
+
...modeOptions
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
dispatch({ type: 'SET_MODE', payload: 'draw_polygon' })
|
|
44
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import VectorLayer from 'ol/layer/Vector.js'
|
|
2
|
+
import { createFeatureStore } from './featureStore.js'
|
|
3
|
+
import { createUndoStack } from './undoStack.js'
|
|
4
|
+
import { createStyles } from './styles.js'
|
|
5
|
+
import { resolveColors } from '../utils/resolveColors.js'
|
|
6
|
+
import { createSnapManager } from '../snap/snapManager.js'
|
|
7
|
+
import { DEFAULTS } from '../defaults.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Mode machine for the OL draw plugin.
|
|
11
|
+
*
|
|
12
|
+
* Owns the VectorSource/Layer, undo stack, and current mode instance.
|
|
13
|
+
* Exposes a minimal on/off/emit event bus for internal plugin communication
|
|
14
|
+
* (separate from the public eventBus used for consumer-facing events).
|
|
15
|
+
*
|
|
16
|
+
* Consumer-facing events are always emitted via eventBus by events.js after
|
|
17
|
+
* listening to the manager's internal events.
|
|
18
|
+
*/
|
|
19
|
+
export class OLDrawManager {
|
|
20
|
+
constructor (map, pluginConfig = {}) {
|
|
21
|
+
this._map = map
|
|
22
|
+
this._pluginConfig = pluginConfig
|
|
23
|
+
this._mode = 'disabled'
|
|
24
|
+
this._modeInstance = null
|
|
25
|
+
this._listeners = new Map()
|
|
26
|
+
|
|
27
|
+
this.store = createFeatureStore()
|
|
28
|
+
this.undoStack = createUndoStack((length) => this.emit('undochange', length))
|
|
29
|
+
|
|
30
|
+
this.colors = resolveColors(null, pluginConfig)
|
|
31
|
+
this.styles = createStyles(this.colors)
|
|
32
|
+
this.snap = createSnapManager(map, pluginConfig.snapLayers ?? null, this.colors, pluginConfig.snapRadius ?? DEFAULTS.snapRadius)
|
|
33
|
+
|
|
34
|
+
this._layer = new VectorLayer({
|
|
35
|
+
source: this.store.source,
|
|
36
|
+
style: this.styles.createFeatureStyle(),
|
|
37
|
+
zIndex: 100
|
|
38
|
+
})
|
|
39
|
+
this._layer.set('layerId', 'draw')
|
|
40
|
+
map.addLayer(this._layer)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// --- Color / style updates ---
|
|
44
|
+
|
|
45
|
+
setMapStyle (mapStyle) {
|
|
46
|
+
this.colors = resolveColors(mapStyle, this._pluginConfig)
|
|
47
|
+
this.styles = createStyles(this.colors)
|
|
48
|
+
this._layer.setStyle(this.styles.createFeatureStyle())
|
|
49
|
+
this.store.source.changed()
|
|
50
|
+
this.snap?.updateColors(this.colors)
|
|
51
|
+
this.emit('styleschanged', this.styles)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// --- Internal event bus ---
|
|
55
|
+
|
|
56
|
+
on (type, handler) {
|
|
57
|
+
if (!this._listeners.has(type)) {
|
|
58
|
+
this._listeners.set(type, new Set())
|
|
59
|
+
}
|
|
60
|
+
this._listeners.get(type).add(handler)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
off (type, handler) {
|
|
64
|
+
this._listeners.get(type)?.delete(handler)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
emit (type, detail) {
|
|
68
|
+
this._listeners.get(type)?.forEach(h => h(detail))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// --- Mode machine ---
|
|
72
|
+
|
|
73
|
+
async changeMode (modeName, options = {}) {
|
|
74
|
+
this._modeInstance?.destroy()
|
|
75
|
+
this._modeInstance = null
|
|
76
|
+
this._mode = modeName
|
|
77
|
+
|
|
78
|
+
const modeOptions = { ...options, snap: this.snap }
|
|
79
|
+
|
|
80
|
+
if (modeName === 'draw_polygon' || modeName === 'draw_line') {
|
|
81
|
+
const { createDrawMode } = await import('../draw/DrawMode.js')
|
|
82
|
+
this._modeInstance = createDrawMode({ map: this._map, manager: this, options: modeOptions })
|
|
83
|
+
} else if (modeName === 'edit_vertex') {
|
|
84
|
+
const { createEditMode } = await import('../edit/EditMode.js')
|
|
85
|
+
this._modeInstance = createEditMode({ map: this._map, manager: this, options: modeOptions })
|
|
86
|
+
} else {
|
|
87
|
+
// disabled — no mode instance needed
|
|
88
|
+
}
|
|
89
|
+
// Reattach snap interaction after mode's interactions are added so it
|
|
90
|
+
// processes pointermove first (OL: last-added interaction = first to handle events).
|
|
91
|
+
this.snap?.reattach()
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
getMode () {
|
|
95
|
+
return this._mode
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// --- High-level operations called by events.js ---
|
|
99
|
+
|
|
100
|
+
done () {
|
|
101
|
+
this._modeInstance?.done()
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
cancel () {
|
|
105
|
+
this._modeInstance?.cancel()
|
|
106
|
+
this.changeMode('disabled')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
undo () {
|
|
110
|
+
this._modeInstance?.undo()
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
deleteVertex () {
|
|
114
|
+
this._modeInstance?.deleteVertex()
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
setInterfaceType (type) {
|
|
118
|
+
this._modeInstance?.setInterfaceType?.(type)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// --- Feature store delegation ---
|
|
122
|
+
|
|
123
|
+
get (id) {
|
|
124
|
+
return this.store.get(id)
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
add (geojsonFeature) {
|
|
128
|
+
return this.store.add(geojsonFeature)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
delete (id) {
|
|
132
|
+
return this.store.remove(id)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
deleteAll () {
|
|
136
|
+
return this.store.clear()
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// --- Cleanup ---
|
|
140
|
+
|
|
141
|
+
remove () {
|
|
142
|
+
this._modeInstance?.destroy()
|
|
143
|
+
this._modeInstance = null
|
|
144
|
+
this.snap?.destroy()
|
|
145
|
+
this.snap = null
|
|
146
|
+
this.store.clear()
|
|
147
|
+
this._map.removeLayer(this._layer)
|
|
148
|
+
this._listeners.clear()
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import VectorSource from 'ol/source/Vector.js'
|
|
2
|
+
import GeoJSON from 'ol/format/GeoJSON.js'
|
|
3
|
+
|
|
4
|
+
const PROJECTION = 'EPSG:27700'
|
|
5
|
+
|
|
6
|
+
// No coordinate transformation: data and map both use BNG.
|
|
7
|
+
const format = new GeoJSON({ dataProjection: PROJECTION, featureProjection: PROJECTION })
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Wraps an OL VectorSource with a GeoJSON-oriented API keyed by feature ID.
|
|
11
|
+
* All GeoJSON in and out uses EPSG:27700 coordinates (BNG easting/northing).
|
|
12
|
+
*/
|
|
13
|
+
export const createFeatureStore = () => {
|
|
14
|
+
const source = new VectorSource()
|
|
15
|
+
|
|
16
|
+
return {
|
|
17
|
+
/** The underlying VectorSource, passed to OL interactions and layers. */
|
|
18
|
+
source,
|
|
19
|
+
|
|
20
|
+
/** Get the raw OL Feature by ID, or null. */
|
|
21
|
+
getOL (id) {
|
|
22
|
+
return source.getFeatureById(String(id)) ?? null
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
/** Add or replace a GeoJSON feature. Returns the OL Feature. */
|
|
26
|
+
add (geojsonFeature) {
|
|
27
|
+
const existing = this.getOL(geojsonFeature.id)
|
|
28
|
+
if (existing) {
|
|
29
|
+
source.removeFeature(existing)
|
|
30
|
+
}
|
|
31
|
+
const olFeature = format.readFeature(geojsonFeature)
|
|
32
|
+
source.addFeature(olFeature)
|
|
33
|
+
return olFeature
|
|
34
|
+
},
|
|
35
|
+
|
|
36
|
+
/** Get a GeoJSON feature by ID, or null. */
|
|
37
|
+
get (id) {
|
|
38
|
+
const feature = this.getOL(id)
|
|
39
|
+
return feature ? format.writeFeatureObject(feature) : null
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
/** Remove a feature by ID. */
|
|
43
|
+
remove (id) {
|
|
44
|
+
const feature = this.getOL(id)
|
|
45
|
+
if (feature) {
|
|
46
|
+
source.removeFeature(feature)
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
|
|
50
|
+
/** Remove all features. */
|
|
51
|
+
clear () {
|
|
52
|
+
source.clear()
|
|
53
|
+
},
|
|
54
|
+
|
|
55
|
+
/** Convert an OL Feature to a GeoJSON object. */
|
|
56
|
+
toGeoJSON (olFeature) {
|
|
57
|
+
return format.writeFeatureObject(olFeature)
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
/** Convert a GeoJSON object to an OL Feature (no side effects). */
|
|
61
|
+
fromGeoJSON (geojsonFeature) {
|
|
62
|
+
return format.readFeature(geojsonFeature)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
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
|
+
|
|
6
|
+
const selectedVertexRadii = { outer: 11, mid: 8, inner: 6 }
|
|
7
|
+
const selectedMidpointRadii = { outer: 9, mid: 6, inner: 4 }
|
|
8
|
+
|
|
9
|
+
const fillArc = (ctx, cx, cy, radius, fillStyle) => {
|
|
10
|
+
ctx.beginPath()
|
|
11
|
+
ctx.arc(cx, cy, radius, 0, Math.PI * 2)
|
|
12
|
+
ctx.fillStyle = fillStyle
|
|
13
|
+
ctx.fill()
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
// Custom renderer draws all arcs at the same (cx,cy) so concentric rings never
|
|
17
|
+
// drift at fractional CSS scales (e.g. 1.5×) the way separate drawImage calls can.
|
|
18
|
+
const makeRingRenderer = ({ outer, mid, inner }, colors, innerKey) => (pixelCoordinates, state) => {
|
|
19
|
+
const ctx = state.context
|
|
20
|
+
const pr = state.pixelRatio
|
|
21
|
+
const [cx, cy] = /** @type {number[]} */ (pixelCoordinates)
|
|
22
|
+
ctx.save()
|
|
23
|
+
fillArc(ctx, cx, cy, outer * pr, colors.editActive)
|
|
24
|
+
fillArc(ctx, cx, cy, mid * pr, colors.editHalo)
|
|
25
|
+
fillArc(ctx, cx, cy, inner * pr, colors[innerKey])
|
|
26
|
+
ctx.restore()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const capitalize = (s) => s.charAt(0).toUpperCase() + s.slice(1)
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Create all draw-ol style instances for the given resolved color set.
|
|
33
|
+
*
|
|
34
|
+
* @param {object} colors - Output of resolveColors()
|
|
35
|
+
* @returns {{ vertexStyle, selectedVertexStyle, midpointStyle, selectedMidpointStyle,
|
|
36
|
+
* editFeatureStyle, createSketchStyle, createFeatureStyle }}
|
|
37
|
+
*/
|
|
38
|
+
export const createStyles = (colors) => {
|
|
39
|
+
const vertexStyle = new Style({
|
|
40
|
+
image: new CircleStyle({
|
|
41
|
+
radius: 6,
|
|
42
|
+
fill: new Fill({ color: colors.editVertex })
|
|
43
|
+
})
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
const selectedVertexStyle = new Style({ renderer: makeRingRenderer(selectedVertexRadii, colors, 'editVertex') })
|
|
47
|
+
|
|
48
|
+
const midpointStyle = new Style({
|
|
49
|
+
image: new CircleStyle({
|
|
50
|
+
radius: 4,
|
|
51
|
+
fill: new Fill({ color: colors.editMidpoint })
|
|
52
|
+
})
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
const selectedMidpointStyle = new Style({ renderer: makeRingRenderer(selectedMidpointRadii, colors, 'editMidpoint') })
|
|
56
|
+
|
|
57
|
+
const editFeatureStyle = new Style({
|
|
58
|
+
stroke: new Stroke({ color: colors.editStroke, width: 2 }),
|
|
59
|
+
fill: new Fill({ color: colors.shapeFill })
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
const sketchLineStyle = new Style({
|
|
63
|
+
stroke: new Stroke({ color: colors.editStroke, width: 2 }),
|
|
64
|
+
fill: new Fill({ color: colors.shapeFill })
|
|
65
|
+
})
|
|
66
|
+
|
|
67
|
+
const sketchPointStyle = new Style({
|
|
68
|
+
image: new CircleStyle({
|
|
69
|
+
radius: 5,
|
|
70
|
+
fill: new Fill({ color: colors.editVertex })
|
|
71
|
+
})
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
const createSketchStyle = () => (feature) =>
|
|
75
|
+
feature.getGeometry().getType() === 'Point' ? [sketchPointStyle] : [sketchLineStyle]
|
|
76
|
+
|
|
77
|
+
const createFeatureStyle = () => (feature) => {
|
|
78
|
+
const p = feature.getProperties()
|
|
79
|
+
const id = colors.mapStyleId
|
|
80
|
+
const stroke = (id && p[`stroke${capitalize(id)}`]) || p.stroke || colors.shapeStroke
|
|
81
|
+
const fill = (id && p[`fill${capitalize(id)}`]) || p.fill || colors.shapeFill
|
|
82
|
+
const strokeWidth = p.strokeWidth || colors.strokeWidth
|
|
83
|
+
return [new Style({
|
|
84
|
+
stroke: new Stroke({ color: stroke, width: strokeWidth }),
|
|
85
|
+
fill: new Fill({ color: fill })
|
|
86
|
+
})]
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return {
|
|
90
|
+
vertexStyle,
|
|
91
|
+
selectedVertexStyle,
|
|
92
|
+
midpointStyle,
|
|
93
|
+
selectedMidpointStyle,
|
|
94
|
+
editFeatureStyle,
|
|
95
|
+
createSketchStyle,
|
|
96
|
+
createFeatureStyle
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Undo stack for draw operations.
|
|
3
|
+
* Calls onChange(length) whenever the stack changes so the UI can update.
|
|
4
|
+
*
|
|
5
|
+
* @param {(length: number) => void} onChange
|
|
6
|
+
*/
|
|
7
|
+
export const createUndoStack = (onChange) => {
|
|
8
|
+
const stack = []
|
|
9
|
+
|
|
10
|
+
return {
|
|
11
|
+
push (operation) {
|
|
12
|
+
stack.push(operation)
|
|
13
|
+
onChange(stack.length)
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
pop () {
|
|
17
|
+
const op = stack.pop()
|
|
18
|
+
onChange(stack.length)
|
|
19
|
+
return op
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
clear () {
|
|
23
|
+
stack.length = 0
|
|
24
|
+
onChange(stack.length)
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
get length () {
|
|
28
|
+
return stack.length
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export const DEFAULTS = {
|
|
2
|
+
editStroke: { light: '#1a65a6', dark: '#ffffff' },
|
|
3
|
+
editVertex: { light: '#1a65a6', dark: '#ffffff' },
|
|
4
|
+
editMidpoint: { light: '#1a65a6', dark: '#ffffff' },
|
|
5
|
+
editHalo: { light: '#ffffff', dark: 'rgba(11,12,12,1)' },
|
|
6
|
+
editActive: { light: '#000000', dark: '#ffffff' },
|
|
7
|
+
splitInvalid: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
|
|
8
|
+
splitValid: { light: 'rgba(29,112,184,1)', dark: 'rgba(29,112,184,1)' },
|
|
9
|
+
shapeStroke: '#1a65a6',
|
|
10
|
+
shapeFill: 'rgba(26,101,166,0.1)',
|
|
11
|
+
strokeWidth: 2,
|
|
12
|
+
snapVertex: 'rgba(212,53,28,0.5)',
|
|
13
|
+
snapMidpoint: 'rgba(40,161,151,1)',
|
|
14
|
+
snapEdge: 'rgba(29,112,184,0.5)',
|
|
15
|
+
snapRadius: 10
|
|
16
|
+
}
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
import Draw from 'ol/interaction/Draw.js'
|
|
2
|
+
import { createDrawInput } from './drawInput.js'
|
|
3
|
+
import { getCoords } from '../utils/geometryHelpers.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Draw mode — handles draw_polygon and draw_line.
|
|
7
|
+
*
|
|
8
|
+
* OL's Draw interaction handles all pointer/mouse behaviour natively.
|
|
9
|
+
* drawInput.js handles touch/keyboard/button input.
|
|
10
|
+
*
|
|
11
|
+
* @returns {{ done, cancel, undo, destroy }}
|
|
12
|
+
*/
|
|
13
|
+
export const createDrawMode = ({ map, manager, options }) => {
|
|
14
|
+
const {
|
|
15
|
+
geometryType, // 'Polygon' | 'LineString'
|
|
16
|
+
featureId,
|
|
17
|
+
properties = {},
|
|
18
|
+
container,
|
|
19
|
+
interfaceType,
|
|
20
|
+
addVertexButtonId,
|
|
21
|
+
mapProvider,
|
|
22
|
+
snap
|
|
23
|
+
} = options
|
|
24
|
+
|
|
25
|
+
const drawInteraction = new Draw({
|
|
26
|
+
type: geometryType,
|
|
27
|
+
style: manager.styles.createSketchStyle(),
|
|
28
|
+
stopClick: true,
|
|
29
|
+
// minPoints defaults: 3 for Polygon, 2 for LineString — OL handles this
|
|
30
|
+
// snapTolerance: how close to first point to auto-close polygon
|
|
31
|
+
snapTolerance: 12
|
|
32
|
+
})
|
|
33
|
+
map.addInteraction(drawInteraction)
|
|
34
|
+
|
|
35
|
+
// Track vertex count for the Done button enabled state
|
|
36
|
+
let sketchFeature = null
|
|
37
|
+
|
|
38
|
+
const updateVertexCount = () => {
|
|
39
|
+
if (!sketchFeature) {
|
|
40
|
+
return
|
|
41
|
+
}
|
|
42
|
+
const geom = sketchFeature.getGeometry()
|
|
43
|
+
const coords = getCoords({ type: geometryType, coordinates: geom.getCoordinates() })
|
|
44
|
+
// OL always keeps a trailing rubber-band coordinate; subtract 1
|
|
45
|
+
const numVertices = Math.max(0, coords.length - 1)
|
|
46
|
+
manager.emit('vertexchange', { numVertices })
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
drawInteraction.on('drawstart', (e) => {
|
|
50
|
+
sketchFeature = e.feature
|
|
51
|
+
sketchFeature.getGeometry().on('change', updateVertexCount)
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
drawInteraction.on('drawend', (e) => {
|
|
55
|
+
const olFeature = e.feature
|
|
56
|
+
olFeature.setId(String(featureId))
|
|
57
|
+
olFeature.setProperties(properties)
|
|
58
|
+
manager.store.source.addFeature(olFeature)
|
|
59
|
+
const geojson = manager.store.toGeoJSON(olFeature)
|
|
60
|
+
manager.emit('create', geojson)
|
|
61
|
+
// Mode switches to disabled in events.js after receiving 'create'
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
drawInteraction.on('drawabort', () => {
|
|
65
|
+
manager.emit('cancel')
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
const input = createDrawInput({
|
|
69
|
+
drawInteraction,
|
|
70
|
+
manager,
|
|
71
|
+
options: {
|
|
72
|
+
container,
|
|
73
|
+
interfaceType,
|
|
74
|
+
addVertexButtonId,
|
|
75
|
+
mapProvider,
|
|
76
|
+
snap,
|
|
77
|
+
onUndo: () => {
|
|
78
|
+
drawInteraction.removeLastPoint()
|
|
79
|
+
updateVertexCount()
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
})
|
|
83
|
+
|
|
84
|
+
return {
|
|
85
|
+
done () {
|
|
86
|
+
// Validate minimum points before finishing
|
|
87
|
+
if (sketchFeature) {
|
|
88
|
+
const geom = sketchFeature.getGeometry()
|
|
89
|
+
const coords = getCoords({ type: geometryType, coordinates: geom.getCoordinates() })
|
|
90
|
+
const min = geometryType === 'Polygon' ? 4 : 3 // +1 for rubber band
|
|
91
|
+
if (coords.length < min) {
|
|
92
|
+
return
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
drawInteraction.finishDrawing()
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
cancel () {
|
|
99
|
+
drawInteraction.abortDrawing()
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
undo () {
|
|
103
|
+
drawInteraction.removeLastPoint()
|
|
104
|
+
updateVertexCount()
|
|
105
|
+
},
|
|
106
|
+
|
|
107
|
+
destroy () {
|
|
108
|
+
input.destroy()
|
|
109
|
+
map.removeInteraction(drawInteraction)
|
|
110
|
+
sketchFeature = null
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|