@emuanalytics/flow-rdf-core 4.0.11 → 4.0.12
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/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/lib/map/flow-core-map.mjs +36 -12
- package/dist/lib/map/plugins/annotations/annotation.events.mjs +9 -0
- package/dist/lib/map/plugins/annotations/annotation.plugin.mjs +134 -2
- package/dist/lib/map/plugins/data-join.plugin.mjs +42 -1
- package/dist/lib/map/plugins/export/export.plugin.mjs +14 -0
- package/dist/lib/map/plugins/hover-effect.plugin.mjs +9 -0
- package/dist/lib/map/plugins/index.d.mts +738 -33
- package/dist/lib/map/plugins/index.d.ts +738 -33
- package/dist/lib/map/plugins/index.mjs +0 -1
- package/dist/lib/map/plugins/keyboard-shortcut-manager.mjs +6 -0
- package/dist/lib/map/plugins/map-plugin-base.mjs +5 -0
- package/dist/lib/map/plugins/plugin-manager.mjs +97 -0
- package/dist/lib/map/plugins/popup.plugin.mjs +12 -0
- package/dist/lib/map/plugins/selection/selection.events.mjs +3 -0
- package/dist/lib/map/plugins/selection/selection.plugin.mjs +75 -4
- package/dist/lib/map/plugins/selection/source-selection.mjs +50 -2
- package/dist/lib/map/support/map-events.mjs +19 -0
- package/dist/lib/map/support/map-popup.mjs +30 -5
- package/dist/lib/utils/index.d.mts +1 -0
- package/dist/lib/utils/index.d.ts +1 -0
- package/dist/package.json.mjs +1 -1
- package/dist/shared/{flow-rdf-core.08156446.d.mts → flow-rdf-core.3da5bd49.d.mts} +448 -17
- package/dist/shared/{flow-rdf-core.08156446.d.ts → flow-rdf-core.3da5bd49.d.ts} +448 -17
- package/package.json +2 -1
- package/dist/lib/map/plugins/url-sync.plugin.mjs +0 -55
|
@@ -8,4 +8,3 @@ export { PluginManager } from './plugin-manager.mjs';
|
|
|
8
8
|
export { PopupPlugin } from './popup.plugin.mjs';
|
|
9
9
|
export { SelectionEvent } from './selection/selection.events.mjs';
|
|
10
10
|
export { SelectionPlugin } from './selection/selection.plugin.mjs';
|
|
11
|
-
export { UrlSyncPlugin } from './url-sync.plugin.mjs';
|
|
@@ -91,6 +91,9 @@ class KeyboardShortcutManager {
|
|
|
91
91
|
removeFromListeners(listenerToRemove) {
|
|
92
92
|
this.listeners = this.listeners.filter((listener) => listener !== listenerToRemove);
|
|
93
93
|
}
|
|
94
|
+
/**
|
|
95
|
+
* Converts a key definition string into a normalized key definition.
|
|
96
|
+
*/
|
|
94
97
|
normalizeKeyDefinition(keyDef) {
|
|
95
98
|
const parts = keyDef.split("+");
|
|
96
99
|
const ev = {
|
|
@@ -102,6 +105,9 @@ class KeyboardShortcutManager {
|
|
|
102
105
|
};
|
|
103
106
|
return this.keyDefinitionFromEvent(ev);
|
|
104
107
|
}
|
|
108
|
+
/**
|
|
109
|
+
* Converts a keyboard event into a key definition string.
|
|
110
|
+
*/
|
|
105
111
|
keyDefinitionFromEvent(ev) {
|
|
106
112
|
let keyDef = "";
|
|
107
113
|
if (ev.ctrlKey)
|
|
@@ -7,6 +7,7 @@ var __publicField = (obj, key, value) => {
|
|
|
7
7
|
return value;
|
|
8
8
|
};
|
|
9
9
|
class MapPluginBase extends EventEmitter {
|
|
10
|
+
/** Constructs an plugin instance. Note this this is called internally when registering a plugin. */
|
|
10
11
|
constructor(pluginManager, options) {
|
|
11
12
|
super();
|
|
12
13
|
__publicField(this, "_pluginManager");
|
|
@@ -14,9 +15,13 @@ class MapPluginBase extends EventEmitter {
|
|
|
14
15
|
this._pluginManager = pluginManager;
|
|
15
16
|
this.options = options ?? {};
|
|
16
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* The map instance associated with this plugin.
|
|
20
|
+
*/
|
|
17
21
|
get map() {
|
|
18
22
|
return this._pluginManager.map;
|
|
19
23
|
}
|
|
24
|
+
/** The {@link PluginManager} that is managing this plugin. */
|
|
20
25
|
get pluginManager() {
|
|
21
26
|
return this._pluginManager;
|
|
22
27
|
}
|
|
@@ -12,6 +12,10 @@ var __publicField = (obj, key, value) => {
|
|
|
12
12
|
};
|
|
13
13
|
const debug = Debug("flow:map-plugin-manager");
|
|
14
14
|
class PluginManager {
|
|
15
|
+
/**
|
|
16
|
+
* Constructs a plugin manager for the provided FlowCoreMap instance. Note that plugin manager construction is performed automatically when a
|
|
17
|
+
* map is created.
|
|
18
|
+
*/
|
|
15
19
|
constructor(map) {
|
|
16
20
|
__publicField(this, "_map");
|
|
17
21
|
__publicField(this, "_keyboardShortcutManager", new KeyboardShortcutManager());
|
|
@@ -28,9 +32,27 @@ class PluginManager {
|
|
|
28
32
|
}
|
|
29
33
|
});
|
|
30
34
|
}
|
|
35
|
+
/** Returns the parent FlowCoreMap instance. */
|
|
31
36
|
get map() {
|
|
32
37
|
return this._map;
|
|
33
38
|
}
|
|
39
|
+
/**
|
|
40
|
+
* Register a plugin for use with a map. This will create an instance of the plugin and call its `initialize` method.
|
|
41
|
+
*
|
|
42
|
+
* Note that this is typically called using the `usePlugin` method of a {@link FlowCoreMap} instance.
|
|
43
|
+
*
|
|
44
|
+
* @example
|
|
45
|
+
* Register the HoverEffectPlugin for use with a map:
|
|
46
|
+
```js
|
|
47
|
+
import { HoverEffectPlugin } from '@emuanalytics/flow-rdf-core/plugins';
|
|
48
|
+
map.usePlugin(HoverEffectPlugin);
|
|
49
|
+
```
|
|
50
|
+
*
|
|
51
|
+
* @param plugin The plugin constructor to register. This must be a class that extends {@link MapPluginBase}
|
|
52
|
+
* @param options Plugin-specific options (if any) to pass to the plugin constructor
|
|
53
|
+
* @typeParam T Type of the plugin being registered
|
|
54
|
+
* @returns The registered plugin instance
|
|
55
|
+
*/
|
|
34
56
|
usePlugin(plugin, options) {
|
|
35
57
|
if (this._plugins.has(plugin)) {
|
|
36
58
|
const oldInstance = this._plugins.get(plugin);
|
|
@@ -45,6 +67,22 @@ class PluginManager {
|
|
|
45
67
|
pluginInstance.initialize();
|
|
46
68
|
return pluginInstance;
|
|
47
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Retrieve a plugin previously registered with a map. This is necessary to access plugin-specific methods or properties.
|
|
72
|
+
*
|
|
73
|
+
* Note that this is typically called using the {@link FlowCoreMap.getPlugin} method.
|
|
74
|
+
*
|
|
75
|
+
* @example
|
|
76
|
+
* Retrieve the SelectionPlugin and clear all selections for a specified map data source:
|
|
77
|
+
```js
|
|
78
|
+
const selectionPlugin = map.getPlugin(SelectionPlugin);
|
|
79
|
+
selectionPlugin.selectFeatures('data-source', []);
|
|
80
|
+
```
|
|
81
|
+
*
|
|
82
|
+
* @param plugin The plugin to retrieve (identified by the plugin constructor or name)
|
|
83
|
+
* @typeParam T Type of the plugin being retrieved
|
|
84
|
+
* @returns The registered plugin instance
|
|
85
|
+
*/
|
|
48
86
|
getPlugin(pluginOrName) {
|
|
49
87
|
let name;
|
|
50
88
|
let pluginInstance;
|
|
@@ -60,6 +98,13 @@ class PluginManager {
|
|
|
60
98
|
}
|
|
61
99
|
return pluginInstance;
|
|
62
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Unregister the specified plugin.
|
|
103
|
+
*
|
|
104
|
+
* @param pluginOrName The plugin to unregister (identified by the plugin constructor or name)
|
|
105
|
+
* @returns The plugin manager instance to allow method chaining
|
|
106
|
+
* @typeParam T Type of the plugin being unregistered
|
|
107
|
+
*/
|
|
63
108
|
removePlugin(pluginOrName) {
|
|
64
109
|
let pluginConstructor;
|
|
65
110
|
let pluginInstance;
|
|
@@ -78,6 +123,11 @@ class PluginManager {
|
|
|
78
123
|
this._plugins.delete(pluginConstructor);
|
|
79
124
|
return this;
|
|
80
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Unregister all plugins.
|
|
128
|
+
*
|
|
129
|
+
* @returns The plugin manager instance to allow method chaining
|
|
130
|
+
*/
|
|
81
131
|
removeAllPlugins() {
|
|
82
132
|
this._plugins.forEach((plugin) => {
|
|
83
133
|
if (plugin.destroy)
|
|
@@ -86,19 +136,58 @@ class PluginManager {
|
|
|
86
136
|
this._plugins.clear();
|
|
87
137
|
return this;
|
|
88
138
|
}
|
|
139
|
+
/**
|
|
140
|
+
* Mark the specified plugin as 'exclusive'. This disables other non-exclusive plugins until the {@link removeExclusivePlugin} method is called.
|
|
141
|
+
*
|
|
142
|
+
* This is used by the annotation plugin to disable map selection and hover effects when annotation features are being drawn by the user.
|
|
143
|
+
*/
|
|
89
144
|
setExclusivePlugin(plugin) {
|
|
90
145
|
this._exclusivePlugins.add(plugin);
|
|
91
146
|
this.forwardSetExclusivePlugin();
|
|
92
147
|
return this;
|
|
93
148
|
}
|
|
149
|
+
/**
|
|
150
|
+
* Remove the 'exclusive' flag from the specified plugin. This allows other non-exclusive plugins to receive events again.
|
|
151
|
+
*/
|
|
94
152
|
removeExclusivePlugin(plugin) {
|
|
95
153
|
this._exclusivePlugins.delete(plugin);
|
|
96
154
|
this.forwardSetExclusivePlugin();
|
|
97
155
|
return this;
|
|
98
156
|
}
|
|
157
|
+
/**
|
|
158
|
+
* This method is called by plugins to register keyboard shortcuts with the {@link KeyboardShortcutManager}.
|
|
159
|
+
*
|
|
160
|
+
* @example
|
|
161
|
+
* Register keyboard shortcuts from a map plugin:
|
|
162
|
+
```js
|
|
163
|
+
// Add shortcuts
|
|
164
|
+
this.removeShortcuts = this.pluginManager.addKeyboardShortcuts({
|
|
165
|
+
'Enter,Escape': () => this.commit(),
|
|
166
|
+
KeyP: () => this.createFeature('Polygon'),
|
|
167
|
+
KeyL: () => this.createFeature('LineString'),
|
|
168
|
+
KeyM: () => this.createFeature('Point'),
|
|
169
|
+
KeyC: () => this.createFeature('Circle')
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
...
|
|
173
|
+
|
|
174
|
+
// Remove shortcuts
|
|
175
|
+
this.removeShortcuts();
|
|
176
|
+
```
|
|
177
|
+
*
|
|
178
|
+
* @param keyboardShortcuts Key bindings to register
|
|
179
|
+
* @param priority Key binding priority
|
|
180
|
+
* @returns Function that can be called to remove the keyboard shortcuts
|
|
181
|
+
*/
|
|
99
182
|
addKeyboardShortcuts(keyboardShortcuts, priority = 0) {
|
|
100
183
|
return this._keyboardShortcutManager.addListener(keyboardShortcuts, priority);
|
|
101
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* This method is called internally to forward map events to active plugin handlers.
|
|
187
|
+
*
|
|
188
|
+
* @param eventName The event to forward
|
|
189
|
+
* @param args Event arguments
|
|
190
|
+
*/
|
|
102
191
|
forwardEvent(eventName, ...args) {
|
|
103
192
|
const handlerName = `handle${capitalize(eventName)}`;
|
|
104
193
|
this._plugins.forEach((plugin) => {
|
|
@@ -110,6 +199,11 @@ class PluginManager {
|
|
|
110
199
|
}
|
|
111
200
|
});
|
|
112
201
|
}
|
|
202
|
+
/**
|
|
203
|
+
* This method is called internally to forward map style change events to active plugin handlers.
|
|
204
|
+
*
|
|
205
|
+
* @param eventName The event to forward
|
|
206
|
+
*/
|
|
113
207
|
forwardStyleChangeEvent(event) {
|
|
114
208
|
this._plugins.forEach((plugin) => {
|
|
115
209
|
if (this._exclusivePlugins.size && !this._exclusivePlugins.has(plugin))
|
|
@@ -123,6 +217,9 @@ class PluginManager {
|
|
|
123
217
|
});
|
|
124
218
|
return event.newStyle;
|
|
125
219
|
}
|
|
220
|
+
/**
|
|
221
|
+
* This method is called internally to forward exclusivity changes to registered plugins.
|
|
222
|
+
*/
|
|
126
223
|
forwardSetExclusivePlugin() {
|
|
127
224
|
this._plugins.forEach((plugin) => {
|
|
128
225
|
if (plugin.handleSetExclusivePlugin) {
|
|
@@ -11,6 +11,12 @@ var __publicField = (obj, key, value) => {
|
|
|
11
11
|
return value;
|
|
12
12
|
};
|
|
13
13
|
class PopupPlugin extends MapPluginBase {
|
|
14
|
+
/**
|
|
15
|
+
* Constructs a new PopupPlugin instance. This is called automatically when the plugin is added to a {@link FlowCoreMap} instance
|
|
16
|
+
* using {@link FlowCoreMap.usePlugin} method.
|
|
17
|
+
*
|
|
18
|
+
* @param pluginManager The plugin manager instance associated with the map
|
|
19
|
+
*/
|
|
14
20
|
constructor(pluginManager) {
|
|
15
21
|
super(pluginManager);
|
|
16
22
|
__publicField(this, "_popupTemplates", /* @__PURE__ */ new Map());
|
|
@@ -37,10 +43,16 @@ class PopupPlugin extends MapPluginBase {
|
|
|
37
43
|
}
|
|
38
44
|
return popup;
|
|
39
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Closes all open popups.
|
|
48
|
+
*/
|
|
40
49
|
closeAllPopups() {
|
|
41
50
|
this._popups.forEach((popup) => popup.close());
|
|
42
51
|
this._popups.clear();
|
|
43
52
|
}
|
|
53
|
+
/**
|
|
54
|
+
* Closes open popups for the specified {@link MapDataSource}s.
|
|
55
|
+
*/
|
|
44
56
|
closePopupsForSources(sources) {
|
|
45
57
|
sources = ensureArray(sources);
|
|
46
58
|
this._popups.forEach((popup) => {
|
|
@@ -9,8 +9,11 @@ var __publicField = (obj, key, value) => {
|
|
|
9
9
|
class SelectionEvent extends MapEvent {
|
|
10
10
|
constructor(type, map, sourceIdentifier, features) {
|
|
11
11
|
super(type, map);
|
|
12
|
+
/** Map data source ID */
|
|
12
13
|
__publicField(this, "source");
|
|
14
|
+
/** Map data source layer ID */
|
|
13
15
|
__publicField(this, "sourceLayer");
|
|
16
|
+
/** Array of selected feature IDs */
|
|
14
17
|
__publicField(this, "features");
|
|
15
18
|
this.source = sourceIdentifier.source;
|
|
16
19
|
this.sourceLayer = sourceIdentifier.sourceLayer;
|
|
@@ -12,12 +12,23 @@ var __publicField = (obj, key, value) => {
|
|
|
12
12
|
return value;
|
|
13
13
|
};
|
|
14
14
|
class SelectionPlugin extends MapPluginBase {
|
|
15
|
+
/**
|
|
16
|
+
* Constructs a new SelectionPlugin instance. This is called automatically when the plugin is added to a {@link FlowCoreMap} instance
|
|
17
|
+
* using {@link FlowCoreMap.usePlugin} method.
|
|
18
|
+
*
|
|
19
|
+
* @param pluginManager The plugin manager instance associated with the map
|
|
20
|
+
*/
|
|
15
21
|
constructor(pluginManager) {
|
|
16
22
|
super(pluginManager);
|
|
17
23
|
__publicField(this, "_sourceSelections", /* @__PURE__ */ new Map());
|
|
18
24
|
__publicField(this, "_enablePopups", true);
|
|
19
25
|
__publicField(this, "name", "SelectionPlugin");
|
|
20
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Clears selection for the specified map data source.
|
|
29
|
+
*
|
|
30
|
+
* @param source Map data source ID
|
|
31
|
+
*/
|
|
21
32
|
clearSelection(source) {
|
|
22
33
|
const sourceSelection = this._sourceSelections.get(source);
|
|
23
34
|
if (!sourceSelection) {
|
|
@@ -29,6 +40,12 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
29
40
|
sourceSelection.updateSelection(/* @__PURE__ */ new Set());
|
|
30
41
|
}
|
|
31
42
|
}
|
|
43
|
+
/**
|
|
44
|
+
* Deselect the specified feature or features.
|
|
45
|
+
*
|
|
46
|
+
* @param source Map data source ID
|
|
47
|
+
* @param features feature ID or array of feature IDs to deselect
|
|
48
|
+
*/
|
|
32
49
|
deselectFeatures(source, features) {
|
|
33
50
|
const sourceSelection = this._sourceSelections.get(source);
|
|
34
51
|
if (!sourceSelection) {
|
|
@@ -43,16 +60,30 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
43
60
|
sourceSelection.updateSelection(currentSelection);
|
|
44
61
|
}
|
|
45
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* @internal
|
|
65
|
+
*/
|
|
46
66
|
destroy() {
|
|
47
67
|
this._sourceSelections.forEach((sel) => sel.destroy());
|
|
48
68
|
this._sourceSelections.clear();
|
|
49
69
|
}
|
|
70
|
+
/**
|
|
71
|
+
* Disable selection for the specified map data source.
|
|
72
|
+
*
|
|
73
|
+
* @param source Map data source ID
|
|
74
|
+
*/
|
|
50
75
|
disableSelection(source) {
|
|
51
76
|
if (this._sourceSelections.has(source)) {
|
|
52
77
|
this._sourceSelections.get(source).destroy();
|
|
53
78
|
this._sourceSelections.delete(source);
|
|
54
79
|
}
|
|
55
80
|
}
|
|
81
|
+
/**
|
|
82
|
+
* Enable selection support for the specified map data source.
|
|
83
|
+
*
|
|
84
|
+
* @param source Map data source ID
|
|
85
|
+
* @param options Selection options
|
|
86
|
+
*/
|
|
56
87
|
enableSelection(source, options) {
|
|
57
88
|
if (options.popup) {
|
|
58
89
|
try {
|
|
@@ -64,12 +95,18 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
64
95
|
this.disableSelection(source);
|
|
65
96
|
this._sourceSelections.set(source, new SourceSelection(this, source, options));
|
|
66
97
|
}
|
|
98
|
+
/**
|
|
99
|
+
* @internal
|
|
100
|
+
*/
|
|
67
101
|
handleBeforeSourceRemoved(event) {
|
|
68
102
|
if (this._sourceSelections.has(event.source)) {
|
|
69
103
|
this._sourceSelections.get(event.source)?.destroy();
|
|
70
104
|
this._sourceSelections.delete(event.source);
|
|
71
105
|
}
|
|
72
106
|
}
|
|
107
|
+
/**
|
|
108
|
+
* @internal
|
|
109
|
+
*/
|
|
73
110
|
handleClick(event) {
|
|
74
111
|
const multiSelect = event.originalEvent.shiftKey || event.originalEvent.altKey || event.originalEvent.metaKey;
|
|
75
112
|
const clickedFeature = event.features[0];
|
|
@@ -79,7 +116,17 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
79
116
|
(sel) => sel.handleClick(event.lngLat, clickedFeature, selectionOptions, multiSelect, this._enablePopups)
|
|
80
117
|
);
|
|
81
118
|
}
|
|
82
|
-
|
|
119
|
+
/**
|
|
120
|
+
* Select the specified feature or features.
|
|
121
|
+
*
|
|
122
|
+
* Use this method to programmatically select features on a map data source. This will update the selection state and trigger display of
|
|
123
|
+
* any associated popups if configured.
|
|
124
|
+
*
|
|
125
|
+
* @param source Map data source ID
|
|
126
|
+
* @param features feature ID or array of feature IDs to select
|
|
127
|
+
* @param showPopups Show popups for the selected features. Defaults to `true`.
|
|
128
|
+
*/
|
|
129
|
+
selectFeatures(source, features, showPopups = true) {
|
|
83
130
|
const sourceSelection = this._sourceSelections.get(source);
|
|
84
131
|
if (!sourceSelection) {
|
|
85
132
|
return;
|
|
@@ -93,11 +140,11 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
93
140
|
switch (sourceSelection.options.mode) {
|
|
94
141
|
case "single":
|
|
95
142
|
currentSelection = /* @__PURE__ */ new Set([features[0]]);
|
|
96
|
-
sourceSelection.updateSelection(currentSelection);
|
|
143
|
+
sourceSelection.updateSelection(currentSelection, showPopups);
|
|
97
144
|
break;
|
|
98
145
|
case "multi":
|
|
99
146
|
features.forEach((f) => currentSelection.add(f));
|
|
100
|
-
sourceSelection.updateSelection(currentSelection);
|
|
147
|
+
sourceSelection.updateSelection(currentSelection, showPopups);
|
|
101
148
|
break;
|
|
102
149
|
}
|
|
103
150
|
if (sourceSelection.selectedFeatureIds.size && isStringFull(sourceSelection.options.group)) {
|
|
@@ -108,6 +155,12 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
108
155
|
});
|
|
109
156
|
}
|
|
110
157
|
}
|
|
158
|
+
/**
|
|
159
|
+
* Gets the selected feature IDs for the specified map data source.
|
|
160
|
+
*
|
|
161
|
+
* @param source map data source ID
|
|
162
|
+
* @returns Array of selected feature IDs
|
|
163
|
+
*/
|
|
111
164
|
selectedFeatureIds(source) {
|
|
112
165
|
const sourceSelection = this._sourceSelections.get(source);
|
|
113
166
|
if (!sourceSelection) {
|
|
@@ -115,6 +168,15 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
115
168
|
}
|
|
116
169
|
return Array.from(sourceSelection.selectedFeatureIds);
|
|
117
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Returns a {@link MapFeatureStream} of selected features for the specified map data source.
|
|
173
|
+
*
|
|
174
|
+
* Subscribe to the stream to receive updates when the selection state changes and receive full feature property data
|
|
175
|
+
* from the map.
|
|
176
|
+
*
|
|
177
|
+
* @param source Map data source ID
|
|
178
|
+
* @returns {@link MapFeatureStream} of selected features
|
|
179
|
+
*/
|
|
118
180
|
selectedFeatureStream(source) {
|
|
119
181
|
const sourceSelection = this._sourceSelections.get(source);
|
|
120
182
|
if (!sourceSelection) {
|
|
@@ -122,9 +184,18 @@ class SelectionPlugin extends MapPluginBase {
|
|
|
122
184
|
}
|
|
123
185
|
return sourceSelection.selectedFeatureStream;
|
|
124
186
|
}
|
|
125
|
-
|
|
187
|
+
/**
|
|
188
|
+
* Enable display of popups when a feature is clicked and an associated popup has been registered with the map data source.
|
|
189
|
+
* See {@link PopupPlugin} for more details of how to define and register popups.
|
|
190
|
+
*
|
|
191
|
+
* Note that popups are enabled by default.
|
|
192
|
+
*/
|
|
193
|
+
enablePopups() {
|
|
126
194
|
this._enablePopups = true;
|
|
127
195
|
}
|
|
196
|
+
/**
|
|
197
|
+
* Disable display of popups when a feature is clicked. This will also close any currently open popups.
|
|
198
|
+
*/
|
|
128
199
|
disablePopups() {
|
|
129
200
|
if (this._enablePopups) {
|
|
130
201
|
this.map.getPlugin(PopupPlugin).closeAllPopups();
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import turfCentroid from '@turf/centroid';
|
|
2
|
+
import Debug from 'debug';
|
|
1
3
|
import { assert } from '../../../utils/assert.mjs';
|
|
2
4
|
import { ensureArray } from '../../../utils/ensure-array.mjs';
|
|
3
5
|
import { isStringFull } from '../../../utils/type-checks.mjs';
|
|
@@ -11,7 +13,9 @@ var __publicField = (obj, key, value) => {
|
|
|
11
13
|
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
12
14
|
return value;
|
|
13
15
|
};
|
|
16
|
+
const debug = Debug("flow:SourceSelection");
|
|
14
17
|
class SourceSelection {
|
|
18
|
+
/** Constructs a SourceSelection to handle selection for the specified map data source. */
|
|
15
19
|
constructor(plugin, source, options) {
|
|
16
20
|
__publicField(this, "_featureStream");
|
|
17
21
|
__publicField(this, "_map");
|
|
@@ -21,6 +25,8 @@ class SourceSelection {
|
|
|
21
25
|
__publicField(this, "_selectedFeatureIds", /* @__PURE__ */ new Set());
|
|
22
26
|
__publicField(this, "_sourceId");
|
|
23
27
|
__publicField(this, "_stateSubscription", null);
|
|
28
|
+
__publicField(this, "_featuresSubscription", null);
|
|
29
|
+
__publicField(this, "_pendingPopups", []);
|
|
24
30
|
this._plugin = plugin;
|
|
25
31
|
this._map = plugin.map;
|
|
26
32
|
this._options = options;
|
|
@@ -34,10 +40,15 @@ class SourceSelection {
|
|
|
34
40
|
sourceLayer: options.sourceLayer,
|
|
35
41
|
featureIds: []
|
|
36
42
|
});
|
|
43
|
+
if (this._options.popup) {
|
|
44
|
+
this._featuresSubscription = this._featureStream.subscribe(() => {
|
|
45
|
+
this.showPendingPopups();
|
|
46
|
+
});
|
|
47
|
+
}
|
|
37
48
|
if (options.selectionState) {
|
|
38
49
|
this._stateSubscription = this._map.context.state.toStream(options.selectionState, true).subscribe((val) => {
|
|
39
50
|
val = new Set(ensureArray(val));
|
|
40
|
-
this.updateSelection(val);
|
|
51
|
+
this.updateSelection(val, true);
|
|
41
52
|
});
|
|
42
53
|
}
|
|
43
54
|
}
|
|
@@ -50,12 +61,15 @@ class SourceSelection {
|
|
|
50
61
|
get selectedFeatureStream() {
|
|
51
62
|
return this._featureStream;
|
|
52
63
|
}
|
|
64
|
+
/** Plugin destroy handler */
|
|
53
65
|
destroy() {
|
|
54
66
|
this._popups.forEach((popup) => popup.close());
|
|
55
67
|
this._stateSubscription?.unsubscribe();
|
|
68
|
+
this._featuresSubscription?.unsubscribe();
|
|
56
69
|
this._selectedFeatureIds = /* @__PURE__ */ new Set();
|
|
57
70
|
this.updateMapSelectionState();
|
|
58
71
|
}
|
|
72
|
+
/** Plugin click handler */
|
|
59
73
|
handleClick(lngLat, feature, selectionOptions, multiSelect, enablePopups) {
|
|
60
74
|
if (this._options.mode === "none") {
|
|
61
75
|
return;
|
|
@@ -111,7 +125,13 @@ class SourceSelection {
|
|
|
111
125
|
}
|
|
112
126
|
}
|
|
113
127
|
}
|
|
114
|
-
|
|
128
|
+
/** Update the current selection. */
|
|
129
|
+
updateSelection(featureIds, showPopups = false) {
|
|
130
|
+
if (showPopups) {
|
|
131
|
+
this._pendingPopups = Array.from(featureIds);
|
|
132
|
+
} else {
|
|
133
|
+
this._pendingPopups = [];
|
|
134
|
+
}
|
|
115
135
|
if (!this.areSetsEqual(featureIds, this._selectedFeatureIds)) {
|
|
116
136
|
this._selectedFeatureIds = featureIds;
|
|
117
137
|
this.updateMapSelectionState();
|
|
@@ -131,16 +151,19 @@ class SourceSelection {
|
|
|
131
151
|
);
|
|
132
152
|
this._plugin.emit(event.type, event);
|
|
133
153
|
}
|
|
154
|
+
this.showPendingPopups();
|
|
134
155
|
}
|
|
135
156
|
areSetsEqual(a, b) {
|
|
136
157
|
return a.size === b.size && [...a].every((value) => b.has(value));
|
|
137
158
|
}
|
|
159
|
+
/** Sync map feature state with current selection. */
|
|
138
160
|
updateMapSelectionState() {
|
|
139
161
|
this._map.removeFeatureState(this._sourceId, "selected", false);
|
|
140
162
|
this._selectedFeatureIds.forEach(
|
|
141
163
|
(id) => this._map.setFeatureState({ ...this._sourceId, id }, { selected: true }, false)
|
|
142
164
|
);
|
|
143
165
|
}
|
|
166
|
+
/** Sync bound reactive property with current selection. */
|
|
144
167
|
updateStateStore() {
|
|
145
168
|
if (isStringFull(this._options.selectionState)) {
|
|
146
169
|
const selectedIds = Array.from(this._selectedFeatureIds);
|
|
@@ -151,6 +174,31 @@ class SourceSelection {
|
|
|
151
174
|
}
|
|
152
175
|
}
|
|
153
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* Supports displaying popups after selection has been set programmatically.
|
|
179
|
+
*
|
|
180
|
+
* Opening a selected popup is deferred until the next feature stream update so that
|
|
181
|
+
* feature geometry is available to calculate the popup position.
|
|
182
|
+
*
|
|
183
|
+
* @param features Selected features from the feature stream.
|
|
184
|
+
*/
|
|
185
|
+
showPendingPopups() {
|
|
186
|
+
for (const feature of Object.values(this._featureStream.featureData)) {
|
|
187
|
+
assert(isFeatureWithId(feature));
|
|
188
|
+
if (this._pendingPopups.includes(feature.id) && !this._popups.has(feature.id) && this._options.popup) {
|
|
189
|
+
debug(`Showing pending popup for source ${this._sourceId.source}`, feature);
|
|
190
|
+
const lngLat = turfCentroid(feature.geometry).geometry.coordinates;
|
|
191
|
+
const popup = this._map.getPlugin(PopupPlugin).openPopup(this._options.popup, lngLat, feature);
|
|
192
|
+
if (popup) {
|
|
193
|
+
popup.on("closed", () => {
|
|
194
|
+
this._popups.delete(feature.id);
|
|
195
|
+
});
|
|
196
|
+
this._popups.set(feature.id, popup);
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
this._pendingPopups = this._pendingPopups.filter((id) => id !== feature.id);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
154
202
|
}
|
|
155
203
|
|
|
156
204
|
export { SourceSelection };
|
|
@@ -5,14 +5,25 @@ var __publicField = (obj, key, value) => {
|
|
|
5
5
|
return value;
|
|
6
6
|
};
|
|
7
7
|
class MapEvent {
|
|
8
|
+
/**
|
|
9
|
+
* Constructs a map event.
|
|
10
|
+
*
|
|
11
|
+
* @param type Event type
|
|
12
|
+
* @param map Map instance that generated the event
|
|
13
|
+
* @param originalEvent Original MapLibre event that triggered the event
|
|
14
|
+
*/
|
|
8
15
|
constructor(type, map, originalEvent) {
|
|
16
|
+
/** Event type */
|
|
9
17
|
__publicField(this, "type");
|
|
18
|
+
/** Map instance that generated event */
|
|
10
19
|
__publicField(this, "map");
|
|
20
|
+
/** Original MapLibre event */
|
|
11
21
|
__publicField(this, "originalEvent");
|
|
12
22
|
this.type = type;
|
|
13
23
|
this.map = map;
|
|
14
24
|
this.originalEvent = originalEvent;
|
|
15
25
|
}
|
|
26
|
+
/** Creates an event from an original MapLibre event. */
|
|
16
27
|
static fromMaplibreEvent(type, map, maplibreEvent) {
|
|
17
28
|
return new MapEvent(type, map, maplibreEvent.originalEvent);
|
|
18
29
|
}
|
|
@@ -28,6 +39,10 @@ class MapMouseEvent extends MapEvent {
|
|
|
28
39
|
* The geographic location on the map of the mouse cursor.
|
|
29
40
|
*/
|
|
30
41
|
__publicField(this, "lngLat");
|
|
42
|
+
/**
|
|
43
|
+
* Features found at the mouse coordinate. Features are ordered by map layer with features higher in the layer stack appearing first in the `features` array.
|
|
44
|
+
* Features from non-interactive layers are excluded.
|
|
45
|
+
*/
|
|
31
46
|
__publicField(this, "features");
|
|
32
47
|
this.point = point;
|
|
33
48
|
this.lngLat = lngLat;
|
|
@@ -47,6 +62,7 @@ class MapMouseEvent extends MapEvent {
|
|
|
47
62
|
class MapSourceEvent extends MapEvent {
|
|
48
63
|
constructor(type, map, source) {
|
|
49
64
|
super(type, map);
|
|
65
|
+
/** Map data source ID */
|
|
50
66
|
__publicField(this, "source");
|
|
51
67
|
this.source = source;
|
|
52
68
|
}
|
|
@@ -54,6 +70,7 @@ class MapSourceEvent extends MapEvent {
|
|
|
54
70
|
class MapLoadingEvent extends MapEvent {
|
|
55
71
|
constructor(map, loading) {
|
|
56
72
|
super("loading", map);
|
|
73
|
+
/** Indicates whether the map is currently loading data. */
|
|
57
74
|
__publicField(this, "loading");
|
|
58
75
|
this.loading = loading;
|
|
59
76
|
}
|
|
@@ -61,7 +78,9 @@ class MapLoadingEvent extends MapEvent {
|
|
|
61
78
|
class MapStyleChangeEvent extends MapEvent {
|
|
62
79
|
constructor(type, map, oldStyle, newStyle) {
|
|
63
80
|
super(type, map);
|
|
81
|
+
/** Old map style before changes. */
|
|
64
82
|
__publicField(this, "oldStyle");
|
|
83
|
+
/** New map style after changes. */
|
|
65
84
|
__publicField(this, "newStyle");
|
|
66
85
|
this.oldStyle = oldStyle;
|
|
67
86
|
this.newStyle = newStyle;
|
|
@@ -16,12 +16,12 @@ var __publicField = (obj, key, value) => {
|
|
|
16
16
|
Debug("flow:map-popup");
|
|
17
17
|
class MapPopup extends EventEmitter {
|
|
18
18
|
/**
|
|
19
|
-
* Constructs a new
|
|
19
|
+
* Constructs a new {@link MapPopup} instance.
|
|
20
20
|
*
|
|
21
|
-
*
|
|
21
|
+
* Typically, a popup is created by interacting with the PopupPlugin or SelectionPlugin rather than instantiating a popup directly.
|
|
22
22
|
*
|
|
23
|
-
* @param map Parent
|
|
24
|
-
* @param options Popup options
|
|
23
|
+
* @param map Parent map
|
|
24
|
+
* @param options Popup options
|
|
25
25
|
*/
|
|
26
26
|
constructor(map, options) {
|
|
27
27
|
super();
|
|
@@ -44,6 +44,9 @@ class MapPopup extends EventEmitter {
|
|
|
44
44
|
...this.options
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Get the map feature associated with the popup.
|
|
49
|
+
*/
|
|
47
50
|
get feature() {
|
|
48
51
|
return this._feature;
|
|
49
52
|
}
|
|
@@ -57,7 +60,7 @@ class MapPopup extends EventEmitter {
|
|
|
57
60
|
}
|
|
58
61
|
}
|
|
59
62
|
/**
|
|
60
|
-
* Move the popup to
|
|
63
|
+
* Move the popup to the specified location.
|
|
61
64
|
*
|
|
62
65
|
* @param lngLat New location.
|
|
63
66
|
*/
|
|
@@ -107,6 +110,9 @@ class MapPopup extends EventEmitter {
|
|
|
107
110
|
this.renderContent(null);
|
|
108
111
|
}
|
|
109
112
|
}
|
|
113
|
+
/**
|
|
114
|
+
* Returns CSS classes to add to the popup.
|
|
115
|
+
*/
|
|
110
116
|
cssClasses() {
|
|
111
117
|
const classes = ["flow-map-popup"];
|
|
112
118
|
if (isStringFull(this.options.class)) {
|
|
@@ -117,6 +123,14 @@ class MapPopup extends EventEmitter {
|
|
|
117
123
|
}
|
|
118
124
|
return classes;
|
|
119
125
|
}
|
|
126
|
+
/**
|
|
127
|
+
* Update the popup in response to feature data changes.
|
|
128
|
+
*
|
|
129
|
+
* The popup may be moved or re-rendered depending on the nature of the
|
|
130
|
+
* feature changes.
|
|
131
|
+
*
|
|
132
|
+
* @param feature Map feature associated with the popup
|
|
133
|
+
*/
|
|
120
134
|
handleFeatureUpdate(feature) {
|
|
121
135
|
if (!feature) {
|
|
122
136
|
this.close();
|
|
@@ -134,6 +148,9 @@ class MapPopup extends EventEmitter {
|
|
|
134
148
|
}
|
|
135
149
|
this.renderContent(feature);
|
|
136
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* Called when the popup is closed to clean up resources.
|
|
153
|
+
*/
|
|
137
154
|
popupCloseHandler() {
|
|
138
155
|
if (this._featureStreamDisposer) {
|
|
139
156
|
this._featureStreamDisposer();
|
|
@@ -142,6 +159,14 @@ class MapPopup extends EventEmitter {
|
|
|
142
159
|
this.emit("closed", this);
|
|
143
160
|
this.removeAllListeners();
|
|
144
161
|
}
|
|
162
|
+
/**
|
|
163
|
+
* Render the popup content.
|
|
164
|
+
*
|
|
165
|
+
* If the content is defined using a template function, the function is called passing
|
|
166
|
+
* in the associated map feature and the reactive state store.
|
|
167
|
+
*
|
|
168
|
+
* @param feature The map feature associated with the popup.
|
|
169
|
+
*/
|
|
145
170
|
renderContent(feature) {
|
|
146
171
|
const state = this.map.context.state;
|
|
147
172
|
const content = isFunction(this.options.content) ? this.options.content(feature, state) : this.options.content;
|