@emuanalytics/flow-rdf-core 4.0.3 → 4.0.5
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/README.md +1 -64
- package/dist/index.d.mts +36 -31
- package/dist/index.d.ts +36 -31
- package/dist/index.mjs +20 -3249
- package/dist/lib/context/context-registry.mjs +63 -0
- package/dist/lib/context/debugger.mjs +240 -0
- package/dist/lib/context/flow-context.mjs +61 -0
- package/dist/lib/context/global-state.mjs +70 -0
- package/dist/lib/context/map-registry.mjs +59 -0
- package/dist/lib/context/query-registry.mjs +53 -0
- package/dist/lib/errors/flow-error.mjs +10 -0
- package/dist/lib/errors/flow-expression-error.mjs +14 -0
- package/dist/lib/errors/flow-map-error.mjs +9 -0
- package/dist/lib/expressions/expression-parser.mjs +198 -0
- package/dist/lib/expressions/functions/from-palette.mjs +75 -0
- package/dist/lib/expressions/functions/regex.mjs +25 -0
- package/dist/lib/expressions/functions/state.mjs +25 -0
- package/dist/lib/expressions/parser-helpers.mjs +37 -0
- package/dist/lib/expressions/reactive-expression.mjs +253 -0
- package/dist/lib/map/flow-core-map.mjs +749 -0
- package/dist/lib/map/plugins/annotations/annotation-layers.mjs +163 -0
- package/dist/lib/map/plugins/annotations/annotation.events.mjs +27 -0
- package/dist/lib/map/plugins/annotations/annotation.plugin.mjs +498 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/modes/DirectModeOverride.mjs +67 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/modes/DragCircleMode.mjs +65 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/utils/create_supplementary_points_circle.mjs +15 -0
- package/dist/lib/map/plugins/annotations/maplibre-gl-draw-circle/lib/utils/drag_pan.mjs +20 -0
- package/dist/lib/map/plugins/data-join.plugin.mjs +173 -0
- package/dist/lib/map/plugins/export/export.plugin.mjs +21 -0
- package/dist/lib/map/plugins/export/map-exporter.mjs +177 -0
- package/dist/lib/map/plugins/hover-effect.plugin.mjs +58 -0
- package/dist/lib/map/plugins/index.d.mts +37 -72
- package/dist/lib/map/plugins/index.d.ts +37 -72
- package/dist/lib/map/plugins/index.mjs +11 -1811
- package/dist/lib/map/plugins/keyboard-shortcut-manager.mjs +120 -0
- package/dist/lib/map/plugins/map-plugin-base.mjs +25 -0
- package/dist/lib/map/plugins/plugin-manager.mjs +135 -0
- package/dist/lib/map/plugins/popup.plugin.mjs +55 -0
- package/dist/lib/map/plugins/selection/selection.events.mjs +21 -0
- package/dist/lib/map/plugins/selection/selection.plugin.mjs +136 -0
- package/dist/lib/map/plugins/selection/source-selection.mjs +156 -0
- package/dist/lib/map/plugins/url-sync.plugin.mjs +55 -0
- package/dist/lib/map/support/image-registry.mjs +82 -0
- package/dist/lib/map/support/map-events.mjs +71 -0
- package/dist/lib/map/support/map-feature-stream.mjs +158 -0
- package/dist/lib/map/support/map-popup.mjs +159 -0
- package/dist/lib/map/types.mjs +7 -0
- package/dist/lib/map-style/map-data-source.mjs +192 -0
- package/dist/lib/map-style/map-layer-style-builder-generated.mjs +1307 -0
- package/dist/lib/map-style/map-layer-style-builder.mjs +86 -0
- package/dist/lib/map-style/map-layer.mjs +223 -0
- package/dist/lib/map-style/map-style.mjs +178 -0
- package/dist/lib/queries/async-committer.mjs +30 -0
- package/dist/lib/queries/query.mjs +172 -0
- package/dist/lib/state-store/decorators.mjs +83 -0
- package/dist/lib/state-store/state-store.mjs +534 -0
- package/dist/lib/state-store/utils.mjs +7 -0
- package/dist/lib/utils/announce.mjs +12 -0
- package/dist/lib/utils/assert.mjs +12 -0
- package/dist/{shared/flow-rdf-core.187cb345.mjs → lib/utils/debounce.mjs} +3 -39
- package/dist/lib/utils/decorators.mjs +59 -0
- package/dist/lib/utils/deferred.mjs +11 -0
- package/dist/lib/utils/ensure-array.mjs +16 -0
- package/dist/{shared/flow-rdf-core.1f0901c5.mjs → lib/utils/feature-type-checks.mjs} +2 -2
- package/dist/lib/utils/functional.mjs +22 -0
- package/dist/lib/utils/global.mjs +18 -0
- package/dist/lib/utils/index.mjs +8 -5
- package/dist/lib/utils/random-id.mjs +9 -0
- package/dist/lib/utils/string-utils.mjs +3 -0
- package/dist/lib/utils/throttle.mjs +20 -0
- package/dist/{shared/flow-rdf-core.25e5f6d7.mjs → lib/utils/type-checks.mjs} +1 -26
- package/dist/lib/utils/watchable-promise.mjs +55 -0
- package/dist/package.json.mjs +3 -0
- package/dist/shared/{flow-rdf-core.4dde996e.d.cts → flow-rdf-core.8b5acf14.d.mts} +1315 -172
- package/dist/shared/{flow-rdf-core.4dde996e.d.ts → flow-rdf-core.8b5acf14.d.ts} +1315 -172
- package/package.json +4 -8
- package/dist/index.cjs +0 -3316
- package/dist/index.d.cts +0 -90
- package/dist/lib/map/plugins/index.cjs +0 -1851
- package/dist/lib/map/plugins/index.d.cts +0 -345
- package/dist/lib/utils/index.cjs +0 -48
- package/dist/lib/utils/index.d.cts +0 -160
- package/dist/shared/flow-rdf-core.06e85a6c.cjs +0 -1394
- package/dist/shared/flow-rdf-core.4dde996e.d.mts +0 -1916
- package/dist/shared/flow-rdf-core.55c169ca.cjs +0 -150
- package/dist/shared/flow-rdf-core.63162669.d.cts +0 -10
- package/dist/shared/flow-rdf-core.65045caf.mjs +0 -1360
- package/dist/shared/flow-rdf-core.a82b9be4.cjs +0 -34
- package/dist/shared/flow-rdf-core.f50908f0.cjs +0 -102
|
@@ -0,0 +1,749 @@
|
|
|
1
|
+
import maplibre from '@emuanalytics/maplibre-gl';
|
|
2
|
+
import Debug from 'debug';
|
|
3
|
+
import EventEmitter from 'eventemitter3';
|
|
4
|
+
import { runInAction } from 'mobx';
|
|
5
|
+
import { toStream } from 'mobx-utils';
|
|
6
|
+
import { FlowMapError } from '../errors/flow-map-error.mjs';
|
|
7
|
+
import { assert } from '../utils/assert.mjs';
|
|
8
|
+
import { debounceWithArgs } from '../utils/debounce.mjs';
|
|
9
|
+
import { randomId } from '../utils/random-id.mjs';
|
|
10
|
+
import { throttle } from '../utils/throttle.mjs';
|
|
11
|
+
import { isPlainObject, isNil, isDefined, isStringFull, isUndefined, isURL, isString } from '../utils/type-checks.mjs';
|
|
12
|
+
import { PluginManager } from './plugins/plugin-manager.mjs';
|
|
13
|
+
import { ImageRegistry } from './support/image-registry.mjs';
|
|
14
|
+
import { MapStyleChangeEvent, MapSourceEvent, MapEvent, MapLoadingEvent, MapMouseEvent } from './support/map-events.mjs';
|
|
15
|
+
import { MapFeatureStream } from './support/map-feature-stream.mjs';
|
|
16
|
+
import { MapStyle } from '../map-style/map-style.mjs';
|
|
17
|
+
import { MapLayer } from '../map-style/map-layer.mjs';
|
|
18
|
+
import { MapDataSource } from '../map-style/map-data-source.mjs';
|
|
19
|
+
import { getGlobalState } from '../context/global-state.mjs';
|
|
20
|
+
import { FlowCoreError } from '../errors/flow-error.mjs';
|
|
21
|
+
|
|
22
|
+
var __defProp = Object.defineProperty;
|
|
23
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
24
|
+
var __publicField = (obj, key, value) => {
|
|
25
|
+
__defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
26
|
+
return value;
|
|
27
|
+
};
|
|
28
|
+
const debug = Debug("flow:map");
|
|
29
|
+
const MOUSE_MOVE_THROTTLE_DURATION = 100;
|
|
30
|
+
const _FlowCoreMap = class _FlowCoreMap extends EventEmitter {
|
|
31
|
+
/**
|
|
32
|
+
* Constructs a new `FlowCoreMap` instance.
|
|
33
|
+
*
|
|
34
|
+
* The map will be initialized with the specified options and base style.
|
|
35
|
+
* When the map API is ready for use, the {@link initialized} promise will resolve.
|
|
36
|
+
*
|
|
37
|
+
* @example
|
|
38
|
+
* ```js
|
|
39
|
+
* const map = new FlowCoreMap({
|
|
40
|
+
* id: 'my-map',
|
|
41
|
+
* container: '#map-container',
|
|
42
|
+
* baseStyle: 'fjord',
|
|
43
|
+
* });
|
|
44
|
+
*
|
|
45
|
+
* map.initialized.then(() => {
|
|
46
|
+
* console.log('Map initialized');
|
|
47
|
+
* // Configure map data sources and layers
|
|
48
|
+
* ...
|
|
49
|
+
* });
|
|
50
|
+
* ```
|
|
51
|
+
*
|
|
52
|
+
*/
|
|
53
|
+
constructor(options) {
|
|
54
|
+
super();
|
|
55
|
+
__publicField(this, "_context");
|
|
56
|
+
__publicField(this, "_initialized");
|
|
57
|
+
__publicField(this, "_loading", true);
|
|
58
|
+
__publicField(this, "_debouncedEmitSourceEvent");
|
|
59
|
+
__publicField(this, "_imageRegistry", new ImageRegistry());
|
|
60
|
+
__publicField(this, "_options");
|
|
61
|
+
__publicField(this, "_pluginManager");
|
|
62
|
+
__publicField(this, "_resolveInitialized", () => {
|
|
63
|
+
});
|
|
64
|
+
__publicField(this, "_style");
|
|
65
|
+
__publicField(this, "_styleUpdateSubscription", null);
|
|
66
|
+
__publicField(this, "_throttledMouseMove", throttle(this.handleMouseMove, MOUSE_MOVE_THROTTLE_DURATION, { context: this }));
|
|
67
|
+
__publicField(this, "_maplibreMap");
|
|
68
|
+
__publicField(this, "updateMapStyle", (style) => {
|
|
69
|
+
const oldStyle = this._maplibreMap.getStyle();
|
|
70
|
+
const beforeEvent = new MapStyleChangeEvent("beforeStyleChanged", this, oldStyle, style);
|
|
71
|
+
style = this._pluginManager.forwardStyleChangeEvent(
|
|
72
|
+
new MapStyleChangeEvent("beforeStyleChanged", this, oldStyle, style)
|
|
73
|
+
);
|
|
74
|
+
this.emit(beforeEvent.type, beforeEvent);
|
|
75
|
+
debug(`${this.formattedName}: Updating style`, style);
|
|
76
|
+
this._maplibreMap.setStyle(style);
|
|
77
|
+
this.emitEvent(new MapStyleChangeEvent("styleChanged", this, oldStyle, style));
|
|
78
|
+
});
|
|
79
|
+
this._options = {
|
|
80
|
+
id: randomId("map"),
|
|
81
|
+
..._FlowCoreMap._defaultOptions,
|
|
82
|
+
...options
|
|
83
|
+
};
|
|
84
|
+
this._style = new MapStyle();
|
|
85
|
+
this._debouncedEmitSourceEvent = debounceWithArgs(this.emitEvent, 20, {
|
|
86
|
+
hash: (event) => event.source,
|
|
87
|
+
context: this
|
|
88
|
+
});
|
|
89
|
+
this._initialized = new Promise((resolve) => {
|
|
90
|
+
this._resolveInitialized = resolve;
|
|
91
|
+
});
|
|
92
|
+
this._pluginManager = new PluginManager(this);
|
|
93
|
+
this.initialize();
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Return the underlying [Maplibre GL JS](https://maplibre.org/maplibre-gl-js/docs/) map instance. This can be used to
|
|
97
|
+
* access the full Maplibre API for advanced use cases.
|
|
98
|
+
*
|
|
99
|
+
* :::danger
|
|
100
|
+
* Use with caution. Directly modifying the Maplibre map instance can cause unexpected behavior particularly if
|
|
101
|
+
* the changes conflict with the {@link FlowCoreMap} state. Do not call any Maplibre methods that add or
|
|
102
|
+
* remove map layers or data sources, or modify the map style directly.
|
|
103
|
+
* :::
|
|
104
|
+
*/
|
|
105
|
+
get maplibreMap() {
|
|
106
|
+
return this._maplibreMap;
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Returns the {@link FlowContext} associated with this map.
|
|
110
|
+
*/
|
|
111
|
+
get context() {
|
|
112
|
+
return this._context;
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Returns the map's container element.
|
|
116
|
+
*/
|
|
117
|
+
get container() {
|
|
118
|
+
return this._maplibreMap.getContainer();
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* Returns the map identifier.
|
|
122
|
+
*/
|
|
123
|
+
get id() {
|
|
124
|
+
return this._options.id;
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Returns a promise that resolves when the map is fully initialized and ready for use.
|
|
128
|
+
*
|
|
129
|
+
* @example
|
|
130
|
+
* ```js
|
|
131
|
+
* map.initialized.then(() => {
|
|
132
|
+
* console.log('Map ready fo use');
|
|
133
|
+
* // Configure map data sources and layers
|
|
134
|
+
* ...
|
|
135
|
+
* });
|
|
136
|
+
* ```
|
|
137
|
+
*/
|
|
138
|
+
get initialized() {
|
|
139
|
+
return this._initialized;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Returns the full Maplibre style specification for this map. This is a `computed` property and will trigger
|
|
143
|
+
* reactive updates when accessed in a reactive context.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```js
|
|
147
|
+
* stateStore.autorun(() => {
|
|
148
|
+
* const style = map.styleSpecification;
|
|
149
|
+
* console.log('Map style was updated', style);
|
|
150
|
+
* });
|
|
151
|
+
* ```
|
|
152
|
+
*/
|
|
153
|
+
get styleSpecification() {
|
|
154
|
+
return this._style.styleSpecification;
|
|
155
|
+
}
|
|
156
|
+
/**
|
|
157
|
+
* @internal
|
|
158
|
+
*
|
|
159
|
+
* Used by map export to get images added to map.
|
|
160
|
+
*/
|
|
161
|
+
get imageRegistry() {
|
|
162
|
+
return this._imageRegistry;
|
|
163
|
+
}
|
|
164
|
+
addLayer(layerOrOptions) {
|
|
165
|
+
const layer = isPlainObject(layerOrOptions) ? new MapLayer(layerOrOptions) : layerOrOptions;
|
|
166
|
+
this._style.addLayer(layer);
|
|
167
|
+
return layer;
|
|
168
|
+
}
|
|
169
|
+
addSource(sourceOrOptions) {
|
|
170
|
+
const source = isPlainObject(sourceOrOptions) ? new MapDataSource(sourceOrOptions) : sourceOrOptions;
|
|
171
|
+
this.emitEvent(new MapSourceEvent("beforeSourceAdded", this, source.id));
|
|
172
|
+
source.setFlowEndpoint({
|
|
173
|
+
host: this.context.flowClient.baseUrl,
|
|
174
|
+
apiKey: this.context.flowClient.apiKey || void 0
|
|
175
|
+
});
|
|
176
|
+
this._style.addSource(source);
|
|
177
|
+
this.emitEvent(new MapSourceEvent("sourceAdded", this, source.id));
|
|
178
|
+
return source;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Destroys the map instance and removes it from the DOM.
|
|
182
|
+
*/
|
|
183
|
+
destroy() {
|
|
184
|
+
this._pluginManager.removeAllPlugins();
|
|
185
|
+
this._styleUpdateSubscription?.unsubscribe();
|
|
186
|
+
this._maplibreMap?.remove();
|
|
187
|
+
if (this.context) {
|
|
188
|
+
this.context.maps.unregisterMap(this);
|
|
189
|
+
}
|
|
190
|
+
this.emit("destroyed", new MapEvent("destroyed", this));
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Called internally and by map plugins to notify an error.
|
|
194
|
+
*
|
|
195
|
+
* To handle errors, attach an error event listener to the map instance as follows:
|
|
196
|
+
*
|
|
197
|
+
* ```js
|
|
198
|
+
* map.on('error', (error) => {
|
|
199
|
+
* // Handle error
|
|
200
|
+
* console.error(`A map error occurred: ${error.message}`);
|
|
201
|
+
* });
|
|
202
|
+
* ```
|
|
203
|
+
*
|
|
204
|
+
* If no error event listener is attached, the error will be logged to the console.
|
|
205
|
+
*/
|
|
206
|
+
emitError(message, originalError) {
|
|
207
|
+
const error = new FlowMapError(`${this.formattedName}: ${message}`, originalError);
|
|
208
|
+
this._pluginManager.forwardEvent("error", error);
|
|
209
|
+
if (this.listenerCount("error")) {
|
|
210
|
+
this.emit("error", error);
|
|
211
|
+
} else {
|
|
212
|
+
console.error(error);
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
/** Used internally and by plugins to emit a map event. */
|
|
216
|
+
emitEvent(event) {
|
|
217
|
+
this._pluginManager.forwardEvent(event.type, event);
|
|
218
|
+
this.emit(event.type, event);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Returns an observable {@link MapFeatureStream} for the specified feature identifiers. The feature stream
|
|
222
|
+
* will emit feature data for the specified features as their properties updated.
|
|
223
|
+
* Call the {@link MapFeatureStream.subscribe} method to receive feature data updates.
|
|
224
|
+
*
|
|
225
|
+
* :::warning
|
|
226
|
+
* All subscriptions should be disposed of when no longer needed.
|
|
227
|
+
* :::
|
|
228
|
+
*
|
|
229
|
+
* This method is used internally by the map popup plugin to receive feature properties for rendering
|
|
230
|
+
* popup content but can be used whenever you want to track feature data changes for a set of features.
|
|
231
|
+
*
|
|
232
|
+
* :::warning
|
|
233
|
+
* The feature stream will only emit feature data for features that are currently visible on the map. If the
|
|
234
|
+
* user pans the map away from the requested features, the map will eventually unload the features and will
|
|
235
|
+
* stop providing updates.
|
|
236
|
+
* :::
|
|
237
|
+
*
|
|
238
|
+
* @example
|
|
239
|
+
* ```js
|
|
240
|
+
* const subscription = map.featureStream({
|
|
241
|
+
* source: 'my-data-source',
|
|
242
|
+
* featureIds: ['feature-1', 'feature-2']
|
|
243
|
+
* }).subscribe((featureData) => {
|
|
244
|
+
* console.log('Feature data updated', featureData);
|
|
245
|
+
* });
|
|
246
|
+
* ...
|
|
247
|
+
* subscription.unsubscribe();
|
|
248
|
+
* ```
|
|
249
|
+
*
|
|
250
|
+
* @param options
|
|
251
|
+
* @returns
|
|
252
|
+
*/
|
|
253
|
+
featureStream(options) {
|
|
254
|
+
return new MapFeatureStream({ map: this, ...options });
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Returns the feature state for the specified map feature.
|
|
258
|
+
*
|
|
259
|
+
* See ##TODO for more information about map feature state.
|
|
260
|
+
*
|
|
261
|
+
* @param feature - Map feature identifier
|
|
262
|
+
*/
|
|
263
|
+
getFeatureState(feature) {
|
|
264
|
+
return this._maplibreMap.getFeatureState(feature);
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Returns the {@link MapLayer} with the specified ID.
|
|
268
|
+
*
|
|
269
|
+
* @param id - Map layer ID
|
|
270
|
+
*/
|
|
271
|
+
getLayer(id) {
|
|
272
|
+
return this._style.getLayer(id);
|
|
273
|
+
}
|
|
274
|
+
/**
|
|
275
|
+
* Returns all map layers that match the specified filter function.
|
|
276
|
+
* If no filter is specified, all layers are returned.
|
|
277
|
+
*
|
|
278
|
+
* @param filter - Filter function. Return `true` to include the map layer in the result.
|
|
279
|
+
*/
|
|
280
|
+
getLayers(filter) {
|
|
281
|
+
return this._style.getLayers(filter);
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Returns a registered map plugin.
|
|
285
|
+
*
|
|
286
|
+
* @example
|
|
287
|
+
* Retrieve the popup plugin from a map:
|
|
288
|
+
* ```js
|
|
289
|
+
* const popupPlugin = map.getPlugin(PopupPlugin);
|
|
290
|
+
* popupPlugin.registerPopup('my-popup', {...});
|
|
291
|
+
* ```
|
|
292
|
+
*
|
|
293
|
+
* @param pluginOrName - Plugin constructor or name as a string.
|
|
294
|
+
*/
|
|
295
|
+
getPlugin(pluginOrName) {
|
|
296
|
+
return this._pluginManager.getPlugin(pluginOrName);
|
|
297
|
+
}
|
|
298
|
+
/**
|
|
299
|
+
* Returns the {@link MapDataSource} with the specified ID.
|
|
300
|
+
*
|
|
301
|
+
* @param id - Data source ID
|
|
302
|
+
*/
|
|
303
|
+
getSource(id) {
|
|
304
|
+
return this._style.getSource(id);
|
|
305
|
+
}
|
|
306
|
+
/**
|
|
307
|
+
* Returns all {@link MapDataSource} instance that match the specified filter function.
|
|
308
|
+
* If no filter is specified, all data sources are returned.
|
|
309
|
+
*
|
|
310
|
+
* @param filter - Filter function. Return `true` to include the map data source in the result.
|
|
311
|
+
*/
|
|
312
|
+
getSources(filter) {
|
|
313
|
+
return this._style.getSources(filter);
|
|
314
|
+
}
|
|
315
|
+
/**
|
|
316
|
+
* Register a map icon image.
|
|
317
|
+
*
|
|
318
|
+
* The image can be used on [symbol-type map layers](/flow-rdf-core/layer-reference/symbol) by specifying the image ID
|
|
319
|
+
* as the [`icon-image`](/flow-rdf-core/layer-reference/symbol#icon-image) property.
|
|
320
|
+
*
|
|
321
|
+
* @param id Image ID.
|
|
322
|
+
* @param src Image source URL.
|
|
323
|
+
* @param width Image display width.
|
|
324
|
+
* @param height Image display height.
|
|
325
|
+
* @param pixelDensity Image pixel density. Defaults to `2` - the source image should be @2x resolution to enable better quality rendering on high-resolution displays.
|
|
326
|
+
* @param recolor Set to `true` to allow recoloring of map icon image using map layer style properties.
|
|
327
|
+
*/
|
|
328
|
+
registerImage(id, src, width, height, pixelDensity = 2, recolor = false) {
|
|
329
|
+
debug(`${this.formattedName}: Registering image '${id}'`);
|
|
330
|
+
this._imageRegistry.registerImage(id, src, width, height, pixelDensity, recolor);
|
|
331
|
+
}
|
|
332
|
+
/**
|
|
333
|
+
* Remove feature state properties from a map feature.
|
|
334
|
+
*
|
|
335
|
+
* @param feature Feature to modify. Specify `null` to modify all map features.
|
|
336
|
+
* @param key Feature state key to remove. Specify `null` to remove all feature state for specified feature(s).
|
|
337
|
+
*/
|
|
338
|
+
removeFeatureState(feature, key, fireEvent = true) {
|
|
339
|
+
if (isNil(feature.id) && isDefined(key)) {
|
|
340
|
+
const style = this._maplibreMap.style;
|
|
341
|
+
style._checkLoaded();
|
|
342
|
+
const sourceId = feature.source;
|
|
343
|
+
const sourceCache = style.sourceCaches[sourceId];
|
|
344
|
+
if (sourceCache === void 0) {
|
|
345
|
+
throw new FlowCoreError(`${this.formattedName}: the source '${sourceId}' does not exist in the map's style.`);
|
|
346
|
+
}
|
|
347
|
+
const sourceState = sourceCache._state;
|
|
348
|
+
const sourceType = sourceCache.getSource().type;
|
|
349
|
+
const sourceLayer = sourceType === "vector" ? feature.sourceLayer ?? "geometry" : "_geojsonTileLayer";
|
|
350
|
+
sourceState.deletedStates[sourceLayer] = sourceState.deletedStates[sourceLayer] || {};
|
|
351
|
+
const sourceLayerDeleted = sourceState.deletedStates[sourceLayer] === null;
|
|
352
|
+
if (sourceLayerDeleted) {
|
|
353
|
+
return;
|
|
354
|
+
}
|
|
355
|
+
if (sourceState.state[sourceLayer]) {
|
|
356
|
+
Object.keys(sourceState.state[sourceLayer]).forEach((feature2) => {
|
|
357
|
+
if (sourceState.deletedStates[sourceLayer][feature2] !== null) {
|
|
358
|
+
sourceState.deletedStates[sourceLayer][feature2] = sourceState.deletedStates[sourceLayer][feature2] || {};
|
|
359
|
+
sourceState.deletedStates[sourceLayer][feature2][key] = null;
|
|
360
|
+
}
|
|
361
|
+
});
|
|
362
|
+
}
|
|
363
|
+
this._maplibreMap._update();
|
|
364
|
+
} else {
|
|
365
|
+
this._maplibreMap.removeFeatureState(feature, key);
|
|
366
|
+
}
|
|
367
|
+
if (fireEvent) {
|
|
368
|
+
const sourceEvent = new MapSourceEvent("sourceFeatureState", this, feature.source);
|
|
369
|
+
this._debouncedEmitSourceEvent(sourceEvent);
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
/**
|
|
373
|
+
* Remove the specified {@link MapLayer} from the map.
|
|
374
|
+
* @param id - Map layer ID
|
|
375
|
+
*/
|
|
376
|
+
removeLayer(id) {
|
|
377
|
+
this._style.removeLayer(id);
|
|
378
|
+
return this;
|
|
379
|
+
}
|
|
380
|
+
/**
|
|
381
|
+
* Remove the specified {@link MapDataSource} from the map.
|
|
382
|
+
* @param id - Map data source ID
|
|
383
|
+
*/
|
|
384
|
+
removeSource(id, removeLayers = false) {
|
|
385
|
+
this.emitEvent(new MapSourceEvent("beforeSourceRemoved", this, id));
|
|
386
|
+
this._style.removeSource(id, removeLayers);
|
|
387
|
+
this.emitEvent(new MapSourceEvent("sourceRemoved", this, id));
|
|
388
|
+
return this;
|
|
389
|
+
}
|
|
390
|
+
/**
|
|
391
|
+
* Set the map's base style.
|
|
392
|
+
*
|
|
393
|
+
* ##TODO list of available base styles
|
|
394
|
+
*
|
|
395
|
+
* @param style - A style identifier or a Maplibre style specification.
|
|
396
|
+
*/
|
|
397
|
+
async setBaseStyle(style) {
|
|
398
|
+
const baseStyle = await this.loadBaseStyle(style);
|
|
399
|
+
this._style.setBaseStyle(baseStyle);
|
|
400
|
+
}
|
|
401
|
+
/**
|
|
402
|
+
* Returns the map's current center, bearing, pitch, and zoom level.
|
|
403
|
+
*/
|
|
404
|
+
getCenter() {
|
|
405
|
+
const lngLat = this._maplibreMap.getCenter();
|
|
406
|
+
const zoom = this._maplibreMap.getZoom();
|
|
407
|
+
const bearing = this._maplibreMap.getBearing();
|
|
408
|
+
const pitch = this._maplibreMap.getPitch();
|
|
409
|
+
return {
|
|
410
|
+
lng: lngLat.lng,
|
|
411
|
+
lat: lngLat.lat,
|
|
412
|
+
zoom,
|
|
413
|
+
bearing,
|
|
414
|
+
pitch
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Sets one or more of the map's center, bearing, pitch, and/or zoom level.
|
|
419
|
+
* Animation options can be specified to animate the transition. If animation options are not specified,
|
|
420
|
+
* the map will jump to the new view immediately.
|
|
421
|
+
*
|
|
422
|
+
* A promise is returned that resolves when the map has finished moving.
|
|
423
|
+
*
|
|
424
|
+
* @example
|
|
425
|
+
* Zoom the map and log to console when finished:
|
|
426
|
+
* ```js
|
|
427
|
+
* const { zoom } = map.getCenter();
|
|
428
|
+
* map.setCenter({zoom: zoom + 2}, {mode: 'ease', duration: 2000}).then(() => {
|
|
429
|
+
* console.log('Map zoomed ');
|
|
430
|
+
* });
|
|
431
|
+
* ```
|
|
432
|
+
*
|
|
433
|
+
* @param center
|
|
434
|
+
* @param animationOptions
|
|
435
|
+
* @returns A promise that resolves when the map has finished moving.
|
|
436
|
+
*/
|
|
437
|
+
setCenter(center, animationOptions) {
|
|
438
|
+
const lngLat = isDefined(center.lng) && isDefined(center.lat) ? new maplibre.LngLat(center.lng, center.lat) : void 0;
|
|
439
|
+
let method = this._maplibreMap.jumpTo;
|
|
440
|
+
if (animationOptions) {
|
|
441
|
+
switch (animationOptions.mode) {
|
|
442
|
+
case "ease":
|
|
443
|
+
method = this._maplibreMap.easeTo;
|
|
444
|
+
break;
|
|
445
|
+
case "fly":
|
|
446
|
+
method = this._maplibreMap.flyTo;
|
|
447
|
+
break;
|
|
448
|
+
default:
|
|
449
|
+
method = this._maplibreMap.jumpTo;
|
|
450
|
+
}
|
|
451
|
+
}
|
|
452
|
+
const options = {
|
|
453
|
+
center: lngLat,
|
|
454
|
+
...isDefined(center.zoom) && { zoom: center.zoom },
|
|
455
|
+
...isDefined(center.bearing) && { bearing: center.bearing },
|
|
456
|
+
...isDefined(center.pitch) && { pitch: center.pitch },
|
|
457
|
+
...animationOptions
|
|
458
|
+
};
|
|
459
|
+
const promise = new Promise((resolve) => {
|
|
460
|
+
this.once("moveEnd", () => resolve());
|
|
461
|
+
});
|
|
462
|
+
method.call(this._maplibreMap, options);
|
|
463
|
+
return promise;
|
|
464
|
+
}
|
|
465
|
+
/**
|
|
466
|
+
* Returns the map's current bounds in degrees as an array of `[west, south, east, north]`.
|
|
467
|
+
*
|
|
468
|
+
* See also {@link getVisibleBounds}.
|
|
469
|
+
*/
|
|
470
|
+
getBounds() {
|
|
471
|
+
const bounds = this._maplibreMap.getBounds();
|
|
472
|
+
return [bounds.getWest(), bounds.getSouth(), bounds.getEast(), bounds.getNorth()];
|
|
473
|
+
}
|
|
474
|
+
/**
|
|
475
|
+
* Set the map's bounds to the specified bounding box.
|
|
476
|
+
*
|
|
477
|
+
* Animation options can be specified to animate the transition. If animation options are not specified,
|
|
478
|
+
* the map will jump to the new view immediately.
|
|
479
|
+
*
|
|
480
|
+
* Specify the `padding` option to add padding around the bounding box in pixels.
|
|
481
|
+
*
|
|
482
|
+
* @param bounds - Bounding box in degrees as an array of `[west, south, east, north]`.
|
|
483
|
+
* @param options - Animation and padding options.
|
|
484
|
+
*/
|
|
485
|
+
setBounds(bounds, options) {
|
|
486
|
+
const fitBoundsOptions = {
|
|
487
|
+
linear: !options || options.mode === "ease" || options.mode === "jump",
|
|
488
|
+
animate: !!options && options.mode !== "jump",
|
|
489
|
+
...options
|
|
490
|
+
};
|
|
491
|
+
this._maplibreMap.fitBounds(bounds, fitBoundsOptions);
|
|
492
|
+
}
|
|
493
|
+
/**
|
|
494
|
+
* Returns the map's current visible bounds in degrees as an array of `[west, south, east, north]`.
|
|
495
|
+
*
|
|
496
|
+
* The visible bounds may differ from the map's full bounds if fixed positioning is used. With fixed positioning,
|
|
497
|
+
* the map's canvas extends outside its container underneath other elements like side-bars. This allows side-bars
|
|
498
|
+
* to be opened and closed without re-rendering the map. The visible bounds represent the region of the map that
|
|
499
|
+
* is currently visible within the map's container.
|
|
500
|
+
*
|
|
501
|
+
* See also {@link getBounds}.
|
|
502
|
+
*/
|
|
503
|
+
getVisibleBounds() {
|
|
504
|
+
const parentEl = this.container.parentElement;
|
|
505
|
+
if (!parentEl) {
|
|
506
|
+
return this.getBounds();
|
|
507
|
+
}
|
|
508
|
+
const mapRect = parentEl.getBoundingClientRect();
|
|
509
|
+
const topLeft = this._maplibreMap.unproject([mapRect.x, mapRect.y]);
|
|
510
|
+
const bottomRight = this._maplibreMap.unproject([mapRect.x + mapRect.width, mapRect.y + mapRect.height]);
|
|
511
|
+
const bounds = [topLeft.lng, bottomRight.lat, bottomRight.lng, topLeft.lat];
|
|
512
|
+
return bounds;
|
|
513
|
+
}
|
|
514
|
+
/**
|
|
515
|
+
* Set feature state properties for a map feature.
|
|
516
|
+
*
|
|
517
|
+
* ##TODO more info on feature state
|
|
518
|
+
*
|
|
519
|
+
* @param feature Feature to modify.
|
|
520
|
+
* @param state Feature state properties to set.
|
|
521
|
+
*/
|
|
522
|
+
setFeatureState(feature, state, fireEvent = true) {
|
|
523
|
+
const source = this.getSource(feature.source);
|
|
524
|
+
assert(
|
|
525
|
+
isDefined(source.featureId),
|
|
526
|
+
`${this.formattedName}: cannot set feature state (source '${source.id}' has not featureId specified)`
|
|
527
|
+
);
|
|
528
|
+
if (source?.type === "vector") {
|
|
529
|
+
feature.sourceLayer = feature.sourceLayer || "geometry";
|
|
530
|
+
}
|
|
531
|
+
this._maplibreMap.setFeatureState(feature, state);
|
|
532
|
+
if (fireEvent) {
|
|
533
|
+
const sourceEvent = new MapSourceEvent("sourceFeatureState", this, feature.source);
|
|
534
|
+
this._debouncedEmitSourceEvent(sourceEvent);
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
/**
|
|
538
|
+
* Set feature state properties in bulk for multiple map features.
|
|
539
|
+
*
|
|
540
|
+
* ##TODO more info on feature state
|
|
541
|
+
*
|
|
542
|
+
* @param feature Feature to modify.
|
|
543
|
+
* @param state Feature state properties to set.
|
|
544
|
+
*/
|
|
545
|
+
setFeatureStateBulk(sourceId, featureState) {
|
|
546
|
+
const source = this.getSource(sourceId.source);
|
|
547
|
+
assert(isStringFull(source.featureId));
|
|
548
|
+
this._maplibreMap.style._checkLoaded();
|
|
549
|
+
const sourceCache = this._maplibreMap.style.sourceCaches[sourceId.source];
|
|
550
|
+
if (isUndefined(sourceCache)) {
|
|
551
|
+
throw new FlowCoreError(
|
|
552
|
+
`${this.formattedName}: the source '${sourceId.source}' does not exist in the map's style.`
|
|
553
|
+
);
|
|
554
|
+
}
|
|
555
|
+
const sourceLayer = sourceCache.getSource().type === "vector" ? sourceId.sourceLayer ?? "geometry" : "";
|
|
556
|
+
Object.keys(featureState).forEach((featureId) => {
|
|
557
|
+
sourceCache.setFeatureState(sourceLayer, featureId, featureState[featureId]);
|
|
558
|
+
});
|
|
559
|
+
this._maplibreMap._update();
|
|
560
|
+
const sourceEvent = new MapSourceEvent("sourceFeatureState", this, sourceId.source);
|
|
561
|
+
this._debouncedEmitSourceEvent(sourceEvent);
|
|
562
|
+
}
|
|
563
|
+
/**
|
|
564
|
+
* Run a series of map updates as a single transaction. This can be used to batch multiple updates
|
|
565
|
+
* while only triggering a single re-render of the map.
|
|
566
|
+
*
|
|
567
|
+
* @example
|
|
568
|
+
* ```js
|
|
569
|
+
* map.transaction(() => {
|
|
570
|
+
* map.addSource({ ... });
|
|
571
|
+
* map.addLayer({ ... });
|
|
572
|
+
* map.addLayer({ ... });
|
|
573
|
+
* });
|
|
574
|
+
* ```
|
|
575
|
+
*
|
|
576
|
+
* @param fn - Function containing map updates to run as a transaction.
|
|
577
|
+
*/
|
|
578
|
+
transaction(fn) {
|
|
579
|
+
runInAction(() => fn());
|
|
580
|
+
}
|
|
581
|
+
/**
|
|
582
|
+
* Register a map plugin with the map instance.
|
|
583
|
+
*
|
|
584
|
+
* Some plugins may require additional options to be passed when registering the plugin.
|
|
585
|
+
*
|
|
586
|
+
* @example
|
|
587
|
+
* Register the HoverEffectPlugin with the map:
|
|
588
|
+
* ```js
|
|
589
|
+
* map.usePlugin(HoverEffectPlugin);
|
|
590
|
+
* ```
|
|
591
|
+
*
|
|
592
|
+
* @param plugin - Plugin constructor or name
|
|
593
|
+
* @param options - Plugin-specific options
|
|
594
|
+
* @returns The plugin instance
|
|
595
|
+
*/
|
|
596
|
+
usePlugin(plugin, options) {
|
|
597
|
+
return this._pluginManager.usePlugin(plugin, options);
|
|
598
|
+
}
|
|
599
|
+
/**
|
|
600
|
+
* Register multiple map plugins with the map instance.
|
|
601
|
+
*
|
|
602
|
+
* @example
|
|
603
|
+
* Register multiple plugins with the map:
|
|
604
|
+
* ```js
|
|
605
|
+
* map.usePlugins([HoverEffectPlugin, PopupPlugin]);
|
|
606
|
+
* ```
|
|
607
|
+
*
|
|
608
|
+
* @param plugins - Array of plugin constructors to register
|
|
609
|
+
*/
|
|
610
|
+
usePlugins(plugins) {
|
|
611
|
+
plugins.forEach((plugin) => this.usePlugin(plugin));
|
|
612
|
+
}
|
|
613
|
+
get formattedName() {
|
|
614
|
+
return `FlowMap<${this._options.id}>`;
|
|
615
|
+
}
|
|
616
|
+
bindMapEvents() {
|
|
617
|
+
this._maplibreMap.on("error", (e) => this.emitError(e.error.message, e.error));
|
|
618
|
+
this._maplibreMap.on("mousemove", this._throttledMouseMove);
|
|
619
|
+
this._maplibreMap.on("mouseout", this._throttledMouseMove);
|
|
620
|
+
this._maplibreMap.on("click", this.handleMouseClick("click").bind(this));
|
|
621
|
+
this._maplibreMap.on("dblclick", this.handleMouseClick("dblClick").bind(this));
|
|
622
|
+
this._maplibreMap.on("styleimagemissing", this.handleMissingImage.bind(this));
|
|
623
|
+
this._maplibreMap.on("sourcedata", this.handleLoading.bind(this));
|
|
624
|
+
this._maplibreMap.on("idle", this.handleLoading.bind(this));
|
|
625
|
+
const eventsToForward = ["moveEnd"];
|
|
626
|
+
eventsToForward.forEach((eventName) => {
|
|
627
|
+
this._maplibreMap.on(eventName.toLowerCase(), (event) => {
|
|
628
|
+
const flowEvent = MapEvent.fromMaplibreEvent(eventName, this, event);
|
|
629
|
+
this.emitEvent(flowEvent);
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
}
|
|
633
|
+
featureFromMaplibreFeature(feature) {
|
|
634
|
+
return {
|
|
635
|
+
id: feature.id,
|
|
636
|
+
geometry: feature.geometry,
|
|
637
|
+
source: feature.source,
|
|
638
|
+
sourceLayer: feature.sourceLayer,
|
|
639
|
+
layer: feature.layer.id,
|
|
640
|
+
properties: feature.properties,
|
|
641
|
+
state: feature.state
|
|
642
|
+
};
|
|
643
|
+
}
|
|
644
|
+
featureQueryBox(event) {
|
|
645
|
+
const queryBox = this._options.featureClickTolerance ? [
|
|
646
|
+
[event.point.x - this._options.featureClickTolerance, event.point.y - this._options.featureClickTolerance],
|
|
647
|
+
[event.point.x + this._options.featureClickTolerance, event.point.y + this._options.featureClickTolerance]
|
|
648
|
+
] : event.point;
|
|
649
|
+
return queryBox;
|
|
650
|
+
}
|
|
651
|
+
async handleLoading(event) {
|
|
652
|
+
let loading = false;
|
|
653
|
+
if (event.type === "sourcedata" && event.isSourceLoaded === false) {
|
|
654
|
+
loading = true;
|
|
655
|
+
} else if (event.type === "idle") {
|
|
656
|
+
loading = false;
|
|
657
|
+
} else {
|
|
658
|
+
loading = !!Object.values(this._maplibreMap.style.sourceCaches).find((cache) => !cache.loaded());
|
|
659
|
+
}
|
|
660
|
+
if (this._loading !== loading) {
|
|
661
|
+
const loadingEvent = new MapLoadingEvent(this, loading);
|
|
662
|
+
this.emitEvent(loadingEvent);
|
|
663
|
+
this._loading = loading;
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
async handleMissingImage(event) {
|
|
667
|
+
const imageInfo = this._imageRegistry.getImage(event.id);
|
|
668
|
+
if (imageInfo === this._imageRegistry.unknownImage) {
|
|
669
|
+
console.error(`${this.formattedName}: Image '${event.id}' not registered`);
|
|
670
|
+
}
|
|
671
|
+
if (!imageInfo.image.complete) {
|
|
672
|
+
imageInfo.image.onload = () => {
|
|
673
|
+
this._maplibreMap.updateImage(event.id, imageInfo.image);
|
|
674
|
+
this._maplibreMap.redraw();
|
|
675
|
+
};
|
|
676
|
+
}
|
|
677
|
+
this._maplibreMap.addImage(event.id, imageInfo.image, {
|
|
678
|
+
pixelRatio: imageInfo.pixelDensity,
|
|
679
|
+
sdf: imageInfo.recolor
|
|
680
|
+
});
|
|
681
|
+
}
|
|
682
|
+
handleMouseClick(type) {
|
|
683
|
+
return (event) => {
|
|
684
|
+
const layerIds = this._style.getLayers((layer) => layer.interactive).map((layer) => layer.id);
|
|
685
|
+
const features = this._maplibreMap.queryRenderedFeatures(this.featureQueryBox(event), { layers: layerIds }).map(this.featureFromMaplibreFeature.bind(this));
|
|
686
|
+
if (features.length) {
|
|
687
|
+
debug(`${this.formattedName} Feature ${type}ed`, features[0]);
|
|
688
|
+
}
|
|
689
|
+
const flowEvent = MapMouseEvent.fromMaplibreMouseEvent(type, event, this, features);
|
|
690
|
+
this.emitEvent(flowEvent);
|
|
691
|
+
};
|
|
692
|
+
}
|
|
693
|
+
handleMouseMove(event) {
|
|
694
|
+
if (event.type === "mousemove") {
|
|
695
|
+
const layerIds = this._style.getLayers((layer) => layer.interactive).map((layer) => layer.id);
|
|
696
|
+
const features = this._maplibreMap.queryRenderedFeatures(this.featureQueryBox(event), { layers: layerIds }).map(this.featureFromMaplibreFeature.bind(this));
|
|
697
|
+
const flowEvent = MapMouseEvent.fromMaplibreMouseEvent("mouseMove", event, this, features);
|
|
698
|
+
this.emitEvent(flowEvent);
|
|
699
|
+
} else if (event.type === "mouseout") {
|
|
700
|
+
const flowEvent = MapMouseEvent.fromMaplibreMouseEvent("mouseOut", event, this, []);
|
|
701
|
+
this.emitEvent(flowEvent);
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
async initialize() {
|
|
705
|
+
const globalContextRegistry = getGlobalState().globalContextRegistry;
|
|
706
|
+
this._context = await globalContextRegistry.getContext(this._options.contextId);
|
|
707
|
+
this._style.bindState(this._context.state);
|
|
708
|
+
const baseStyle = await this.loadBaseStyle(this._options.baseStyle);
|
|
709
|
+
this._style.setBaseStyle(baseStyle);
|
|
710
|
+
this._maplibreMap = new maplibre.Map({
|
|
711
|
+
...this._options,
|
|
712
|
+
transformRequest: this.transformMapRequest.bind(this),
|
|
713
|
+
style: baseStyle
|
|
714
|
+
});
|
|
715
|
+
this._maplibreMap.once("load", () => {
|
|
716
|
+
this._styleUpdateSubscription = toStream(() => this._style.styleSpecification, false).subscribe(
|
|
717
|
+
this.updateMapStyle
|
|
718
|
+
);
|
|
719
|
+
this.bindMapEvents();
|
|
720
|
+
this._resolveInitialized();
|
|
721
|
+
debug(`${this.formattedName}: initialized with options`, this._options);
|
|
722
|
+
this.context.maps.registerMap(this);
|
|
723
|
+
});
|
|
724
|
+
}
|
|
725
|
+
async loadBaseStyle(style) {
|
|
726
|
+
if (isURL(style)) {
|
|
727
|
+
return fetch(style).then((resp) => resp.json());
|
|
728
|
+
} else if (isString(style)) {
|
|
729
|
+
return this.context.flowClient.maps.get(style);
|
|
730
|
+
} else {
|
|
731
|
+
return style;
|
|
732
|
+
}
|
|
733
|
+
}
|
|
734
|
+
transformMapRequest(url, resourceType) {
|
|
735
|
+
const requestParameters = { url };
|
|
736
|
+
if (this.context.flowClient.accessToken && url.startsWith(this.context.flowClient.baseUrl)) {
|
|
737
|
+
requestParameters.headers = { Authorization: `Bearer ${this.context.flowClient.accessToken}` };
|
|
738
|
+
}
|
|
739
|
+
return requestParameters;
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
__publicField(_FlowCoreMap, "_defaultOptions", {
|
|
743
|
+
boxZoom: false,
|
|
744
|
+
doubleClickZoom: false,
|
|
745
|
+
featureClickTolerance: 0
|
|
746
|
+
});
|
|
747
|
+
let FlowCoreMap = _FlowCoreMap;
|
|
748
|
+
|
|
749
|
+
export { FlowCoreMap };
|