@defra/interactive-map 0.0.22-alpha → 0.0.24-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/images/light-raster-thumb.jpg +0 -0
- package/assets/images/outdoor-raster-thumb.jpg +0 -0
- package/assets/images/road-raster-thumb.jpg +0 -0
- 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/button-definition.md +39 -0
- package/docs/api/context.md +4 -4
- package/docs/api/map-style-config.md +11 -2
- package/docs/api/marker-config.md +38 -7
- package/docs/api/panel-definition.md +7 -3
- package/docs/api/symbol-config.md +10 -26
- package/docs/api/symbol-registry.md +28 -13
- package/docs/api.md +24 -2
- package/docs/assets/css/{docusaurus.css → docusaurus.scss} +18 -12
- package/docs/assets/images/toggle-marker-label.jpg +0 -0
- package/docs/examples/index.mdx +12 -0
- package/docs/examples/toggle-marker-label.mdx +89 -0
- package/docs/plugins/interact.md +4 -11
- package/docs/plugins/search.md +70 -0
- package/docusaurus.config.cjs +2 -1
- package/package.json +23 -9
- package/plugins/beta/datasets/dist/adapters/maplibre/esm/im-datasets-ml-adapter.js +1 -1
- package/plugins/beta/datasets/dist/css/index.css +2 -2
- package/plugins/beta/datasets/dist/esm/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-maplibre-adapter.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/im-datasets-plugin.js +1 -1
- package/plugins/beta/datasets/dist/umd/maplibre/index.js +1 -1
- package/plugins/beta/datasets/src/adapters/maplibre/layerBuilders.js +5 -5
- package/plugins/beta/datasets/src/adapters/maplibre/maplibreLayerAdapter.js +24 -29
- package/plugins/beta/datasets/src/adapters/maplibre/patternImages.js +2 -2
- package/plugins/beta/datasets/src/adapters/maplibre/symbolImages.js +3 -3
- package/plugins/beta/datasets/src/components/KeySvg.jsx +2 -2
- package/plugins/beta/datasets/src/components/KeySvgPattern.jsx +1 -2
- package/plugins/beta/datasets/src/components/svgProperties.js +2 -2
- package/plugins/beta/datasets/src/panels/Key.module.scss +2 -2
- package/plugins/beta/draw-es/dist/esm/im-draw-es-plugin.js +1 -1
- package/plugins/beta/draw-ml/dist/css/index.css +1 -21
- 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/api/editFeature.js +9 -4
- package/plugins/beta/draw-ml/src/modes/createDrawMode.js +11 -3
- package/plugins/beta/map-styles/dist/esm/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/im-map-styles-plugin.js +1 -1
- package/plugins/beta/map-styles/dist/umd/index.js +1 -1
- package/plugins/beta/use-location/dist/esm/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/dist/umd/im-use-location-plugin.js +1 -1
- package/plugins/beta/use-location/src/manifest.js +1 -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/dist/umd/index.js +1 -1
- package/plugins/interact/src/InteractInit.jsx +23 -55
- package/plugins/interact/src/InteractInit.test.js +58 -80
- package/plugins/interact/src/defaults.js +1 -3
- package/plugins/interact/src/hooks/useAttachEvents.js +46 -0
- package/plugins/interact/src/hooks/useAttachEvents.test.js +68 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.js +47 -0
- package/plugins/interact/src/hooks/useCrossHairVisibility.test.js +54 -0
- package/plugins/interact/src/hooks/useHighlightSync.js +30 -8
- package/plugins/interact/src/hooks/useHighlightSync.test.js +127 -80
- package/plugins/interact/src/hooks/useInteractionHandlers.js +18 -17
- package/plugins/interact/src/hooks/useInteractionHandlers.test.js +22 -61
- package/plugins/interact/src/hooks/useMapItemList.js +187 -0
- package/plugins/interact/src/hooks/useMapItemList.test.js +584 -0
- package/plugins/interact/src/index.js +0 -2
- package/plugins/interact/src/index.test.js +0 -2
- package/plugins/interact/src/manifest.js +37 -6
- package/plugins/interact/src/reducer.js +8 -4
- package/plugins/interact/src/reducer.test.js +18 -3
- package/plugins/interact/src/utils/buildStylesMap.js +21 -10
- package/plugins/interact/src/utils/buildStylesMap.test.js +29 -29
- package/plugins/interact/src/utils/spatial.js +0 -15
- package/plugins/interact/src/utils/spatial.test.js +1 -13
- package/plugins/search/dist/css/index.css +1 -1
- package/plugins/search/dist/esm/im-search-plugin.js +1 -1
- package/plugins/search/dist/esm/index.js +1 -1
- package/plugins/search/dist/umd/im-search-plugin.js +1 -1
- package/plugins/search/dist/umd/index.js +1 -1
- package/plugins/search/src/Search.jsx +2 -1
- package/plugins/search/src/components/CloseButton/CloseButton.module.scss +2 -0
- package/plugins/search/src/components/Form/Form.jsx +11 -7
- package/plugins/search/src/components/Form/Form.test.jsx +23 -1
- package/plugins/search/src/components/OpenButton/OpenButton.jsx +1 -1
- package/plugins/search/src/components/OpenButton/OpenButton.module.scss +3 -0
- package/plugins/search/src/datasets.js +2 -2
- package/plugins/search/src/datasets.test.js +1 -1
- package/plugins/search/src/defaults.js +7 -1
- package/plugins/search/src/events/fetchSuggestions.js +13 -10
- package/plugins/search/src/events/fetchSuggestions.test.js +14 -1
- package/plugins/search/src/events/formHandlers.js +2 -4
- package/plugins/search/src/events/formHandlers.test.js +3 -3
- package/plugins/search/src/events/inputHandlers.js +1 -1
- package/plugins/search/src/events/inputHandlers.test.js +1 -1
- package/plugins/search/src/events/suggestionHandlers.js +2 -1
- package/plugins/search/src/events/suggestionHandlers.test.js +7 -1
- package/plugins/search/src/index.js +3 -7
- package/plugins/search/src/reducer.js +10 -5
- package/plugins/search/src/reducer.test.js +2 -1
- package/plugins/search/src/search.scss +1 -0
- package/plugins/search/src/utils/parseOsNamesResults.js +3 -3
- package/providers/beta/esri/dist/esm/im-esri-provider.js +1 -1
- package/providers/beta/esri/src/esriProvider.js +4 -0
- package/providers/beta/openlayers/dist/esm/im-openlayers-provider.js +1 -0
- package/providers/beta/openlayers/dist/esm/index.js +1 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js +2 -0
- package/providers/beta/openlayers/dist/umd/im-openlayers-provider.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/dist/umd/index.js +2 -0
- package/providers/beta/openlayers/dist/umd/index.js.LICENSE.txt +1 -0
- package/providers/beta/openlayers/src/appEvents.js +50 -0
- package/providers/beta/openlayers/src/appEvents.test.js +140 -0
- package/providers/beta/openlayers/src/defaults.js +36 -0
- package/providers/beta/openlayers/src/index.js +21 -0
- package/providers/beta/openlayers/src/mapEvents.js +132 -0
- package/providers/beta/openlayers/src/mapEvents.test.js +197 -0
- package/providers/beta/openlayers/src/openlayersProvider.js +234 -0
- package/providers/beta/openlayers/src/openlayersProvider.test.js +331 -0
- package/providers/beta/openlayers/src/utils/spatial.js +142 -0
- package/providers/beta/openlayers/src/utils/spatial.test.js +117 -0
- package/providers/beta/openlayers/src/utils/zoom.js +26 -0
- package/providers/beta/openlayers/src/utils/zoom.test.js +23 -0
- package/providers/maplibre/dist/esm/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/esm/index.js +1 -1
- package/providers/maplibre/dist/umd/im-maplibre-provider.js +1 -1
- package/providers/maplibre/dist/umd/index.js +1 -1
- package/providers/maplibre/src/index.js +1 -1
- package/providers/maplibre/src/index.test.js +9 -14
- package/providers/maplibre/src/mapEvents.js +11 -5
- package/providers/maplibre/src/mapEvents.test.js +14 -3
- package/providers/maplibre/src/maplibreProvider.js +18 -11
- package/providers/maplibre/src/maplibreProvider.test.js +43 -36
- package/providers/maplibre/src/utils/highlightFeatures.js +112 -57
- package/providers/maplibre/src/utils/highlightFeatures.test.js +179 -51
- package/providers/maplibre/src/utils/maplibreFixes.js +10 -0
- package/providers/maplibre/src/utils/maplibreFixes.test.js +31 -0
- package/providers/maplibre/src/utils/patternImages.js +22 -19
- package/providers/maplibre/src/utils/patternImages.test.js +146 -167
- package/providers/maplibre/src/utils/rasteriseToImageData.js +1 -4
- package/providers/maplibre/src/utils/rasteriseToImageData.test.js +23 -24
- package/providers/maplibre/src/utils/symbolImages.js +23 -86
- package/providers/maplibre/src/utils/symbolImages.test.js +60 -105
- package/rollup.esm.mjs +14 -3
- package/src/App/components/Actions/Actions.module.scss +1 -0
- package/src/App/components/Actions/Actions.test.jsx +17 -4
- package/src/App/components/Hints/Hints.jsx +52 -0
- package/src/App/components/Hints/Hints.module.scss +37 -0
- package/src/App/components/Hints/Hints.test.jsx +104 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.jsx +57 -19
- package/src/App/components/KeyboardHelp/KeyboardHelp.module.scss +10 -0
- package/src/App/components/KeyboardHelp/KeyboardHelp.test.jsx +140 -31
- package/src/App/components/Markers/LabelMarker.jsx +14 -0
- package/src/App/components/Markers/LabelMarker.test.jsx +36 -0
- package/src/App/components/Markers/Markers.jsx +44 -75
- package/src/App/components/Markers/Markers.module.scss +58 -0
- package/src/App/components/Markers/Markers.test.jsx +90 -113
- package/src/App/components/Markers/SymbolLabelMarker.jsx +43 -0
- package/src/App/components/Markers/SymbolLabelMarker.test.jsx +56 -0
- package/src/App/components/Markers/SymbolMarker.jsx +31 -0
- package/src/App/components/Markers/SymbolMarker.test.jsx +72 -0
- package/src/App/components/Panel/Panel.jsx +1 -1
- package/src/App/components/Panel/Panel.module.scss +1 -0
- package/src/App/components/PopupMenu/PopupMenu.test.jsx +117 -1
- package/src/App/components/PopupMenu/usePopupMenu.js +132 -85
- package/src/App/components/Tabs/Tabs.jsx +68 -0
- package/src/App/components/Tabs/Tabs.module.scss +57 -0
- package/src/App/components/Tabs/Tabs.test.jsx +121 -0
- package/src/App/components/Tooltip/Tooltip.module.scss +1 -1
- package/src/App/components/Viewport/Features.jsx +35 -0
- package/src/App/components/Viewport/Features.module.scss +31 -0
- package/src/App/components/Viewport/Features.test.jsx +124 -0
- package/src/App/components/Viewport/MapStatus.jsx +0 -4
- package/src/App/components/Viewport/Viewport.jsx +35 -29
- package/src/App/components/Viewport/Viewport.module.scss +7 -21
- package/src/App/components/Viewport/Viewport.test.jsx +108 -65
- package/src/App/controls/keyboardActions.js +9 -3
- package/src/App/controls/keyboardActions.test.js +122 -87
- package/src/App/controls/keyboardShortcuts.js +1 -14
- package/src/App/hooks/useFeatureFocus.js +199 -0
- package/src/App/hooks/useFeatureFocus.test.js +635 -0
- package/src/App/hooks/useFeatureItems.js +30 -0
- package/src/App/hooks/useFeatureItems.test.js +98 -0
- package/src/App/hooks/useFocusVisible.js +4 -7
- package/src/App/hooks/useFocusVisible.test.js +6 -6
- package/src/App/hooks/useKeyboardHint.js +9 -34
- package/src/App/hooks/useKeyboardHint.test.js +34 -84
- package/src/App/hooks/useKeyboardShortcuts.js +8 -5
- package/src/App/hooks/useKeyboardShortcuts.test.js +16 -12
- package/src/App/hooks/useLayoutMeasurements.js +10 -4
- package/src/App/hooks/useLayoutMeasurements.test.js +30 -1
- package/src/App/hooks/useMarkerCursor.js +33 -0
- package/src/App/hooks/useMarkerCursor.test.js +87 -0
- package/src/App/hooks/useMarkersAPI.js +32 -9
- package/src/App/hooks/useMarkersAPI.test.js +163 -94
- package/src/App/hooks/useModalPanelBehaviour.test.js +2 -1
- package/src/App/layout/Layout.jsx +2 -0
- package/src/App/layout/Layout.test.jsx +3 -0
- package/src/App/registry/keyboardShortcutRegistry.js +3 -2
- package/src/App/registry/keyboardShortcutRegistry.test.js +6 -8
- package/src/App/renderer/mapButtons.js +5 -1
- package/src/App/renderer/mapButtons.test.js +37 -1
- package/src/App/store/AppProvider.test.jsx +5 -0
- package/src/App/store/MapProvider.test.jsx +8 -1
- package/src/App/store/ServiceProvider.jsx +4 -1
- package/src/App/store/ServiceProvider.test.jsx +11 -0
- package/src/App/store/appActionsMap.js +5 -1
- package/src/App/store/appActionsMap.test.js +13 -0
- package/src/App/store/appReducer.js +1 -0
- package/src/App/store/mapActionsMap.js +2 -1
- package/src/InteractiveMap/InteractiveMap.js +12 -0
- package/src/InteractiveMap/InteractiveMap.test.js +2 -0
- package/src/config/appConfig.js +3 -1
- package/src/config/appConfig.test.js +4 -0
- package/src/config/defaults.js +1 -1
- package/src/config/events.js +8 -0
- package/src/config/mapTheme.js +8 -4
- package/src/config/symbolConfig.js +23 -16
- package/src/scss/main.scss +3 -0
- package/src/scss/tools/_border-focus.scss +0 -1
- package/src/services/announcer.test.js +5 -7
- package/src/services/eventBus.js +15 -0
- package/src/services/eventBus.test.js +11 -0
- package/src/services/hints.js +47 -0
- package/src/services/hints.test.js +110 -0
- package/src/services/patternRegistry.js +69 -1
- package/src/services/patternRegistry.test.js +112 -1
- package/src/services/reverseGeocode.js +4 -0
- package/src/services/reverseGeocode.test.js +11 -1
- package/src/services/symbolRegistry.js +152 -17
- package/src/services/symbolRegistry.test.js +141 -31
- package/src/types.js +71 -14
- package/src/utils/constrainKeyboardFocus.js +5 -5
- package/src/utils/detectInterfaceType.js +27 -5
- package/src/utils/detectInterfaceType.test.js +29 -3
- package/src/utils/patternUtils.js +4 -67
- package/src/utils/patternUtils.test.js +1 -123
- package/src/utils/symbolUtils.js +11 -13
- package/src/utils/symbolUtils.test.js +18 -26
- package/webpack.dev.mjs +6 -1
- package/webpack.umd.mjs +1 -0
- package/plugins/interact/dist/css/index.css +0 -1
- package/plugins/interact/src/interact.scss +0 -24
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { createHints } from './hints.js'
|
|
2
|
+
|
|
3
|
+
let announce
|
|
4
|
+
let hints
|
|
5
|
+
|
|
6
|
+
beforeEach(() => {
|
|
7
|
+
jest.useFakeTimers()
|
|
8
|
+
announce = jest.fn()
|
|
9
|
+
hints = createHints(announce)
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
afterEach(() => {
|
|
13
|
+
jest.useRealTimers()
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
// ─── show() ──────────────────────────────────────────────────────────────────
|
|
17
|
+
|
|
18
|
+
describe('show', () => {
|
|
19
|
+
it('notifies subscribers with the html payload', () => {
|
|
20
|
+
const sub = jest.fn()
|
|
21
|
+
hints.subscribe(sub)
|
|
22
|
+
hints.show('<kbd>Enter</kbd> to select')
|
|
23
|
+
expect(sub).toHaveBeenCalledWith({ html: '<kbd>Enter</kbd> to select' })
|
|
24
|
+
})
|
|
25
|
+
|
|
26
|
+
it('calls announce with stripped html', () => {
|
|
27
|
+
hints.show('<kbd>Enter</kbd> to select')
|
|
28
|
+
expect(announce).toHaveBeenCalledWith('Enter to select', 'plugin')
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('calls announce with custom text when announce option is provided', () => {
|
|
32
|
+
hints.show('<kbd>Alt+K</kbd> help', { announce: 'Press Alt+K for keyboard controls' })
|
|
33
|
+
expect(announce).toHaveBeenCalledWith('Press Alt+K for keyboard controls', 'plugin')
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('replaces an existing hint and resets the timer', () => {
|
|
37
|
+
const sub = jest.fn()
|
|
38
|
+
hints.subscribe(sub)
|
|
39
|
+
hints.show('first', { duration: 2000 })
|
|
40
|
+
hints.show('second', { duration: 2000 })
|
|
41
|
+
jest.advanceTimersByTime(2000)
|
|
42
|
+
expect(sub).toHaveBeenLastCalledWith(null)
|
|
43
|
+
// Only one dismiss fired (the second hint's timer)
|
|
44
|
+
expect(sub).toHaveBeenCalledTimes(3) // show1, show2, dismiss
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
it('auto-dismisses after duration', () => {
|
|
48
|
+
const sub = jest.fn()
|
|
49
|
+
hints.subscribe(sub)
|
|
50
|
+
hints.show('hello', { duration: 3000 })
|
|
51
|
+
jest.advanceTimersByTime(3000)
|
|
52
|
+
expect(sub).toHaveBeenLastCalledWith(null)
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('does not auto-dismiss when duration is 0', () => {
|
|
56
|
+
const sub = jest.fn()
|
|
57
|
+
hints.subscribe(sub)
|
|
58
|
+
hints.show('persistent', { duration: 0 })
|
|
59
|
+
jest.advanceTimersByTime(60000)
|
|
60
|
+
expect(sub).not.toHaveBeenCalledWith(null)
|
|
61
|
+
})
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// ─── dismiss() ───────────────────────────────────────────────────────────────
|
|
65
|
+
|
|
66
|
+
describe('dismiss', () => {
|
|
67
|
+
it('clears the active hint and notifies', () => {
|
|
68
|
+
const sub = jest.fn()
|
|
69
|
+
hints.subscribe(sub)
|
|
70
|
+
hints.show('hello')
|
|
71
|
+
hints.dismiss()
|
|
72
|
+
expect(sub).toHaveBeenLastCalledWith(null)
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('cancels the auto-dismiss timer', () => {
|
|
76
|
+
const sub = jest.fn()
|
|
77
|
+
hints.subscribe(sub)
|
|
78
|
+
hints.show('hello', { duration: 3000 })
|
|
79
|
+
hints.dismiss()
|
|
80
|
+
sub.mockClear()
|
|
81
|
+
jest.advanceTimersByTime(3000)
|
|
82
|
+
expect(sub).not.toHaveBeenCalled()
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
it('is safe to call when no hint is active', () => {
|
|
86
|
+
expect(() => hints.dismiss()).not.toThrow()
|
|
87
|
+
})
|
|
88
|
+
})
|
|
89
|
+
|
|
90
|
+
// ─── subscribe / unsubscribe ──────────────────────────────────────────────────
|
|
91
|
+
|
|
92
|
+
describe('subscribe', () => {
|
|
93
|
+
it('returns an unsubscribe function', () => {
|
|
94
|
+
const sub = jest.fn()
|
|
95
|
+
const unsub = hints.subscribe(sub)
|
|
96
|
+
unsub()
|
|
97
|
+
hints.show('hello')
|
|
98
|
+
expect(sub).not.toHaveBeenCalled()
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
it('notifies multiple subscribers', () => {
|
|
102
|
+
const sub1 = jest.fn()
|
|
103
|
+
const sub2 = jest.fn()
|
|
104
|
+
hints.subscribe(sub1)
|
|
105
|
+
hints.subscribe(sub2)
|
|
106
|
+
hints.show('hello')
|
|
107
|
+
expect(sub1).toHaveBeenCalled()
|
|
108
|
+
expect(sub2).toHaveBeenCalled()
|
|
109
|
+
})
|
|
110
|
+
})
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { BUILT_IN_PATTERNS } from '../config/patternConfig.js'
|
|
2
|
-
|
|
2
|
+
import { getValueForStyle } from '../utils/getValueForStyle.js'
|
|
3
|
+
import { KEY_BORDER_PATH, getEffectivePixelRatio, injectColors, hashString } from '../utils/patternUtils.js'
|
|
3
4
|
const patterns = new Map()
|
|
4
5
|
|
|
5
6
|
export const patternRegistry = {
|
|
@@ -31,6 +32,73 @@ export const patternRegistry = {
|
|
|
31
32
|
*/
|
|
32
33
|
list () {
|
|
33
34
|
return [...patterns.values()]
|
|
35
|
+
},
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Clears all registered patterns (including built-ins). Mainly for testing purposes.
|
|
39
|
+
*/
|
|
40
|
+
clear () {
|
|
41
|
+
patterns.clear()
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns the raw (un-coloured) inner SVG content for a style's pattern.
|
|
46
|
+
* Precedence: inline fillPatternSvgContent → named fillPattern from registry.
|
|
47
|
+
*
|
|
48
|
+
* @param {Object} style
|
|
49
|
+
* @returns {string|null}
|
|
50
|
+
*/
|
|
51
|
+
getPatternInnerContent (style) {
|
|
52
|
+
if (style.fillPatternSvgContent) {
|
|
53
|
+
return style.fillPatternSvgContent
|
|
54
|
+
}
|
|
55
|
+
if (style.fillPattern) {
|
|
56
|
+
return this.get(style.fillPattern)?.svgContent ?? null
|
|
57
|
+
}
|
|
58
|
+
return null
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Returns colour-injected SVG path content for use in Key panel pattern symbols.
|
|
63
|
+
* Returns { border, content } where border is the rounded-rect outline and content
|
|
64
|
+
* is the pattern fill. Returns null if the style has no pattern.
|
|
65
|
+
*
|
|
66
|
+
* @param {Object} style
|
|
67
|
+
* @param {string} mapStyleId
|
|
68
|
+
* @returns {{ border: string, content: string }|null}
|
|
69
|
+
*/
|
|
70
|
+
getKeyPatternPaths (style, mapStyleId) {
|
|
71
|
+
const innerContent = this.getPatternInnerContent(style)
|
|
72
|
+
if (!innerContent) {
|
|
73
|
+
return null
|
|
74
|
+
}
|
|
75
|
+
const fg = getValueForStyle(style.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
76
|
+
const bg = getValueForStyle(style.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
77
|
+
const borderStroke = getValueForStyle(style.stroke, mapStyleId) || fg
|
|
78
|
+
const keyPatternPaths = {
|
|
79
|
+
border: injectColors(KEY_BORDER_PATH, borderStroke, bg),
|
|
80
|
+
content: injectColors(innerContent, fg, bg)
|
|
81
|
+
}
|
|
82
|
+
return keyPatternPaths
|
|
83
|
+
},
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Returns a deterministic image ID for a pattern + resolved colour + pixel ratio combination.
|
|
87
|
+
*
|
|
88
|
+
* @param {Object} dataset
|
|
89
|
+
* @param {string} mapStyleId
|
|
90
|
+
* @param {number} [pixelRatio=1]
|
|
91
|
+
* @returns {string|null}
|
|
92
|
+
*/
|
|
93
|
+
getPatternImageId (dataset, mapStyleId, pixelRatio = 1) {
|
|
94
|
+
const innerContent = this.getPatternInnerContent(dataset)
|
|
95
|
+
if (!innerContent) {
|
|
96
|
+
return null
|
|
97
|
+
}
|
|
98
|
+
const fg = getValueForStyle(dataset.fillPatternForegroundColor, mapStyleId) || 'black'
|
|
99
|
+
const bg = getValueForStyle(dataset.fillPatternBackgroundColor, mapStyleId) || 'transparent'
|
|
100
|
+
const effectiveRatio = getEffectivePixelRatio(pixelRatio)
|
|
101
|
+
return `pattern-${hashString(innerContent + fg + bg)}-${effectiveRatio}x`
|
|
34
102
|
}
|
|
35
103
|
}
|
|
36
104
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { patternRegistry } from './patternRegistry.js'
|
|
2
|
-
|
|
2
|
+
import { KEY_BORDER_PATH } from '../utils/patternUtils.js'
|
|
3
3
|
describe('patternRegistry', () => {
|
|
4
4
|
describe('built-in patterns', () => {
|
|
5
5
|
test.each([
|
|
@@ -45,4 +45,115 @@ describe('patternRegistry', () => {
|
|
|
45
45
|
expect(all.every(p => p.id && p.svgContent)).toBe(true)
|
|
46
46
|
})
|
|
47
47
|
})
|
|
48
|
+
|
|
49
|
+
describe('getPatternInnerContent', () => {
|
|
50
|
+
test('returns fillPatternSvgContent when set (inline SVG takes precedence)', () => {
|
|
51
|
+
const style = { fillPatternSvgContent: '<path d="custom"/>', fillPattern: 'dot' }
|
|
52
|
+
expect(patternRegistry.getPatternInnerContent(style)).toBe('<path d="custom"/>')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
test('returns svgContent from registry for a named fillPattern', () => {
|
|
56
|
+
const style = { fillPattern: 'dot' }
|
|
57
|
+
patternRegistry.register('dot', '<path d="M4 4" fill="{{foregroundColor}}"/>')
|
|
58
|
+
expect(patternRegistry.getPatternInnerContent(style)).toBe('<path d="M4 4" fill="{{foregroundColor}}"/>')
|
|
59
|
+
})
|
|
60
|
+
|
|
61
|
+
test('returns null for an unregistered fillPattern name', () => {
|
|
62
|
+
const style = { fillPattern: 'unknown-pattern' }
|
|
63
|
+
expect(patternRegistry.getPatternInnerContent(style)).toBeNull()
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
test('returns null when no pattern is configured', () => {
|
|
67
|
+
expect(patternRegistry.getPatternInnerContent({})).toBeNull()
|
|
68
|
+
})
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
describe('getKeyPatternPaths', () => {
|
|
72
|
+
test('returns border and content strings with colours injected', () => {
|
|
73
|
+
const dataset = {
|
|
74
|
+
fillPattern: 'dot',
|
|
75
|
+
fillPatternForegroundColor: 'red',
|
|
76
|
+
fillPatternBackgroundColor: 'white',
|
|
77
|
+
stroke: 'black'
|
|
78
|
+
}
|
|
79
|
+
const result = patternRegistry.getKeyPatternPaths(dataset, 'style-a')
|
|
80
|
+
expect(result).not.toBeNull()
|
|
81
|
+
expect(result.border).toContain('black') // stroke colour
|
|
82
|
+
expect(result.border).toContain('white') // background colour
|
|
83
|
+
expect(result.content).toContain('red') // foreground colour
|
|
84
|
+
expect(result.border).not.toContain('{{foregroundColor}}')
|
|
85
|
+
expect(result.content).not.toContain('{{foregroundColor}}')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
test('returns null when no pattern content is found', () => {
|
|
89
|
+
expect(patternRegistry.getKeyPatternPaths({ fillPattern: 'unknown' }, 'style-a')).toBeNull()
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
test('falls back to "black" fg and "transparent" bg when colour properties are absent', () => {
|
|
93
|
+
const result = patternRegistry.getKeyPatternPaths({ fillPattern: 'dot' }, 'style-a')
|
|
94
|
+
expect(result).not.toBeNull()
|
|
95
|
+
expect(result.content).toContain('black')
|
|
96
|
+
expect(result.border).toContain('transparent')
|
|
97
|
+
})
|
|
98
|
+
|
|
99
|
+
test('border stroke falls back to foreground colour when stroke is absent', () => {
|
|
100
|
+
const result = patternRegistry.getKeyPatternPaths(
|
|
101
|
+
{ fillPattern: 'dot', fillPatternForegroundColor: 'green' },
|
|
102
|
+
'style-a'
|
|
103
|
+
)
|
|
104
|
+
expect(result).not.toBeNull()
|
|
105
|
+
// borderStroke falls back to fg ('green'), so the border uses green for both stroke and background
|
|
106
|
+
expect(result.border).toContain('green')
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
test('KEY_BORDER_PATH contains foregroundColor and backgroundColor tokens', () => {
|
|
110
|
+
expect(KEY_BORDER_PATH).toContain('{{foregroundColor}}')
|
|
111
|
+
expect(KEY_BORDER_PATH).toContain('{{backgroundColor}}')
|
|
112
|
+
})
|
|
113
|
+
})
|
|
114
|
+
|
|
115
|
+
describe('patternRegistry.getPatternImageId', () => {
|
|
116
|
+
test('returns a deterministic string id', () => {
|
|
117
|
+
const dataset = { fillPattern: 'dot', fillPatternForegroundColor: 'red', fillPatternBackgroundColor: 'blue' }
|
|
118
|
+
const id = patternRegistry.getPatternImageId(dataset, 'style-a')
|
|
119
|
+
expect(typeof id).toBe('string')
|
|
120
|
+
expect(id).toMatch(/^pattern-/)
|
|
121
|
+
expect(id).toBe(patternRegistry.getPatternImageId(dataset, 'style-a'))
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
test('returns null when no pattern content is found', () => {
|
|
125
|
+
expect(patternRegistry.getPatternImageId({ fillPattern: 'unknown' }, 'style-a')).toBeNull()
|
|
126
|
+
})
|
|
127
|
+
|
|
128
|
+
test('produces different ids for different colours', () => {
|
|
129
|
+
const base = { fillPattern: 'dot' }
|
|
130
|
+
const idA = patternRegistry.getPatternImageId({ ...base, fillPatternForegroundColor: 'red' }, 'style-a')
|
|
131
|
+
const idB = patternRegistry.getPatternImageId({ ...base, fillPatternForegroundColor: 'blue' }, 'style-a')
|
|
132
|
+
expect(idA).not.toBe(idB)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
test('floors effective ratio at 2 * so low-DPI ids match 2x', () => {
|
|
136
|
+
const dataset = { fillPattern: 'dot' }
|
|
137
|
+
const id1x = patternRegistry.getPatternImageId(dataset, 'style-a', 1)
|
|
138
|
+
expect(id1x).toMatch(/-2x$/)
|
|
139
|
+
})
|
|
140
|
+
|
|
141
|
+
test('produces different ids for pixelRatios above the floor', () => {
|
|
142
|
+
const dataset = { fillPattern: 'dot' }
|
|
143
|
+
const id2x = patternRegistry.getPatternImageId(dataset, 'style-a', 2)
|
|
144
|
+
const id3x = patternRegistry.getPatternImageId(dataset, 'style-a', 3)
|
|
145
|
+
expect(id2x).not.toBe(id3x)
|
|
146
|
+
expect(id2x).toMatch(/-4x$/)
|
|
147
|
+
expect(id3x).toMatch(/-6x$/)
|
|
148
|
+
})
|
|
149
|
+
|
|
150
|
+
test('falls back to "black" foreground and "transparent" background when colours are absent', () => {
|
|
151
|
+
const id = patternRegistry.getPatternImageId({ fillPattern: 'dot' }, 'style-a')
|
|
152
|
+
const idExplicit = patternRegistry.getPatternImageId(
|
|
153
|
+
{ fillPattern: 'dot', fillPatternForegroundColor: 'black', fillPatternBackgroundColor: 'transparent' },
|
|
154
|
+
'style-a'
|
|
155
|
+
)
|
|
156
|
+
expect(id).toBe(idExplicit)
|
|
157
|
+
})
|
|
158
|
+
})
|
|
48
159
|
})
|
|
@@ -7,6 +7,10 @@ export function createReverseGeocode ({ url, transformRequest, load }, crs) {
|
|
|
7
7
|
}
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
export function hasReverseGeocode () {
|
|
11
|
+
return reverseGeocodeFn !== null
|
|
12
|
+
}
|
|
13
|
+
|
|
10
14
|
export function reverseGeocode (zoom, coord) {
|
|
11
15
|
if (!reverseGeocodeFn) {
|
|
12
16
|
throw new Error('ReverseGeocode not initialised')
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
// reverseGeocode.test.js
|
|
2
|
-
import { createReverseGeocode, reverseGeocode } from './reverseGeocode'
|
|
2
|
+
import { createReverseGeocode, reverseGeocode, hasReverseGeocode } from './reverseGeocode'
|
|
3
3
|
|
|
4
4
|
describe('reverseGeocode module', () => {
|
|
5
5
|
beforeEach(() => {
|
|
@@ -11,6 +11,10 @@ describe('reverseGeocode module', () => {
|
|
|
11
11
|
expect(() => reverseGeocode(10, [0, 0])).toThrow('ReverseGeocode not initialised')
|
|
12
12
|
})
|
|
13
13
|
|
|
14
|
+
it('hasReverseGeocode returns false before initialisation', () => {
|
|
15
|
+
expect(hasReverseGeocode()).toBe(false)
|
|
16
|
+
})
|
|
17
|
+
|
|
14
18
|
it('sets reverseGeocodeFn via createReverseGeocode and calls providerFn correctly', async () => {
|
|
15
19
|
const mockProviderFn = jest.fn().mockResolvedValue('result')
|
|
16
20
|
|
|
@@ -44,6 +48,12 @@ describe('reverseGeocode module', () => {
|
|
|
44
48
|
expect(result).toBe('result')
|
|
45
49
|
})
|
|
46
50
|
|
|
51
|
+
it('hasReverseGeocode returns true after initialisation', () => {
|
|
52
|
+
const config = { url: 'x', transformRequest: jest.fn(), load: jest.fn().mockResolvedValue(jest.fn()) }
|
|
53
|
+
createReverseGeocode(config, 'EPSG:4326')
|
|
54
|
+
expect(hasReverseGeocode()).toBe(true)
|
|
55
|
+
})
|
|
56
|
+
|
|
47
57
|
it('supports multiple calls after initialisation', async () => {
|
|
48
58
|
const mockProviderFn = jest.fn().mockResolvedValue('ok')
|
|
49
59
|
|
|
@@ -1,21 +1,33 @@
|
|
|
1
1
|
import { getValueForStyle } from '../utils/getValueForStyle.js'
|
|
2
2
|
import { symbolDefaults, pin, circle, square, graphics } from '../config/symbolConfig.js'
|
|
3
|
-
import {
|
|
3
|
+
import { getSymbolStyleColors, getSymbolViewBox } from '../utils/symbolUtils.js'
|
|
4
|
+
import { THEME_COLORS } from '../config/mapTheme.js'
|
|
5
|
+
import { rasteriseToImageData } from '../../providers/maplibre/src/utils/rasteriseToImageData.js'
|
|
4
6
|
|
|
5
7
|
const symbols = new Map()
|
|
8
|
+
// Module-level cache: imageId → ImageData. Avoids re-rasterising identical symbols.
|
|
9
|
+
const imageDataCache = new Map()
|
|
6
10
|
let _constructorDefaults = {}
|
|
7
11
|
|
|
12
|
+
const HASH_BASE = 36
|
|
13
|
+
|
|
14
|
+
const hashString = (str) => {
|
|
15
|
+
let hash = 0
|
|
16
|
+
for (const ch of str) {
|
|
17
|
+
hash = Math.trunc(((hash << 5) - hash) + ch.codePointAt(0))
|
|
18
|
+
}
|
|
19
|
+
return Math.abs(hash).toString(HASH_BASE)
|
|
20
|
+
}
|
|
21
|
+
|
|
8
22
|
// Keys that are structural — not token values for SVG substitution
|
|
9
23
|
const STRUCTURAL = new Set(['id', 'svg', 'viewBox', 'anchor', 'symbol', 'symbolSvgContent'])
|
|
10
24
|
|
|
11
|
-
//
|
|
12
|
-
// selectedColor is a map style concern — always injected from mapStyle, never from cascade.
|
|
13
|
-
const REGISTRY_EXCLUDED = new Set([...STRUCTURAL, 'selectedWidth'])
|
|
25
|
+
// selectedColor and activeColor are map style concerns — always injected from mapStyle, never from cascade.
|
|
14
26
|
|
|
15
27
|
function resolveValues (symbolDef, markerValues, mapStyle) {
|
|
16
28
|
const mapStyleId = mapStyle?.id
|
|
17
29
|
const symbolTokens = Object.fromEntries(
|
|
18
|
-
Object.entries(symbolDef || {}).filter(([k]) => !
|
|
30
|
+
Object.entries(symbolDef || {}).filter(([k]) => !STRUCTURAL.has(k))
|
|
19
31
|
)
|
|
20
32
|
const constructorTokens = Object.fromEntries(
|
|
21
33
|
Object.entries(_constructorDefaults).filter(([k]) => !STRUCTURAL.has(k))
|
|
@@ -24,10 +36,11 @@ function resolveValues (symbolDef, markerValues, mapStyle) {
|
|
|
24
36
|
Object.entries(markerValues).filter(([, v]) => v != null)
|
|
25
37
|
)
|
|
26
38
|
const merged = { ...symbolDefaults, ...constructorTokens, ...symbolTokens, ...defined }
|
|
27
|
-
// haloColor and
|
|
28
|
-
const scheme =
|
|
39
|
+
// haloColor, selectedColor and activeColor are map style concerns — always injected from mapStyle, never from the cascade
|
|
40
|
+
const scheme = THEME_COLORS[mapStyle?.mapColorScheme] ?? THEME_COLORS.light
|
|
29
41
|
merged.haloColor = mapStyle?.haloColor ?? scheme.haloColor
|
|
30
42
|
merged.selectedColor = mapStyle?.selectedColor ?? scheme.selectedColor
|
|
43
|
+
merged.activeColor = mapStyle?.activeColor ?? scheme.activeColor
|
|
31
44
|
if (typeof merged.graphic === 'string' && graphics[merged.graphic]) {
|
|
32
45
|
merged.graphic = graphics[merged.graphic]
|
|
33
46
|
}
|
|
@@ -77,37 +90,159 @@ export const symbolRegistry = {
|
|
|
77
90
|
},
|
|
78
91
|
|
|
79
92
|
/**
|
|
80
|
-
*
|
|
81
|
-
|
|
93
|
+
* Clears all registered symbols (including built-ins). Mainly for testing purposes.
|
|
94
|
+
*/
|
|
95
|
+
clear () {
|
|
96
|
+
symbols.clear()
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Clears all registered symbols (including built-ins). Mainly for testing purposes.
|
|
101
|
+
*/
|
|
102
|
+
initialise () {
|
|
103
|
+
this.register(pin)
|
|
104
|
+
this.register(circle)
|
|
105
|
+
this.register(square)
|
|
106
|
+
},
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Resolve a symbol's SVG string for normal (unselected, inactive) rendering.
|
|
110
|
+
* Both selectedColor and activeColor are set to 'none' — all rings hidden.
|
|
82
111
|
*
|
|
83
112
|
* @param {Object} symbolDef - Symbol definition
|
|
84
113
|
* @param {Object} styleColors - Token overrides
|
|
85
|
-
* @param {Object} mapStyle - Current map style config (provides selectedColor, haloColor)
|
|
114
|
+
* @param {Object} mapStyle - Current map style config (provides selectedColor, activeColor, haloColor)
|
|
86
115
|
* @returns {string} Resolved SVG string
|
|
87
116
|
*/
|
|
88
117
|
resolve (symbolDef, styleColors, mapStyle) {
|
|
89
118
|
const colors = resolveValues(symbolDef, styleColors || {}, mapStyle)
|
|
90
119
|
if (!symbolDef) { return '' }
|
|
91
|
-
colors.selectedColor = ''
|
|
120
|
+
colors.selectedColor = 'none'
|
|
121
|
+
colors.activeColor = 'none'
|
|
92
122
|
return resolveLayer(symbolDef.svg, colors)
|
|
93
123
|
},
|
|
94
124
|
|
|
95
125
|
/**
|
|
96
|
-
* Resolve a symbol's SVG string for
|
|
97
|
-
* selectedColor
|
|
126
|
+
* Resolve a symbol's SVG string for active (keyboard cursor) rendering.
|
|
127
|
+
* Both selectedColor (committed ring) and activeColor (focus ring) are shown simultaneously,
|
|
128
|
+
* so an item that is active always displays both rings regardless of selection state.
|
|
98
129
|
*
|
|
99
130
|
* @param {Object} symbolDef - Symbol definition
|
|
100
131
|
* @param {Object} styleColors - Token overrides
|
|
101
|
-
* @param {Object} mapStyle - Current map style config (provides selectedColor, haloColor)
|
|
132
|
+
* @param {Object} mapStyle - Current map style config (provides selectedColor, activeColor, haloColor)
|
|
133
|
+
* @returns {string} Resolved SVG string
|
|
134
|
+
*/
|
|
135
|
+
resolveActive (symbolDef, styleColors, mapStyle) {
|
|
136
|
+
const colors = resolveValues(symbolDef, styleColors || {}, mapStyle)
|
|
137
|
+
if (!symbolDef) { return '' }
|
|
138
|
+
return resolveLayer(symbolDef.svg, colors)
|
|
139
|
+
},
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Resolve a symbol's SVG string for committed-selection rendering.
|
|
143
|
+
* selectedColor (committed ring) is shown; activeColor is set to 'none'.
|
|
144
|
+
*
|
|
145
|
+
* @param {Object} symbolDef - Symbol definition
|
|
146
|
+
* @param {Object} styleColors - Token overrides
|
|
147
|
+
* @param {Object} mapStyle - Current map style config (provides selectedColor, activeColor, haloColor)
|
|
102
148
|
* @returns {string} Resolved SVG string
|
|
103
149
|
*/
|
|
104
150
|
resolveSelected (symbolDef, styleColors, mapStyle) {
|
|
105
151
|
const colors = resolveValues(symbolDef, styleColors || {}, mapStyle)
|
|
106
152
|
if (!symbolDef) { return '' }
|
|
153
|
+
colors.activeColor = 'none'
|
|
107
154
|
return resolveLayer(symbolDef.svg, colors)
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
// ─── Image IDs ────────────────────────────────────────────────────────────────
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Returns a deterministic image ID for a symbol in normal or selected state.
|
|
161
|
+
* Based on the hash of the fully resolved SVG content and the pixel ratio.
|
|
162
|
+
*
|
|
163
|
+
* @param {Object} style
|
|
164
|
+
* @param {Object} mapStyle - Current map style config (provides id, selectedColor, haloColor)
|
|
165
|
+
* @param {boolean} [selected=false]
|
|
166
|
+
* @param {number} [pixelRatio=2] - Device pixel ratio × map size scale factor
|
|
167
|
+
* @returns {string|null}
|
|
168
|
+
*/
|
|
169
|
+
getSymbolImageId (style, mapStyle, active = false, pixelRatio = 2) {
|
|
170
|
+
const symbolDef = this.getSymbolDef(style)
|
|
171
|
+
if (!symbolDef) {
|
|
172
|
+
return null
|
|
173
|
+
}
|
|
174
|
+
const styleColors = getSymbolStyleColors(style)
|
|
175
|
+
const resolved = active
|
|
176
|
+
? this.resolveActive(symbolDef, styleColors, mapStyle)
|
|
177
|
+
: this.resolve(symbolDef, styleColors, mapStyle)
|
|
178
|
+
return `symbol-${active ? 'act-' : ''}${hashString(resolved)}-${pixelRatio}x`
|
|
179
|
+
},
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Resolves the symbolDef for a style's symbol config.
|
|
183
|
+
*
|
|
184
|
+
* style.symbol is a string symbol ID (e.g. 'pin').
|
|
185
|
+
* style.symbolSvgContent is inline SVG content for a custom symbol.
|
|
186
|
+
*
|
|
187
|
+
* @param {Object} style
|
|
188
|
+
* @returns {Object|undefined}
|
|
189
|
+
*/
|
|
190
|
+
getSymbolDef (style) {
|
|
191
|
+
if (style.symbolSvgContent) {
|
|
192
|
+
return { svg: style.symbolSvgContent }
|
|
193
|
+
}
|
|
194
|
+
if (style.symbol) {
|
|
195
|
+
return this.get(style.symbol)
|
|
196
|
+
}
|
|
197
|
+
return undefined
|
|
198
|
+
},
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Rasterise one variant of a symbol to ImageData for use as a MapLibre image.
|
|
202
|
+
* Results are cached by imageId so identical symbols are only rendered once.
|
|
203
|
+
*
|
|
204
|
+
* @param {Object} style - Dataset or marker config with symbol properties
|
|
205
|
+
* @param {Object} mapStyle - Current map style config
|
|
206
|
+
* @param {'normal'|'active'|'selected'} variant
|
|
207
|
+
* - `'normal'` — no rings (default display)
|
|
208
|
+
* - `'active'` — both rings (keyboard cursor, yellow + black)
|
|
209
|
+
* - `'selected'` — selected ring only (black)
|
|
210
|
+
* @param {number} pixelRatio - Device pixel ratio × map size scale factor
|
|
211
|
+
* @returns {Promise<{imageId: string, imageData: ImageData}|null>}
|
|
212
|
+
*/
|
|
213
|
+
async rasteriseSymbolImage (style, mapStyle, variant, pixelRatio) {
|
|
214
|
+
const symbolDef = this.getSymbolDef(style)
|
|
215
|
+
if (!symbolDef) {
|
|
216
|
+
return null
|
|
217
|
+
}
|
|
218
|
+
const styleColors = getSymbolStyleColors(style)
|
|
219
|
+
let resolvedContent, prefix
|
|
220
|
+
if (variant === 'active') {
|
|
221
|
+
resolvedContent = this.resolveActive(symbolDef, styleColors, mapStyle)
|
|
222
|
+
prefix = 'act-'
|
|
223
|
+
} else if (variant === 'selected') {
|
|
224
|
+
resolvedContent = this.resolveSelected(symbolDef, styleColors, mapStyle)
|
|
225
|
+
prefix = 'sel-'
|
|
226
|
+
} else {
|
|
227
|
+
resolvedContent = this.resolve(symbolDef, styleColors, mapStyle)
|
|
228
|
+
prefix = ''
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
const imageId = `symbol-${prefix}${hashString(resolvedContent)}-${pixelRatio}x`
|
|
232
|
+
|
|
233
|
+
let imageData = imageDataCache.get(imageId)
|
|
234
|
+
if (!imageData) {
|
|
235
|
+
const viewBox = getSymbolViewBox(style, symbolDef)
|
|
236
|
+
const [,, width, height] = viewBox.split(' ').map(Number)
|
|
237
|
+
// Render at pixelRatio× to keep icons crisp at the current device DPI and map size.
|
|
238
|
+
// MapLibre receives the matching pixelRatio so the image displays at its original logical size.
|
|
239
|
+
const svgString = `<svg xmlns="http://www.w3.org/2000/svg" width="${width * pixelRatio}" height="${height * pixelRatio}" viewBox="${viewBox}">${resolvedContent}</svg>`
|
|
240
|
+
imageData = await rasteriseToImageData(svgString, width * pixelRatio, height * pixelRatio)
|
|
241
|
+
imageDataCache.set(imageId, imageData)
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return { imageId, imageData }
|
|
108
245
|
}
|
|
109
246
|
}
|
|
110
247
|
|
|
111
|
-
symbolRegistry.
|
|
112
|
-
symbolRegistry.register(circle)
|
|
113
|
-
symbolRegistry.register(square)
|
|
248
|
+
symbolRegistry.initialise()
|