@defra/interactive-map 0.0.50-alpha → 0.0.51-apha
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/umd/im-core.js +1 -1
- package/docs/api.md +19 -0
- package/docs/architecture.md +14 -0
- package/docs/getting-started.md +10 -0
- package/docs/plugins/datasets.md +9 -3
- package/jest.setup.js +8 -0
- package/package.json +8 -3
- package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/esm/im-datasets-ol-adapter.js +1 -0
- package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
- package/plugins/datasets/dist/umd/im-datasets-ol-adapter.js +2 -0
- package/plugins/datasets/dist/umd/im-datasets-ol-adapter.js.LICENSE.txt +1 -0
- package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
- package/plugins/datasets/dist/umd/index.js +1 -1
- package/plugins/datasets/src/adapters/loadLayerAdapter.js +7 -3
- package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +17 -0
- package/plugins/datasets/src/adapters/mapboxStyleLayerAdapter.js +74 -0
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +14 -79
- package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +4 -0
- package/plugins/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +3 -89
- package/plugins/datasets/src/adapters/openlayers/canvasPatternStyle.js +111 -0
- package/plugins/datasets/src/adapters/openlayers/canvasPatternStyle.test.js +198 -0
- package/plugins/datasets/src/adapters/openlayers/layerBuilders.js +205 -0
- package/plugins/datasets/src/adapters/openlayers/layerBuilders.test.js +233 -0
- package/plugins/datasets/src/adapters/openlayers/openlayersLayerAdapter.js +276 -0
- package/plugins/datasets/src/adapters/openlayers/openlayersLayerAdapter.test.js +431 -0
- package/plugins/datasets/src/adapters/openlayers/registry/openLayersDataset.js +172 -0
- package/plugins/datasets/src/adapters/openlayers/registry/openLayersDataset.test.js +262 -0
- package/plugins/datasets/src/fetch/createDynamicSource.js +12 -8
- package/plugins/datasets/src/fetch/createDynamicSource.test.js +61 -52
- package/plugins/datasets/src/initialise/DatasetsInit.jsx +13 -1
- package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +20 -0
- package/plugins/datasets/src/initialise/initialiseDatasets.js +1 -1
- package/plugins/datasets/src/initialise/initialiseDatasets.test.js +1 -1
- package/plugins/datasets/src/registry/mapboxStyleDataset.js +108 -0
- package/plugins/datasets/src/registry/mapboxStyleDataset.test.js +240 -0
- package/plugins/datasets/src/utils/bbox.js +5 -7
- package/plugins/datasets/src/utils/bbox.test.js +3 -10
- package/plugins/draw/dist/css/index.css +1 -0
- package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
- package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
- package/plugins/draw/src/index.js +2 -0
- package/plugins/draw/src/index.test.js +1 -0
- package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -1
- package/plugins/map-key/dist/umd/im-map-key-plugin.js +1 -1
- 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/index.js +16 -0
- package/providers/beta/openlayers/src/openlayersProvider.js +160 -7
- package/providers/beta/openlayers/src/openlayersProvider.test.js +13 -0
- package/providers/beta/openlayers/src/utils/bngProjection.js +9 -0
- package/providers/beta/openlayers/src/utils/filterEvaluator.js +32 -0
- package/providers/beta/openlayers/src/utils/filterEvaluator.test.js +36 -0
- package/providers/beta/openlayers/src/utils/highlightFeatures.js +38 -23
- package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +153 -8
- package/providers/beta/openlayers/src/utils/hoverCursor.js +5 -1
- package/providers/beta/openlayers/src/utils/hoverCursor.test.js +40 -0
- package/providers/beta/openlayers/src/utils/queryFeatures.js +72 -54
- package/providers/beta/openlayers/src/utils/queryFeatures.test.js +143 -1
- package/providers/beta/openlayers/src/utils/symbolImages.js +79 -5
- package/providers/beta/openlayers/src/utils/symbolImages.test.js +111 -1
- package/providers/beta/openlayers/src/utils/vtTileFragments.js +20 -8
- package/providers/beta/openlayers/src/utils/vtTileFragments.test.js +162 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.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/utils/patternImages.js +4 -44
- package/rollup.esm.mjs +2 -0
- package/src/services/patternRegistry.js +38 -0
- package/src/services/patternRegistry.test.js +61 -0
- package/src/types.js +11 -0
- package/webpack.dev.mjs +1 -0
- /package/plugins/datasets/dist/esm/{esriLayerAdapter.js → im-datasets-esri-adapter.js} +0 -0
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
import { OpenLayersDataset } from './registry/openLayersDataset.js'
|
|
2
|
+
import { datasetRegistry } from '../../registry/datasetRegistry.js'
|
|
3
|
+
import { MapboxStyleLayerAdapter } from '../mapboxStyleLayerAdapter.js'
|
|
4
|
+
import { createDatasetSource, createDatasetLayer, resolveLayerStyle, readGeoJSONFeatures } from './layerBuilders.js'
|
|
5
|
+
import { registerSymbols, SYMBOL_RASTER_PIXEL_RATIO } from '../../../../../providers/beta/openlayers/src/utils/symbolImages.js'
|
|
6
|
+
import { registerCrispCanvasPatterns } from './canvasPatternStyle.js'
|
|
7
|
+
import { logger } from '../../../../../src/services/logger.js'
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* OpenLayers implementation of the LayerAdapter interface for the datasets plugin.
|
|
11
|
+
*
|
|
12
|
+
* GeoJSON and vector tile datasets are both supported (see layerBuilders.createDatasetSource).
|
|
13
|
+
* Pattern fills and symbols are both wired into OpenLayersDataset.flatStyle, registered ahead
|
|
14
|
+
* of time here (see _registerPatterns/_registerSymbols) since flatStyle is a synchronous getter.
|
|
15
|
+
*
|
|
16
|
+
* One OL layer per dataset/sublayer (see OpenLayersDataset's leafLayerIds), not up to three like
|
|
17
|
+
* MapLibre — an ol/style/flat spec can combine fill+stroke (or an icon) for one feature in a
|
|
18
|
+
* single layer. Sublayers of the same parent share one OL source (tracked by sourceId), each
|
|
19
|
+
* with its own layer + filter-scoped style, mirroring how MapLibre sublayers share one source.
|
|
20
|
+
*/
|
|
21
|
+
export default class OpenLayersLayerAdapter extends MapboxStyleLayerAdapter {
|
|
22
|
+
/**
|
|
23
|
+
* @param {Object} mapProvider - Map provider instance (e.g. OpenLayersProvider)
|
|
24
|
+
* @param {Object} symbolRegistry
|
|
25
|
+
* @param {Object} patternRegistry
|
|
26
|
+
*/
|
|
27
|
+
constructor (mapProvider, symbolRegistry, patternRegistry) {
|
|
28
|
+
super(mapProvider, symbolRegistry, patternRegistry)
|
|
29
|
+
// sourceId → OL source, shared across sublayers of the same dataset
|
|
30
|
+
this._sourcesById = new Map()
|
|
31
|
+
// leaf layer id (== dataset/sublayer id) → OL layer
|
|
32
|
+
this._layersById = new Map()
|
|
33
|
+
// initialiseDatasets.js passes this to datasetRegistry.attachCreateDataset unbound, then
|
|
34
|
+
// calls it as registry._createDataset(...) — bind so `this` still resolves here, not to
|
|
35
|
+
// the registry.
|
|
36
|
+
this.createDataset = this.createDataset.bind(this)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
createDataset (datasetDefinition) {
|
|
40
|
+
return new OpenLayersDataset(datasetDefinition)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
get _styleContext () {
|
|
44
|
+
return { mapStyleId: datasetRegistry.mapStyle?.id, pixelRatio: this._pixelRatio, patternRegistry: this._patternRegistry }
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// ─── Lifecycle ──────────────────────────────────────────────────────────────
|
|
48
|
+
|
|
49
|
+
async init () {
|
|
50
|
+
await Promise.all([this._registerPatterns(), this._registerSymbols()])
|
|
51
|
+
datasetRegistry.forEachDataset(registryDataset => this._addLayers(registryDataset))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
destroy () {
|
|
55
|
+
this._layersById.forEach(layer => this._map.removeLayer(layer))
|
|
56
|
+
this._layersById.clear()
|
|
57
|
+
this._sourcesById.clear()
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// OL's basemap swap (see providers/beta/openlayers's appEvents.js) only replaces the base
|
|
61
|
+
// tile layer — dataset layers are separate map.addLayer() entries and are never wiped, so
|
|
62
|
+
// unlike MapLibre there's nothing to rebuild here, just colour tokens to re-resolve. Pattern
|
|
63
|
+
// fg/bg colours can be mapStyle-keyed too, so re-registering (not just re-styling) is needed.
|
|
64
|
+
async onMapStyleChange () {
|
|
65
|
+
await Promise.all([this._registerPatterns(), this._registerSymbols()])
|
|
66
|
+
datasetRegistry.forEachDataset(registryDataset => this._forEachLeafDataset(registryDataset, leaf => {
|
|
67
|
+
this._setLayerStyle(leaf)
|
|
68
|
+
}))
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// Plain fill-color/stroke layers don't depend on map size at all. But a pattern fill (see
|
|
72
|
+
// canvasPatternStyle.js) is deliberately rasterised at the map's *actual* pixelRatio to stay
|
|
73
|
+
// genuinely crisp, so a pixelRatio change (map resize) needs a fresh rasterisation and a
|
|
74
|
+
// re-applied style, the same way MapLibreLayerAdapter.onMapSizeChange re-rasterises
|
|
75
|
+
// symbols/patterns at the new ratio.
|
|
76
|
+
async onMapSizeChange () {
|
|
77
|
+
await this._registerPatterns()
|
|
78
|
+
datasetRegistry.forEachDataset(registryDataset => this._forEachLeafDataset(registryDataset, leaf => {
|
|
79
|
+
if (leaf.hasPattern) {
|
|
80
|
+
this._setLayerStyle(leaf)
|
|
81
|
+
}
|
|
82
|
+
}))
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Dataset operations ─────────────────────────────────────────────────────
|
|
86
|
+
// addDataset is inherited from MapboxStyleLayerAdapter.
|
|
87
|
+
|
|
88
|
+
_registerPatternsAndSymbols (registryDataset) {
|
|
89
|
+
return Promise.all([this._registerPatterns([registryDataset]), this._registerSymbols([registryDataset])])
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
removeDataset (datasetId) {
|
|
93
|
+
const registryDataset = datasetRegistry.getDataset(datasetId)
|
|
94
|
+
if (!registryDataset) {
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
registryDataset.layerIds.forEach(layerId => {
|
|
98
|
+
const layer = this._layersById.get(layerId)
|
|
99
|
+
if (layer) {
|
|
100
|
+
this._map.removeLayer(layer)
|
|
101
|
+
this._layersById.delete(layerId)
|
|
102
|
+
}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
const { sourceId } = registryDataset
|
|
106
|
+
const sourceIsShared = datasetRegistry.topLevelDatasets()
|
|
107
|
+
.some(dataset => dataset.id !== datasetId && dataset.sourceId === sourceId)
|
|
108
|
+
if (!sourceIsShared) {
|
|
109
|
+
this._sourcesById.delete(sourceId)
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
async applyStyle (datasetId) {
|
|
114
|
+
const registryDataset = datasetRegistry.getDataset(datasetId)
|
|
115
|
+
if (!registryDataset) {
|
|
116
|
+
return
|
|
117
|
+
}
|
|
118
|
+
// A style change can introduce a pattern/symbol that wasn't previously configured.
|
|
119
|
+
await this._registerPatternsAndSymbols(registryDataset)
|
|
120
|
+
this._forEachLeafDataset(registryDataset, leaf => {
|
|
121
|
+
if (this._layersById.has(leaf.id)) {
|
|
122
|
+
this._setLayerStyle(leaf)
|
|
123
|
+
} else {
|
|
124
|
+
this._addLayers(leaf)
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ─── Feature operations ─────────────────────────────────────────────────────
|
|
130
|
+
// Opacity/visibility methods are inherited from MapboxStyleLayerAdapter.
|
|
131
|
+
|
|
132
|
+
_setLayerOpacity (layerId, opacity) {
|
|
133
|
+
this._layersById.get(layerId)?.setOpacity(opacity)
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
_setLayerVisibility (layerId, visibility) {
|
|
137
|
+
this._layersById.get(layerId)?.setVisible(visibility === 'visible')
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Hidden features are baked into flatStyle's filter (see OpenLayersDataset), so applying the
|
|
141
|
+
// filter means recomputing and re-setting style rather than a separate declarative call like
|
|
142
|
+
// MapLibre's map.setFilter.
|
|
143
|
+
applyFeatureFilter (datasetId) {
|
|
144
|
+
const registryDataset = datasetRegistry.getDataset(datasetId)
|
|
145
|
+
if (!registryDataset) {
|
|
146
|
+
return
|
|
147
|
+
}
|
|
148
|
+
this._forEachLeafDataset(registryDataset, leaf => {
|
|
149
|
+
this._setLayerStyle(leaf)
|
|
150
|
+
})
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
setData (datasetId, geojson) {
|
|
154
|
+
const registryDataset = datasetRegistry.getDataset(datasetId)
|
|
155
|
+
const olSource = registryDataset && this._sourcesById.get(registryDataset.sourceId)
|
|
156
|
+
if (!olSource) {
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
olSource.clear()
|
|
160
|
+
if (geojson?.features?.length) {
|
|
161
|
+
olSource.addFeatures(readGeoJSONFeatures(geojson, registryDataset.idStrategy))
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// ─── Private ─────────────────────────────────────────────────────────────────
|
|
166
|
+
|
|
167
|
+
// Rasterises and caches each pattern's image ahead of time, so OpenLayersDataset.flatStyle /
|
|
168
|
+
// resolveLayerStyle (both synchronous) can find it already resolved — mirrors
|
|
169
|
+
// MapLibreLayerAdapter's addPatternsAndSymbolsToMap, which likewise runs before _addLayers
|
|
170
|
+
// reads the dataset's style. Goes through canvasPatternStyle.js's genuinely-crisp,
|
|
171
|
+
// pixelRatio-scaled pipeline — see layerBuilders.js's resolveLayerStyle.
|
|
172
|
+
async _registerPatterns (registryDatasets = datasetRegistry.topLevelDatasets()) {
|
|
173
|
+
const styles = []
|
|
174
|
+
registryDatasets.forEach(registryDataset => this._forEachLeafDataset(registryDataset, leaf => {
|
|
175
|
+
if (leaf.hasPattern) {
|
|
176
|
+
styles.push(leaf.style)
|
|
177
|
+
}
|
|
178
|
+
}))
|
|
179
|
+
const mapStyleId = datasetRegistry.mapStyle?.id
|
|
180
|
+
await registerCrispCanvasPatterns(styles, mapStyleId, this._patternRegistry, this._pixelRatio)
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Rasterises and caches each symbol's icon image ahead of time, so OpenLayersDataset.flatStyle
|
|
184
|
+
// (a synchronous getter) can find it already resolved — same convention as _registerPatterns.
|
|
185
|
+
async _registerSymbols (registryDatasets = datasetRegistry.topLevelDatasets()) {
|
|
186
|
+
const styles = []
|
|
187
|
+
registryDatasets.forEach(registryDataset => this._forEachLeafDataset(registryDataset, leaf => {
|
|
188
|
+
if (leaf.hasSymbol) {
|
|
189
|
+
styles.push(leaf.style)
|
|
190
|
+
}
|
|
191
|
+
}))
|
|
192
|
+
await registerSymbols(styles, datasetRegistry.mapStyle, this._symbolRegistry, SYMBOL_RASTER_PIXEL_RATIO)
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// ol/style/flat's expression parser is stricter than MapLibre's filter dialect in some places
|
|
196
|
+
// — e.g. its "in" operator requires a real array second argument, where MapLibre also accepts
|
|
197
|
+
// a bare scalar. A hand-authored dataset filter written for MapLibre can hit this; catch per
|
|
198
|
+
// dataset so one bad config can't take down every other dataset's layers.
|
|
199
|
+
_setLayerStyle (registryDataset) {
|
|
200
|
+
const layer = this._layersById.get(registryDataset.id)
|
|
201
|
+
if (!layer) {
|
|
202
|
+
return
|
|
203
|
+
}
|
|
204
|
+
try {
|
|
205
|
+
layer.setStyle(resolveLayerStyle(registryDataset, this._styleContext))
|
|
206
|
+
// symbolMeta.imageId is only ever resolved for the 'normal' variant, but that's still
|
|
207
|
+
// mapStyle-dependent (resolve() falls back to the scheme's own foreground/halo colours,
|
|
208
|
+
// not just resolveActive/resolveSelected) — so onMapStyleChange (which calls this after
|
|
209
|
+
// re-registering) needs this refreshed too, or highlightFeatures.js keeps reverse-mapping
|
|
210
|
+
// from a stale, pre-switch base id forever (see layerBuilders.js's createDatasetLayer,
|
|
211
|
+
// which sets this the same way when the layer is first built).
|
|
212
|
+
if (registryDataset.hasSymbol) {
|
|
213
|
+
layer.set('symbolMeta', registryDataset.symbolMeta)
|
|
214
|
+
}
|
|
215
|
+
} catch (error) {
|
|
216
|
+
logger.warn(`OpenLayers datasets adapter: failed to build a style for dataset "${registryDataset.id}" — ${error.message}`)
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
_forEachLeafDataset (registryDataset, callback) {
|
|
221
|
+
if (registryDataset.hasSublayers) {
|
|
222
|
+
registryDataset.sublayers.forEach(sublayer => this._forEachLeafDataset(sublayer, callback))
|
|
223
|
+
return
|
|
224
|
+
}
|
|
225
|
+
callback(registryDataset)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Wrapped the same way _setLayerStyle/_addLayers wrap style/layer construction: a malformed
|
|
229
|
+
// dataset config (e.g. a bad tile URL) should log and skip that one dataset, not crash the
|
|
230
|
+
// whole init()/forEachDataset loop for every other dataset sharing it.
|
|
231
|
+
_getOrCreateSource (registryDataset) {
|
|
232
|
+
const { sourceId } = registryDataset
|
|
233
|
+
if (this._sourcesById.has(sourceId)) {
|
|
234
|
+
return this._sourcesById.get(sourceId)
|
|
235
|
+
}
|
|
236
|
+
let olSource
|
|
237
|
+
try {
|
|
238
|
+
olSource = createDatasetSource(registryDataset)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
logger.warn(`OpenLayers datasets adapter: failed to build a source for dataset "${registryDataset.id}" — ${error.message}`)
|
|
241
|
+
return null
|
|
242
|
+
}
|
|
243
|
+
if (olSource) {
|
|
244
|
+
this._sourcesById.set(sourceId, olSource)
|
|
245
|
+
}
|
|
246
|
+
return olSource
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
_addLayers (registryDataset) {
|
|
250
|
+
if (registryDataset.hasSublayers) {
|
|
251
|
+
// A sublayer's own geojson/tiles is always undefined (Dataset.tiles/geojson don't
|
|
252
|
+
// inherit from parent) — the shared source can only be built from the parent's own
|
|
253
|
+
// source descriptor, so create/cache it here before any sublayer looks it up by sourceId.
|
|
254
|
+
this._getOrCreateSource(registryDataset)
|
|
255
|
+
registryDataset.sublayers.forEach(sublayer => this._addLayers(sublayer))
|
|
256
|
+
return
|
|
257
|
+
}
|
|
258
|
+
if (!registryDataset.leafLayerIds.length) {
|
|
259
|
+
return
|
|
260
|
+
}
|
|
261
|
+
const olSource = this._getOrCreateSource(registryDataset)
|
|
262
|
+
if (!olSource) {
|
|
263
|
+
return
|
|
264
|
+
}
|
|
265
|
+
let layer
|
|
266
|
+
try {
|
|
267
|
+
// See _setLayerStyle's comment: a flatStyle built from a hand-authored filter can throw.
|
|
268
|
+
layer = createDatasetLayer(registryDataset, olSource, this._styleContext)
|
|
269
|
+
} catch (error) {
|
|
270
|
+
logger.warn(`OpenLayers datasets adapter: failed to build a style for dataset "${registryDataset.id}" — ${error.message}`)
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
this._map.addLayer(layer)
|
|
274
|
+
this._layersById.set(registryDataset.id, layer)
|
|
275
|
+
}
|
|
276
|
+
}
|