@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.
Files changed (76) hide show
  1. package/dist/esm/im-core.js +1 -1
  2. package/dist/umd/im-core.js +1 -1
  3. package/docs/api.md +19 -0
  4. package/docs/architecture.md +14 -0
  5. package/docs/getting-started.md +10 -0
  6. package/docs/plugins/datasets.md +9 -3
  7. package/jest.setup.js +8 -0
  8. package/package.json +8 -3
  9. package/plugins/datasets/dist/esm/im-datasets-ml-adapter.js +1 -1
  10. package/plugins/datasets/dist/esm/im-datasets-ol-adapter.js +1 -0
  11. package/plugins/datasets/dist/esm/im-datasets-plugin.js +1 -1
  12. package/plugins/datasets/dist/umd/im-datasets-ml-adapter.js +1 -1
  13. package/plugins/datasets/dist/umd/im-datasets-ol-adapter.js +2 -0
  14. package/plugins/datasets/dist/umd/im-datasets-ol-adapter.js.LICENSE.txt +1 -0
  15. package/plugins/datasets/dist/umd/im-datasets-plugin.js +1 -1
  16. package/plugins/datasets/dist/umd/index.js +1 -1
  17. package/plugins/datasets/src/adapters/loadLayerAdapter.js +7 -3
  18. package/plugins/datasets/src/adapters/loadLayerAdapter.test.js +17 -0
  19. package/plugins/datasets/src/adapters/mapboxStyleLayerAdapter.js +74 -0
  20. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +14 -79
  21. package/plugins/datasets/src/adapters/maplibre/maplibreLayerAdapter.test.js +4 -0
  22. package/plugins/datasets/src/adapters/maplibre/registry/mapLibreDataset.js +3 -89
  23. package/plugins/datasets/src/adapters/openlayers/canvasPatternStyle.js +111 -0
  24. package/plugins/datasets/src/adapters/openlayers/canvasPatternStyle.test.js +198 -0
  25. package/plugins/datasets/src/adapters/openlayers/layerBuilders.js +205 -0
  26. package/plugins/datasets/src/adapters/openlayers/layerBuilders.test.js +233 -0
  27. package/plugins/datasets/src/adapters/openlayers/openlayersLayerAdapter.js +276 -0
  28. package/plugins/datasets/src/adapters/openlayers/openlayersLayerAdapter.test.js +431 -0
  29. package/plugins/datasets/src/adapters/openlayers/registry/openLayersDataset.js +172 -0
  30. package/plugins/datasets/src/adapters/openlayers/registry/openLayersDataset.test.js +262 -0
  31. package/plugins/datasets/src/fetch/createDynamicSource.js +12 -8
  32. package/plugins/datasets/src/fetch/createDynamicSource.test.js +61 -52
  33. package/plugins/datasets/src/initialise/DatasetsInit.jsx +13 -1
  34. package/plugins/datasets/src/initialise/DatasetsInit.test.jsx +20 -0
  35. package/plugins/datasets/src/initialise/initialiseDatasets.js +1 -1
  36. package/plugins/datasets/src/initialise/initialiseDatasets.test.js +1 -1
  37. package/plugins/datasets/src/registry/mapboxStyleDataset.js +108 -0
  38. package/plugins/datasets/src/registry/mapboxStyleDataset.test.js +240 -0
  39. package/plugins/datasets/src/utils/bbox.js +5 -7
  40. package/plugins/datasets/src/utils/bbox.test.js +3 -10
  41. package/plugins/draw/dist/css/index.css +1 -0
  42. package/plugins/draw/dist/umd/im-draw-ol-adapter.js +1 -1
  43. package/plugins/draw/dist/umd/im-draw-plugin.js +1 -1
  44. package/plugins/draw/src/index.js +2 -0
  45. package/plugins/draw/src/index.test.js +1 -0
  46. package/plugins/map-key/dist/esm/im-map-key-plugin.js +1 -1
  47. package/plugins/map-key/dist/umd/im-map-key-plugin.js +1 -1
  48. package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -1
  49. package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +1 -1
  50. package/providers/beta/openlayers/dist/umd/index.js +1 -1
  51. package/providers/beta/openlayers/src/index.js +16 -0
  52. package/providers/beta/openlayers/src/openlayersProvider.js +160 -7
  53. package/providers/beta/openlayers/src/openlayersProvider.test.js +13 -0
  54. package/providers/beta/openlayers/src/utils/bngProjection.js +9 -0
  55. package/providers/beta/openlayers/src/utils/filterEvaluator.js +32 -0
  56. package/providers/beta/openlayers/src/utils/filterEvaluator.test.js +36 -0
  57. package/providers/beta/openlayers/src/utils/highlightFeatures.js +38 -23
  58. package/providers/beta/openlayers/src/utils/highlightFeatures.test.js +153 -8
  59. package/providers/beta/openlayers/src/utils/hoverCursor.js +5 -1
  60. package/providers/beta/openlayers/src/utils/hoverCursor.test.js +40 -0
  61. package/providers/beta/openlayers/src/utils/queryFeatures.js +72 -54
  62. package/providers/beta/openlayers/src/utils/queryFeatures.test.js +143 -1
  63. package/providers/beta/openlayers/src/utils/symbolImages.js +79 -5
  64. package/providers/beta/openlayers/src/utils/symbolImages.test.js +111 -1
  65. package/providers/beta/openlayers/src/utils/vtTileFragments.js +20 -8
  66. package/providers/beta/openlayers/src/utils/vtTileFragments.test.js +162 -0
  67. package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
  68. package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
  69. package/providers/maplibre/dist/umd/index.js +1 -1
  70. package/providers/maplibre/src/utils/patternImages.js +4 -44
  71. package/rollup.esm.mjs +2 -0
  72. package/src/services/patternRegistry.js +38 -0
  73. package/src/services/patternRegistry.test.js +61 -0
  74. package/src/types.js +11 -0
  75. package/webpack.dev.mjs +1 -0
  76. /package/plugins/datasets/dist/esm/{esriLayerAdapter.js → im-datasets-esri-adapter.js} +0 -0
@@ -1,50 +1,10 @@
1
- import { injectColors, getEffectivePixelRatio } from '../../../../src/utils/patternUtils.js'
2
- import { getValueForStyle } from '../../../../src/utils/getValueForStyle.js'
3
- import { rasteriseToImageData } from '../../../../src/utils/rasteriseToImageData.js'
4
-
5
- // Module-level cache: imageId → ImageData. Avoids re-rasterising identical patterns.
6
- const imageDataCache = new Map()
7
-
8
- /**
9
- * Rasterises a dataset's pattern SVG to ImageData, using an in-memory cache
10
- * to avoid re-rasterising identical patterns.
11
- *
12
- * @param {Object} dataset
13
- * @param {string} mapStyleId
14
- * @param {Object} patternRegistry
15
- * @param {number} pixelRatio
16
- * @returns {Promise<{ imageId: string, imageData: ImageData }|null>}
17
- */
18
- const rasterisePattern = async (dataset, mapStyleId, patternRegistry, pixelRatio) => {
19
- const innerContent = patternRegistry.getPatternInnerContent(dataset)
20
- if (!innerContent) {
21
- return null
22
- }
23
-
24
- const imageId = patternRegistry.getPatternImageId(dataset, mapStyleId, pixelRatio)
25
- if (!imageId) {
26
- return null
27
- }
28
-
29
- let imageData = imageDataCache.get(imageId)
30
- if (!imageData) {
31
- const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
32
- const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
33
- const colored = injectColors(innerContent, fg, bg)
34
- const bgRect = `<rect width="16" height="16" fill="${bg}"/>`
35
- const effectiveRatio = getEffectivePixelRatio(pixelRatio)
36
- const physicalSize = Math.round(8 * effectiveRatio)
37
- const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${physicalSize}" height="${physicalSize}" viewBox="0 0 16 16">${bgRect}${colored}</svg>`
38
- imageData = await rasteriseToImageData(svgString, physicalSize, physicalSize)
39
- imageDataCache.set(imageId, imageData)
40
- }
41
-
42
- return { imageId, imageData }
43
- }
1
+ import { getEffectivePixelRatio } from '../../../../src/utils/patternUtils.js'
44
2
 
45
3
  /**
46
4
  * Register pattern images for the given pre-resolved pattern configs.
47
5
  * Skips images that are already registered (safe to call on style change).
6
+ * Rasterisation itself lives in patternRegistry.rasterisePatternImage, shared with OpenLayers —
7
+ * this only owns the MapLibre-specific "last mile" of registering via map.addImage().
48
8
  *
49
9
  * @param {Object} map - MapLibre map instance
50
10
  * @param {Object[]} styleArray - an array of pattern style configs
@@ -64,7 +24,7 @@ export const addPatternsToMap = async (map, styleArray, mapStyleId, patternRegis
64
24
  const imageId = patternRegistry.getPatternImageId(style, mapStyleId, pixelRatio)
65
25
  if (imageId && !imagesToAdd[imageId] && !map.hasImage(imageId)) {
66
26
  imagesToAdd[imageId] = async () => {
67
- const result = await rasterisePattern(style, mapStyleId, patternRegistry, pixelRatio)
27
+ const result = await patternRegistry.rasterisePatternImage(style, mapStyleId, pixelRatio)
68
28
  if (result && !map.hasImage(result.imageId)) {
69
29
  map.addImage(result.imageId, result.imageData, { pixelRatio: effectiveRatio })
70
30
  }
package/rollup.esm.mjs CHANGED
@@ -265,6 +265,8 @@ const ALL_BUILDS = [
265
265
  manualChunks: (id) => {
266
266
  if (id.includes('/manifest')) { return 'im-datasets-plugin' }
267
267
  if (id.includes('maplibreLayerAdapter')) { return 'im-datasets-ml-adapter' }
268
+ if (id.includes('openlayersLayerAdapter')) { return 'im-datasets-ol-adapter' }
269
+ if (id.includes('esriLayerAdapter')) { return 'im-datasets-esri-adapter' }
268
270
  return undefined
269
271
  }
270
272
  },
@@ -1,7 +1,10 @@
1
1
  import { BUILT_IN_PATTERNS } from '../config/patternConfig.js'
2
2
  import { getValueForStyle } from '../utils/getValueForStyle.js'
3
3
  import { KEY_BORDER_PATH, getEffectivePixelRatio, injectColors, hashString } from '../utils/patternUtils.js'
4
+ import { rasteriseToImageData } from '../utils/rasteriseToImageData.js'
4
5
  const patterns = new Map()
6
+ // Module-level cache: imageId → ImageData. Avoids re-rasterising identical patterns.
7
+ const imageDataCache = new Map()
5
8
 
6
9
  export const patternRegistry = {
7
10
  /**
@@ -106,6 +109,41 @@ export const patternRegistry = {
106
109
  const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
107
110
  const effectiveRatio = getEffectivePixelRatio(pixelRatio)
108
111
  return `pattern-${hashString(innerContent + fg + bg)}-${effectiveRatio}x`
112
+ },
113
+
114
+ /**
115
+ * Rasterise a pattern to ImageData, cached by imageId — shared by MapLibre's map.addImage()
116
+ * and OpenLayers' CanvasPattern (canvasPatternStyle.js). Note: the two currently render the
117
+ * pattern tile at different on-screen sizes (8px vs 16px CSS) — not yet confirmed if that
118
+ * needs aligning.
119
+ *
120
+ * @param {Object} style - Dataset or marker config with fillPattern* properties
121
+ * @param {string} mapStyleId - Current style/theme identifier
122
+ * @param {number} [pixelRatio=1] - Device pixel ratio × map size scale factor
123
+ * @returns {Promise<{imageId: string, imageData: ImageData}|null>}
124
+ */
125
+ async rasterisePatternImage (style, mapStyleId, pixelRatio = 1) {
126
+ const innerContent = this.getPatternInnerContent(style)
127
+ if (!innerContent) {
128
+ return null
129
+ }
130
+ const imageId = this.getPatternImageId(style, mapStyleId, pixelRatio)
131
+ if (!imageId) {
132
+ return null
133
+ }
134
+ let imageData = imageDataCache.get(imageId)
135
+ if (!imageData) {
136
+ const fg = getValueForStyle(style.fillPatternForegroundColor, mapStyleId) || 'black'
137
+ const bg = getValueForStyle(style.fillPatternBackgroundColor, mapStyleId) || 'transparent'
138
+ const colored = injectColors(innerContent, fg, bg)
139
+ const bgRect = `<rect width="16" height="16" fill="${bg}"/>`
140
+ const effectiveRatio = getEffectivePixelRatio(pixelRatio)
141
+ const physicalSize = Math.round(8 * effectiveRatio)
142
+ const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${physicalSize}" height="${physicalSize}" viewBox="0 0 16 16">${bgRect}${colored}</svg>`
143
+ imageData = await rasteriseToImageData(svgString, physicalSize, physicalSize)
144
+ imageDataCache.set(imageId, imageData)
145
+ }
146
+ return { imageId, imageData }
109
147
  }
110
148
  }
111
149
 
@@ -1,5 +1,24 @@
1
1
  import { patternRegistry } from './patternRegistry.js'
2
2
  import { KEY_BORDER_PATH } from '../utils/patternUtils.js'
3
+
4
+ beforeAll(() => {
5
+ globalThis.Image = class {
6
+ constructor (w, h) {
7
+ this.width = w
8
+ this.height = h
9
+ this._src = ''
10
+ }
11
+
12
+ get src () { return this._src }
13
+ set src (val) { this._src = val; this.onload?.() }
14
+ }
15
+
16
+ HTMLCanvasElement.prototype.getContext = jest.fn(() => ({
17
+ drawImage: jest.fn(),
18
+ getImageData: jest.fn((_x, _y, w, h) => ({ width: w, height: h }))
19
+ }))
20
+ })
21
+
3
22
  describe('patternRegistry', () => {
4
23
  describe('built-in patterns', () => {
5
24
  test.each([
@@ -156,4 +175,46 @@ describe('patternRegistry', () => {
156
175
  expect(id).toBe(idExplicit)
157
176
  })
158
177
  })
178
+
179
+ describe('rasterisePatternImage', () => {
180
+ beforeEach(() => {
181
+ patternRegistry.register('rasterise-test', '<path d="M0 0 L8 8"/>')
182
+ })
183
+
184
+ test('returns null when the pattern has no inner content', async () => {
185
+ const result = await patternRegistry.rasterisePatternImage({ fillPattern: 'unknown' }, 'style-a')
186
+ expect(result).toBeNull()
187
+ })
188
+
189
+ test('returns null when getPatternImageId cannot resolve an id even though inner content exists', async () => {
190
+ // Defensive: getPatternImageId re-derives its own innerContent internally, so this
191
+ // shouldn't diverge from the check above in practice — but a consumer (see
192
+ // providers/maplibre/src/utils/patternImages.test.js's equivalent case) still relies on
193
+ // this guard rather than assuming the two calls can never disagree.
194
+ const getPatternImageId = jest.spyOn(patternRegistry, 'getPatternImageId').mockReturnValue(null)
195
+ const result = await patternRegistry.rasterisePatternImage({ fillPattern: 'rasterise-test' }, 'style-a')
196
+ expect(result).toBeNull()
197
+ getPatternImageId.mockRestore()
198
+ })
199
+
200
+ test('returns imageId and imageData for a valid pattern', async () => {
201
+ const result = await patternRegistry.rasterisePatternImage({ fillPattern: 'rasterise-test' }, 'style-a')
202
+ expect(result.imageId).toBe(patternRegistry.getPatternImageId({ fillPattern: 'rasterise-test' }, 'style-a'))
203
+ expect(result.imageData).toBeDefined()
204
+ })
205
+
206
+ test('rasterises at 8 * effectivePixelRatio physical pixels', async () => {
207
+ const result = await patternRegistry.rasterisePatternImage({ fillPattern: 'rasterise-test' }, 'style-a', 2)
208
+ // effectivePixelRatio(2) = max(2, 2*2) = 4 -> 8 * 4 = 32
209
+ expect(result.imageData.width).toBe(32)
210
+ expect(result.imageData.height).toBe(32)
211
+ })
212
+
213
+ test('returns the same cached ImageData object on a second call with identical inputs', async () => {
214
+ const style = { fillPattern: 'rasterise-test', fillPatternForegroundColor: '#unique-cache-key' }
215
+ const first = await patternRegistry.rasterisePatternImage(style, 'style-a')
216
+ const second = await patternRegistry.rasterisePatternImage(style, 'style-a')
217
+ expect(second.imageData).toBe(first.imageData)
218
+ })
219
+ })
159
220
  })
package/src/types.js CHANGED
@@ -356,6 +356,17 @@
356
356
  * inline blob, so your CSP only needs `worker-src 'self'`.
357
357
  */
358
358
 
359
+ /**
360
+ * Configuration options for the OpenLayers provider.
361
+ *
362
+ * @typedef {Object} OpenLayersProviderConfig
363
+ *
364
+ * @property {'uk' | 'world'} [zoomAlignment='uk']
365
+ * Which zoom-level sequence the map's view resolutions follow. 'uk' uses the OS tile
366
+ * grid zoom levels (0–13, zoom 0 shows all of Great Britain). 'world' uses the ESRI LOD
367
+ * sequence, for zoom levels that match the ESRI SDK (full UK visible around zoom 7).
368
+ */
369
+
359
370
  /**
360
371
  * Descriptor for lazy-loading a map provider.
361
372
  *
package/webpack.dev.mjs CHANGED
@@ -18,6 +18,7 @@ export default {
18
18
  ],
19
19
  entry: {
20
20
  index: path.join(__dirname, 'demo/js/index.js'),
21
+ 'index-ol': path.join(__dirname, 'demo/js/index-ol.js'),
21
22
  draw: path.join(__dirname, 'demo/js/draw.js'),
22
23
  'draw-ol': path.join(__dirname, 'demo/js/draw-ol.js'),
23
24
  farming: path.join(__dirname, 'demo/js/farming.js'),