aur-openlayers 0.0.1
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 +63 -0
- package/fesm2022/aur-openlayers.mjs +2118 -0
- package/fesm2022/aur-openlayers.mjs.map +1 -0
- package/index.d.ts +5 -0
- package/lib/lib.component.d.ts +5 -0
- package/lib/lib.service.d.ts +6 -0
- package/lib/map-framework/index.d.ts +2 -0
- package/lib/map-framework/public/types.d.ts +658 -0
- package/lib/map-framework/runtime/cluster-utils.d.ts +3 -0
- package/lib/map-framework/runtime/clustered-layer.d.ts +17 -0
- package/lib/map-framework/runtime/feature-registry.d.ts +16 -0
- package/lib/map-framework/runtime/fit-layer.utils.d.ts +3 -0
- package/lib/map-framework/runtime/interaction-manager.d.ts +110 -0
- package/lib/map-framework/runtime/layer-manager.d.ts +17 -0
- package/lib/map-framework/runtime/map-context.d.ts +4 -0
- package/lib/map-framework/runtime/plain-layer.d.ts +6 -0
- package/lib/map-framework/runtime/popup-host.d.ts +31 -0
- package/lib/map-framework/runtime/scheduler.d.ts +21 -0
- package/lib/map-framework/runtime/style/feature-states.d.ts +5 -0
- package/lib/map-framework/runtime/style/style-cache.d.ts +7 -0
- package/lib/map-framework/runtime/style/style-pipeline.d.ts +20 -0
- package/lib/map-framework/runtime/vector-layer-base.d.ts +53 -0
- package/package.json +24 -0
- package/public-api.d.ts +3 -0
|
@@ -0,0 +1,2118 @@
|
|
|
1
|
+
import * as i0 from '@angular/core';
|
|
2
|
+
import { Injectable, Component } from '@angular/core';
|
|
3
|
+
import VectorLayer from 'ol/layer/Vector';
|
|
4
|
+
import ClusterSource from 'ol/source/Cluster';
|
|
5
|
+
import VectorSource from 'ol/source/Vector';
|
|
6
|
+
import Feature from 'ol/Feature';
|
|
7
|
+
import Collection from 'ol/Collection';
|
|
8
|
+
import DragPan from 'ol/interaction/DragPan';
|
|
9
|
+
import Modify from 'ol/interaction/Modify';
|
|
10
|
+
import * as Observable from 'ol/Observable';
|
|
11
|
+
import { createEmpty, extend, isEmpty } from 'ol/extent';
|
|
12
|
+
|
|
13
|
+
class LibService {
|
|
14
|
+
constructor() { }
|
|
15
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LibService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
16
|
+
static ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LibService, providedIn: 'root' });
|
|
17
|
+
}
|
|
18
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LibService, decorators: [{
|
|
19
|
+
type: Injectable,
|
|
20
|
+
args: [{
|
|
21
|
+
providedIn: 'root'
|
|
22
|
+
}]
|
|
23
|
+
}], ctorParameters: () => [] });
|
|
24
|
+
|
|
25
|
+
class LibComponent {
|
|
26
|
+
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LibComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
27
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.18", type: LibComponent, isStandalone: true, selector: "lib-lib", ngImport: i0, template: `
|
|
28
|
+
<p>
|
|
29
|
+
lib works!
|
|
30
|
+
</p>
|
|
31
|
+
`, isInline: true, styles: [""] });
|
|
32
|
+
}
|
|
33
|
+
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.18", ngImport: i0, type: LibComponent, decorators: [{
|
|
34
|
+
type: Component,
|
|
35
|
+
args: [{ selector: 'lib-lib', imports: [], template: `
|
|
36
|
+
<p>
|
|
37
|
+
lib works!
|
|
38
|
+
</p>
|
|
39
|
+
` }]
|
|
40
|
+
}] });
|
|
41
|
+
|
|
42
|
+
class FlushScheduler {
|
|
43
|
+
defaultPolicy;
|
|
44
|
+
queue = new Map();
|
|
45
|
+
pendingPolicy = null;
|
|
46
|
+
scheduledToken = 0;
|
|
47
|
+
rafId = null;
|
|
48
|
+
batchDepth = 0;
|
|
49
|
+
policyStack = [];
|
|
50
|
+
constructor(defaultPolicy = 'microtask') {
|
|
51
|
+
this.defaultPolicy = defaultPolicy;
|
|
52
|
+
}
|
|
53
|
+
batch(fn, options) {
|
|
54
|
+
if (options?.policy) {
|
|
55
|
+
this.policyStack.push(options.policy);
|
|
56
|
+
}
|
|
57
|
+
this.batchDepth += 1;
|
|
58
|
+
try {
|
|
59
|
+
fn();
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
this.batchDepth = Math.max(0, this.batchDepth - 1);
|
|
63
|
+
if (options?.policy) {
|
|
64
|
+
this.policyStack.pop();
|
|
65
|
+
}
|
|
66
|
+
if (this.batchDepth === 0 && (this.pendingPolicy !== null || this.queue.size > 0)) {
|
|
67
|
+
const policy = this.pendingPolicy ?? this.currentPolicy();
|
|
68
|
+
this.pendingPolicy = policy;
|
|
69
|
+
this.scheduleFlush(policy);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
schedule(key, task, policy) {
|
|
74
|
+
this.queue.set(key, task);
|
|
75
|
+
this.requestFlush(policy);
|
|
76
|
+
}
|
|
77
|
+
requestFlush(policy) {
|
|
78
|
+
const desired = policy ?? this.currentPolicy();
|
|
79
|
+
if (this.batchDepth > 0) {
|
|
80
|
+
this.pendingPolicy = this.combinePolicies(this.pendingPolicy, desired);
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
if (this.pendingPolicy) {
|
|
84
|
+
if (this.shouldUpgradePolicy(this.pendingPolicy, desired)) {
|
|
85
|
+
this.pendingPolicy = desired;
|
|
86
|
+
this.scheduleFlush(desired);
|
|
87
|
+
}
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
this.pendingPolicy = desired;
|
|
91
|
+
this.scheduleFlush(desired);
|
|
92
|
+
}
|
|
93
|
+
scheduleFlush(policy) {
|
|
94
|
+
const token = ++this.scheduledToken;
|
|
95
|
+
if (policy === 'raf') {
|
|
96
|
+
if (this.rafId !== null) {
|
|
97
|
+
cancelAnimationFrame(this.rafId);
|
|
98
|
+
}
|
|
99
|
+
this.rafId = requestAnimationFrame(() => this.flush('raf', token));
|
|
100
|
+
return;
|
|
101
|
+
}
|
|
102
|
+
queueMicrotask(() => this.flush('microtask', token));
|
|
103
|
+
}
|
|
104
|
+
flush(policy, token) {
|
|
105
|
+
if (token !== this.scheduledToken || this.pendingPolicy !== policy) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
this.pendingPolicy = null;
|
|
109
|
+
if (policy === 'raf') {
|
|
110
|
+
this.rafId = null;
|
|
111
|
+
}
|
|
112
|
+
const tasks = Array.from(this.queue.values());
|
|
113
|
+
this.queue.clear();
|
|
114
|
+
tasks.forEach((task) => task());
|
|
115
|
+
}
|
|
116
|
+
currentPolicy() {
|
|
117
|
+
if (this.policyStack.length > 0) {
|
|
118
|
+
return this.policyStack[this.policyStack.length - 1];
|
|
119
|
+
}
|
|
120
|
+
return this.defaultPolicy;
|
|
121
|
+
}
|
|
122
|
+
combinePolicies(current, next) {
|
|
123
|
+
if (!current) {
|
|
124
|
+
return next;
|
|
125
|
+
}
|
|
126
|
+
return current === 'raf' || next === 'raf' ? 'raf' : 'microtask';
|
|
127
|
+
}
|
|
128
|
+
shouldUpgradePolicy(current, next) {
|
|
129
|
+
return current === 'microtask' && next === 'raf';
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const createMapContext = (map, layers, popupHost, scheduler = new FlushScheduler()) => {
|
|
134
|
+
return {
|
|
135
|
+
map,
|
|
136
|
+
layers,
|
|
137
|
+
popupHost,
|
|
138
|
+
batch: (fn, options) => scheduler.batch(fn, options),
|
|
139
|
+
};
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
const CLUSTER_FEATURES_KEY = 'features';
|
|
143
|
+
const getClusterFeatures = (feature) => {
|
|
144
|
+
const features = feature.get(CLUSTER_FEATURES_KEY);
|
|
145
|
+
if (!Array.isArray(features)) {
|
|
146
|
+
return null;
|
|
147
|
+
}
|
|
148
|
+
const items = features.filter((item) => {
|
|
149
|
+
if (!item || typeof item !== 'object') {
|
|
150
|
+
return false;
|
|
151
|
+
}
|
|
152
|
+
const candidate = item;
|
|
153
|
+
return typeof candidate.get === 'function' && typeof candidate.getGeometry === 'function';
|
|
154
|
+
});
|
|
155
|
+
if (items.length === 0) {
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
158
|
+
return items;
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Default padding for View#fit in pixels.
|
|
163
|
+
* Order: [top, right, bottom, left]
|
|
164
|
+
*/
|
|
165
|
+
const DEFAULT_OL_PADDING = [64, 64, 64, 64];
|
|
166
|
+
const DEFAULT_FIT_DURATION = 500;
|
|
167
|
+
function toOlPadding(p, fallback = DEFAULT_OL_PADDING) {
|
|
168
|
+
if (!p) {
|
|
169
|
+
return fallback;
|
|
170
|
+
}
|
|
171
|
+
if ('all' in p) {
|
|
172
|
+
const a = p.all;
|
|
173
|
+
return [a, a, a, a];
|
|
174
|
+
}
|
|
175
|
+
if ('vertical' in p && 'horizontal' in p) {
|
|
176
|
+
return [p.vertical, p.horizontal, p.vertical, p.horizontal];
|
|
177
|
+
}
|
|
178
|
+
return [p.top, p.right, p.bottom, p.left];
|
|
179
|
+
}
|
|
180
|
+
function toOlFitOptions(opts) {
|
|
181
|
+
const padding = toOlPadding(opts?.padding) ?? DEFAULT_OL_PADDING;
|
|
182
|
+
const duration = opts?.duration ?? DEFAULT_FIT_DURATION;
|
|
183
|
+
// OL View#fit options are a plain object; keep it minimal & typed-friendly
|
|
184
|
+
const fitOpts = { padding, duration };
|
|
185
|
+
if (opts?.maxZoom != null) {
|
|
186
|
+
fitOpts.maxZoom = opts.maxZoom;
|
|
187
|
+
}
|
|
188
|
+
return fitOpts;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const STATES_KEY = '__mff_states__';
|
|
192
|
+
const getFeatureStates = (feature) => {
|
|
193
|
+
const states = feature.get(STATES_KEY);
|
|
194
|
+
if (Array.isArray(states)) {
|
|
195
|
+
return states;
|
|
196
|
+
}
|
|
197
|
+
if (typeof states === 'string') {
|
|
198
|
+
return [states];
|
|
199
|
+
}
|
|
200
|
+
return [];
|
|
201
|
+
};
|
|
202
|
+
const setFeatureStates = (feature, states) => {
|
|
203
|
+
feature.set(STATES_KEY, states);
|
|
204
|
+
};
|
|
205
|
+
const clearFeatureStates = (feature) => {
|
|
206
|
+
feature.set(STATES_KEY, []);
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
class InteractionManager {
|
|
210
|
+
ctx;
|
|
211
|
+
map;
|
|
212
|
+
schema;
|
|
213
|
+
layers;
|
|
214
|
+
apis;
|
|
215
|
+
hitTest;
|
|
216
|
+
hoverItems = new Map();
|
|
217
|
+
selectedItems = new Map();
|
|
218
|
+
activeTranslates = new Map();
|
|
219
|
+
activeModifies = new Map();
|
|
220
|
+
nativeModifies = new Map();
|
|
221
|
+
popupStack;
|
|
222
|
+
listenerKeys = new Map();
|
|
223
|
+
enabledState = new Map();
|
|
224
|
+
dragPanLocks = 0;
|
|
225
|
+
dragPanStates;
|
|
226
|
+
currentCursor;
|
|
227
|
+
constructor(options) {
|
|
228
|
+
this.ctx = options.ctx;
|
|
229
|
+
this.map = options.map;
|
|
230
|
+
this.schema = options.schema;
|
|
231
|
+
this.layers = options.layers;
|
|
232
|
+
this.apis = options.apis;
|
|
233
|
+
this.hitTest = options.hitTest ?? this.createDefaultHitTest();
|
|
234
|
+
this.popupStack = this.schema.options?.popupHost?.stack ?? 'stop';
|
|
235
|
+
this.refreshEnabled();
|
|
236
|
+
}
|
|
237
|
+
refreshEnabled() {
|
|
238
|
+
const popupEnabled = this.isEnabled(this.schema.options?.popupHost?.enabled);
|
|
239
|
+
if (!popupEnabled) {
|
|
240
|
+
this.ctx.popupHost?.clear();
|
|
241
|
+
}
|
|
242
|
+
const nextState = new Map();
|
|
243
|
+
this.schema.layers.forEach((descriptor) => {
|
|
244
|
+
const interactions = descriptor.feature.interactions;
|
|
245
|
+
nextState.set(descriptor.id, {
|
|
246
|
+
click: !!interactions?.click && this.isEnabled(interactions.click.enabled),
|
|
247
|
+
doubleClick: !!interactions?.doubleClick && this.isEnabled(interactions.doubleClick.enabled),
|
|
248
|
+
hover: !!interactions?.hover && this.isEnabled(interactions.hover.enabled),
|
|
249
|
+
select: !!interactions?.select && this.isEnabled(interactions.select.enabled),
|
|
250
|
+
translate: !!interactions?.translate && this.isEnabled(interactions.translate.enabled),
|
|
251
|
+
modify: !!interactions?.modify && this.isEnabled(interactions.modify.enabled),
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
this.syncNativeModify(nextState);
|
|
255
|
+
this.applyDisabledCleanup(nextState);
|
|
256
|
+
this.enabledState.clear();
|
|
257
|
+
nextState.forEach((value, key) => this.enabledState.set(key, value));
|
|
258
|
+
this.syncListeners(popupEnabled);
|
|
259
|
+
}
|
|
260
|
+
handlePointerDown(event) {
|
|
261
|
+
if (!this.isListening('pointerdown')) {
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
const layers = this.getOrderedLayers();
|
|
265
|
+
for (const entry of layers) {
|
|
266
|
+
const interactions = entry.descriptor.feature.interactions;
|
|
267
|
+
const translate = interactions?.translate;
|
|
268
|
+
if (translate && this.isEnabled(translate.enabled)) {
|
|
269
|
+
const { items: candidates } = this.hitTest({
|
|
270
|
+
layerId: entry.descriptor.id,
|
|
271
|
+
layer: entry.layer,
|
|
272
|
+
api: entry.api,
|
|
273
|
+
descriptor: entry.descriptor,
|
|
274
|
+
event,
|
|
275
|
+
hitTolerance: this.getHitTolerance(translate.hitTolerance),
|
|
276
|
+
});
|
|
277
|
+
if (candidates.length > 0) {
|
|
278
|
+
const translateEvent = this.createTranslateEvent(event, event.coordinate, 'translatestart', candidates);
|
|
279
|
+
let target;
|
|
280
|
+
if (translate.pickTarget) {
|
|
281
|
+
target = translate.pickTarget({ candidates, ctx: this.ctx, event: translateEvent });
|
|
282
|
+
}
|
|
283
|
+
else {
|
|
284
|
+
target = candidates[0];
|
|
285
|
+
}
|
|
286
|
+
if (target) {
|
|
287
|
+
const targetKey = entry.descriptor.feature.id(target.model);
|
|
288
|
+
const resolved = this.resolveTarget(entry, targetKey);
|
|
289
|
+
if (resolved) {
|
|
290
|
+
const active = {
|
|
291
|
+
targetKey,
|
|
292
|
+
startCoordinate: event.coordinate,
|
|
293
|
+
lastCoordinate: event.coordinate,
|
|
294
|
+
lastItem: resolved,
|
|
295
|
+
moveThrottleMs: translate.moveThrottleMs ?? 0,
|
|
296
|
+
translate,
|
|
297
|
+
};
|
|
298
|
+
this.activeTranslates.set(entry.descriptor.id, active);
|
|
299
|
+
this.lockDragPan();
|
|
300
|
+
if (translate.state) {
|
|
301
|
+
this.applyState([resolved], translate.state, true);
|
|
302
|
+
}
|
|
303
|
+
const handled = translate.onStart
|
|
304
|
+
? this.isHandled(translate.onStart({ item: resolved, ctx: this.ctx, event: translateEvent }))
|
|
305
|
+
: false;
|
|
306
|
+
active.lastHandled = handled;
|
|
307
|
+
if (handled && this.shouldStopPropagation(translate)) {
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
if (this.activeTranslates.has(entry.descriptor.id)) {
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
this.updateCursor(event);
|
|
319
|
+
}
|
|
320
|
+
handlePointerDrag(event) {
|
|
321
|
+
if (!this.isListening('pointerdrag')) {
|
|
322
|
+
return;
|
|
323
|
+
}
|
|
324
|
+
if (this.activeTranslates.size === 0 && this.activeModifies.size === 0) {
|
|
325
|
+
return;
|
|
326
|
+
}
|
|
327
|
+
for (const entry of this.getOrderedLayers()) {
|
|
328
|
+
const active = this.activeTranslates.get(entry.descriptor.id);
|
|
329
|
+
if (!active) {
|
|
330
|
+
const activeModify = this.activeModifies.get(entry.descriptor.id);
|
|
331
|
+
if (!activeModify) {
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
const modify = activeModify.modify;
|
|
335
|
+
const execute = () => this.applyModifyChange(entry, activeModify, event);
|
|
336
|
+
if (activeModify.moveThrottleMs > 0) {
|
|
337
|
+
if (!activeModify.throttleTimer) {
|
|
338
|
+
execute();
|
|
339
|
+
activeModify.throttleTimer = setTimeout(() => {
|
|
340
|
+
activeModify.throttleTimer = undefined;
|
|
341
|
+
if (activeModify.pendingEvent) {
|
|
342
|
+
const pending = activeModify.pendingEvent;
|
|
343
|
+
activeModify.pendingEvent = undefined;
|
|
344
|
+
this.applyModifyChange(entry, activeModify, pending);
|
|
345
|
+
}
|
|
346
|
+
}, activeModify.moveThrottleMs);
|
|
347
|
+
}
|
|
348
|
+
else {
|
|
349
|
+
activeModify.pendingEvent = event;
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
else {
|
|
353
|
+
execute();
|
|
354
|
+
}
|
|
355
|
+
if (activeModify.lastHandled && this.shouldStopPropagation(modify)) {
|
|
356
|
+
break;
|
|
357
|
+
}
|
|
358
|
+
continue;
|
|
359
|
+
}
|
|
360
|
+
const translate = active.translate;
|
|
361
|
+
const execute = () => this.applyTranslateMove(entry, active, event);
|
|
362
|
+
if (active.moveThrottleMs > 0) {
|
|
363
|
+
if (!active.throttleTimer) {
|
|
364
|
+
execute();
|
|
365
|
+
active.throttleTimer = setTimeout(() => {
|
|
366
|
+
active.throttleTimer = undefined;
|
|
367
|
+
if (active.pendingEvent) {
|
|
368
|
+
const pending = active.pendingEvent;
|
|
369
|
+
active.pendingEvent = undefined;
|
|
370
|
+
this.applyTranslateMove(entry, active, pending);
|
|
371
|
+
}
|
|
372
|
+
}, active.moveThrottleMs);
|
|
373
|
+
}
|
|
374
|
+
else {
|
|
375
|
+
active.pendingEvent = event;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
execute();
|
|
380
|
+
}
|
|
381
|
+
if (active.lastHandled && this.shouldStopPropagation(translate)) {
|
|
382
|
+
break;
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
this.updateCursor(event);
|
|
386
|
+
}
|
|
387
|
+
handlePointerUp(event) {
|
|
388
|
+
if (!this.isListening('pointerup')) {
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
if (this.activeTranslates.size === 0 && this.activeModifies.size === 0) {
|
|
392
|
+
return;
|
|
393
|
+
}
|
|
394
|
+
for (const entry of this.getOrderedLayers()) {
|
|
395
|
+
const active = this.activeTranslates.get(entry.descriptor.id);
|
|
396
|
+
if (active) {
|
|
397
|
+
const translate = active.translate;
|
|
398
|
+
if (active.throttleTimer) {
|
|
399
|
+
clearTimeout(active.throttleTimer);
|
|
400
|
+
active.throttleTimer = undefined;
|
|
401
|
+
}
|
|
402
|
+
if (active.pendingEvent) {
|
|
403
|
+
const pending = active.pendingEvent;
|
|
404
|
+
active.pendingEvent = undefined;
|
|
405
|
+
this.applyTranslateMove(entry, active, pending);
|
|
406
|
+
}
|
|
407
|
+
const resolved = this.resolveTarget(entry, active.targetKey);
|
|
408
|
+
if (resolved && translate.onEnd) {
|
|
409
|
+
const translateEvent = this.createTranslateEvent(event, active.startCoordinate, 'translateend', [resolved]);
|
|
410
|
+
active.lastHandled = this.isHandled(translate.onEnd({ item: resolved, ctx: this.ctx, event: translateEvent }));
|
|
411
|
+
}
|
|
412
|
+
else {
|
|
413
|
+
active.lastHandled = false;
|
|
414
|
+
}
|
|
415
|
+
this.finishTranslate(entry, active, resolved);
|
|
416
|
+
if (active.lastHandled && this.shouldStopPropagation(translate)) {
|
|
417
|
+
break;
|
|
418
|
+
}
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
const activeModify = this.activeModifies.get(entry.descriptor.id);
|
|
422
|
+
if (!activeModify) {
|
|
423
|
+
continue;
|
|
424
|
+
}
|
|
425
|
+
const modify = activeModify.modify;
|
|
426
|
+
if (activeModify.throttleTimer) {
|
|
427
|
+
clearTimeout(activeModify.throttleTimer);
|
|
428
|
+
activeModify.throttleTimer = undefined;
|
|
429
|
+
}
|
|
430
|
+
if (activeModify.pendingEvent) {
|
|
431
|
+
const pending = activeModify.pendingEvent;
|
|
432
|
+
activeModify.pendingEvent = undefined;
|
|
433
|
+
this.applyModifyChange(entry, activeModify, pending);
|
|
434
|
+
}
|
|
435
|
+
const resolved = this.resolveTarget(entry, activeModify.targetKey);
|
|
436
|
+
if (resolved && modify.onEnd) {
|
|
437
|
+
const modifyEvent = this.createModifyEvent(event, 'modifyend', [resolved]);
|
|
438
|
+
activeModify.lastHandled = this.isHandled(modify.onEnd({ item: resolved, ctx: this.ctx, event: modifyEvent }));
|
|
439
|
+
}
|
|
440
|
+
else {
|
|
441
|
+
activeModify.lastHandled = false;
|
|
442
|
+
}
|
|
443
|
+
this.finishModify(entry, activeModify, resolved);
|
|
444
|
+
if (activeModify.lastHandled && this.shouldStopPropagation(modify)) {
|
|
445
|
+
break;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
this.updateCursor(event);
|
|
449
|
+
}
|
|
450
|
+
handlePointerMove(event) {
|
|
451
|
+
if (!this.isListening('pointermove')) {
|
|
452
|
+
return;
|
|
453
|
+
}
|
|
454
|
+
const popupHost = this.ctx.popupHost;
|
|
455
|
+
const popupEnabled = this.isEnabled(this.schema.options?.popupHost?.enabled);
|
|
456
|
+
const autoMode = popupEnabled ? this.schema.options?.popupHost?.autoMode ?? 'off' : 'off';
|
|
457
|
+
const autoHover = autoMode === 'hover';
|
|
458
|
+
const popupItems = [];
|
|
459
|
+
let popupStopped = false;
|
|
460
|
+
const layers = this.getOrderedLayers();
|
|
461
|
+
for (const entry of layers) {
|
|
462
|
+
const hover = entry.descriptor.feature.interactions?.hover;
|
|
463
|
+
const hoverEnabled = !!hover && this.isEnabled(hover.enabled);
|
|
464
|
+
if (!hoverEnabled && !autoHover) {
|
|
465
|
+
continue;
|
|
466
|
+
}
|
|
467
|
+
const hitResult = this.hitTest({
|
|
468
|
+
layerId: entry.descriptor.id,
|
|
469
|
+
layer: entry.layer,
|
|
470
|
+
api: entry.api,
|
|
471
|
+
descriptor: entry.descriptor,
|
|
472
|
+
event,
|
|
473
|
+
hitTolerance: this.getHitTolerance(hoverEnabled ? hover.hitTolerance : undefined),
|
|
474
|
+
});
|
|
475
|
+
if (hoverEnabled) {
|
|
476
|
+
const handled = this.processHover(entry, hover, hitResult.items, event);
|
|
477
|
+
if (handled && this.shouldStopPropagation(hover)) {
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
if (autoHover && popupHost && !popupStopped) {
|
|
482
|
+
const collected = this.collectPopupItems(entry, hitResult, event);
|
|
483
|
+
if (collected.length > 0) {
|
|
484
|
+
popupItems.push(...collected);
|
|
485
|
+
if (this.popupStack === 'stop') {
|
|
486
|
+
popupStopped = true;
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
}
|
|
491
|
+
if (autoHover && popupHost) {
|
|
492
|
+
popupHost.set(popupItems);
|
|
493
|
+
}
|
|
494
|
+
this.updateCursor(event);
|
|
495
|
+
}
|
|
496
|
+
handleSingleClick(event) {
|
|
497
|
+
if (!this.isListening('singleclick')) {
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
const popupHost = this.ctx.popupHost;
|
|
501
|
+
const popupEnabled = this.isEnabled(this.schema.options?.popupHost?.enabled);
|
|
502
|
+
const autoMode = popupEnabled ? this.schema.options?.popupHost?.autoMode ?? 'off' : 'off';
|
|
503
|
+
const autoClick = autoMode === 'click';
|
|
504
|
+
const popupItems = [];
|
|
505
|
+
let popupStopped = false;
|
|
506
|
+
const layers = this.getOrderedLayers();
|
|
507
|
+
for (const entry of layers) {
|
|
508
|
+
const select = entry.descriptor.feature.interactions?.select;
|
|
509
|
+
const click = entry.descriptor.feature.interactions?.click;
|
|
510
|
+
const selectEnabled = select && this.isEnabled(select.enabled);
|
|
511
|
+
const clickEnabled = click && this.isEnabled(click.enabled);
|
|
512
|
+
const clusteringEnabled = !!entry.descriptor.clustering && !!entry.api.isClusteringEnabled?.();
|
|
513
|
+
if (!selectEnabled && !clickEnabled && !clusteringEnabled && !autoClick) {
|
|
514
|
+
continue;
|
|
515
|
+
}
|
|
516
|
+
const tolerances = [
|
|
517
|
+
selectEnabled ? select?.hitTolerance : undefined,
|
|
518
|
+
clickEnabled ? click?.hitTolerance : undefined,
|
|
519
|
+
].filter((value) => value !== undefined);
|
|
520
|
+
const hitTolerance = tolerances.length > 0 ? Math.max(...tolerances) : this.getHitTolerance(undefined);
|
|
521
|
+
const hitResult = this.hitTest({
|
|
522
|
+
layerId: entry.descriptor.id,
|
|
523
|
+
layer: entry.layer,
|
|
524
|
+
api: entry.api,
|
|
525
|
+
descriptor: entry.descriptor,
|
|
526
|
+
event,
|
|
527
|
+
hitTolerance,
|
|
528
|
+
});
|
|
529
|
+
if (clusteringEnabled && hitResult.cluster) {
|
|
530
|
+
if (autoClick && popupHost && !popupStopped) {
|
|
531
|
+
const collected = this.collectPopupItems(entry, hitResult, event);
|
|
532
|
+
if (collected.length > 0) {
|
|
533
|
+
popupItems.push(...collected);
|
|
534
|
+
if (this.popupStack === 'stop') {
|
|
535
|
+
popupStopped = true;
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
}
|
|
539
|
+
const handled = this.handleClusterClick(entry, hitResult.cluster, event, !autoClick);
|
|
540
|
+
if (handled) {
|
|
541
|
+
if (this.shouldStopClusterPropagation()) {
|
|
542
|
+
break;
|
|
543
|
+
}
|
|
544
|
+
continue;
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
if (selectEnabled || clickEnabled) {
|
|
548
|
+
const selectItems = selectEnabled ? hitResult.items : [];
|
|
549
|
+
const clickItems = clickEnabled ? hitResult.items : [];
|
|
550
|
+
const selectHandled = selectEnabled
|
|
551
|
+
? this.processSelect(entry, select, selectItems, event)
|
|
552
|
+
: false;
|
|
553
|
+
const selectStops = selectHandled && this.shouldStopPropagation(select);
|
|
554
|
+
const allowClick = !selectHandled || (selectEnabled && this.shouldContinuePropagation(select));
|
|
555
|
+
if (clickEnabled && allowClick) {
|
|
556
|
+
const clickHandled = this.processClick(entry, click, clickItems, event);
|
|
557
|
+
if (clickHandled && this.shouldStopPropagation(click)) {
|
|
558
|
+
break;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
if (selectStops) {
|
|
562
|
+
break;
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
if (autoClick && popupHost && !popupStopped && !(clusteringEnabled && hitResult.cluster)) {
|
|
566
|
+
const collected = this.collectPopupItems(entry, hitResult, event);
|
|
567
|
+
if (collected.length > 0) {
|
|
568
|
+
popupItems.push(...collected);
|
|
569
|
+
if (this.popupStack === 'stop') {
|
|
570
|
+
popupStopped = true;
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (autoClick && popupHost) {
|
|
576
|
+
popupHost.set(popupItems);
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
handleDoubleClick(event) {
|
|
580
|
+
if (!this.isListening('dblclick')) {
|
|
581
|
+
return;
|
|
582
|
+
}
|
|
583
|
+
const layers = this.getOrderedLayers();
|
|
584
|
+
for (const entry of layers) {
|
|
585
|
+
const doubleClick = entry.descriptor.feature.interactions?.doubleClick;
|
|
586
|
+
if (!doubleClick || !this.isEnabled(doubleClick.enabled)) {
|
|
587
|
+
continue;
|
|
588
|
+
}
|
|
589
|
+
const hitResult = this.hitTest({
|
|
590
|
+
layerId: entry.descriptor.id,
|
|
591
|
+
layer: entry.layer,
|
|
592
|
+
api: entry.api,
|
|
593
|
+
descriptor: entry.descriptor,
|
|
594
|
+
event,
|
|
595
|
+
hitTolerance: this.getHitTolerance(doubleClick.hitTolerance),
|
|
596
|
+
});
|
|
597
|
+
if (entry.descriptor.clustering && entry.api.isClusteringEnabled?.() && hitResult.cluster) {
|
|
598
|
+
continue;
|
|
599
|
+
}
|
|
600
|
+
const { items } = hitResult;
|
|
601
|
+
const handled = this.processDoubleClick(entry, doubleClick, items, event);
|
|
602
|
+
if (handled && this.shouldStopPropagation(doubleClick)) {
|
|
603
|
+
break;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
runInteractionMutation(fn) {
|
|
608
|
+
const policy = this.schema.options?.scheduler?.interactionPolicy;
|
|
609
|
+
if (policy) {
|
|
610
|
+
this.ctx.batch(fn, { policy });
|
|
611
|
+
return;
|
|
612
|
+
}
|
|
613
|
+
fn();
|
|
614
|
+
}
|
|
615
|
+
syncListeners(popupEnabled) {
|
|
616
|
+
const autoMode = popupEnabled ? this.schema.options?.popupHost?.autoMode ?? 'off' : 'off';
|
|
617
|
+
const needsHover = autoMode === 'hover';
|
|
618
|
+
const needsClick = autoMode === 'click';
|
|
619
|
+
let needsPointerMove = needsHover;
|
|
620
|
+
let needsSingleClick = needsClick;
|
|
621
|
+
let needsDoubleClick = false;
|
|
622
|
+
let needsPointerDown = false;
|
|
623
|
+
let needsPointerDrag = false;
|
|
624
|
+
let needsPointerUp = false;
|
|
625
|
+
this.schema.layers.forEach((descriptor) => {
|
|
626
|
+
const state = this.enabledState.get(descriptor.id);
|
|
627
|
+
if (state?.hover) {
|
|
628
|
+
needsPointerMove = true;
|
|
629
|
+
}
|
|
630
|
+
if (state?.click || state?.select) {
|
|
631
|
+
needsSingleClick = true;
|
|
632
|
+
}
|
|
633
|
+
if (state?.doubleClick) {
|
|
634
|
+
needsDoubleClick = true;
|
|
635
|
+
}
|
|
636
|
+
if (state?.translate || state?.modify) {
|
|
637
|
+
needsPointerDown = true;
|
|
638
|
+
needsPointerDrag = true;
|
|
639
|
+
needsPointerUp = true;
|
|
640
|
+
}
|
|
641
|
+
const interactions = descriptor.feature.interactions;
|
|
642
|
+
if (interactions && this.hasCursorInteraction(interactions)) {
|
|
643
|
+
needsPointerMove = true;
|
|
644
|
+
}
|
|
645
|
+
if (descriptor.clustering &&
|
|
646
|
+
(descriptor.clustering.expandOnClick || descriptor.clustering.popup)) {
|
|
647
|
+
needsSingleClick = true;
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
this.toggleListener('pointermove', needsPointerMove, (event) => this.handlePointerMove(event));
|
|
651
|
+
this.toggleListener('singleclick', needsSingleClick, (event) => this.handleSingleClick(event));
|
|
652
|
+
this.toggleListener('dblclick', needsDoubleClick, (event) => this.handleDoubleClick(event));
|
|
653
|
+
this.toggleListener('pointerdown', needsPointerDown, (event) => this.handlePointerDown(event));
|
|
654
|
+
this.toggleListener('pointerdrag', needsPointerDrag, (event) => this.handlePointerDrag(event));
|
|
655
|
+
this.toggleListener('pointerup', needsPointerUp, (event) => this.handlePointerUp(event));
|
|
656
|
+
}
|
|
657
|
+
syncNativeModify(nextState) {
|
|
658
|
+
const handled = new Set();
|
|
659
|
+
this.schema.layers.forEach((descriptor) => {
|
|
660
|
+
const interactions = descriptor.feature.interactions;
|
|
661
|
+
const modify = interactions?.modify;
|
|
662
|
+
const enabled = nextState.get(descriptor.id)?.modify ?? false;
|
|
663
|
+
const shouldEnable = !!modify && enabled;
|
|
664
|
+
if (!shouldEnable) {
|
|
665
|
+
return;
|
|
666
|
+
}
|
|
667
|
+
const entry = this.getLayerEntry(descriptor.id);
|
|
668
|
+
if (!entry) {
|
|
669
|
+
return;
|
|
670
|
+
}
|
|
671
|
+
handled.add(descriptor.id);
|
|
672
|
+
if (this.nativeModifies.has(descriptor.id)) {
|
|
673
|
+
this.nativeModifies.get(descriptor.id).interaction.setActive(true);
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
const source = entry.layer.getSource();
|
|
677
|
+
if (!source) {
|
|
678
|
+
return;
|
|
679
|
+
}
|
|
680
|
+
const interaction = new Modify({
|
|
681
|
+
source,
|
|
682
|
+
pixelTolerance: this.getHitTolerance(modify.hitTolerance),
|
|
683
|
+
style: modify.vertexStyle,
|
|
684
|
+
});
|
|
685
|
+
const keys = [
|
|
686
|
+
interaction.on('modifystart', (event) => this.handleNativeModifyStart(entry, modify, event)),
|
|
687
|
+
interaction.on('modifyend', (event) => this.handleNativeModifyEnd(entry, modify, event)),
|
|
688
|
+
];
|
|
689
|
+
this.map.addInteraction(interaction);
|
|
690
|
+
this.nativeModifies.set(descriptor.id, { interaction, keys });
|
|
691
|
+
});
|
|
692
|
+
Array.from(this.nativeModifies.keys()).forEach((layerId) => {
|
|
693
|
+
if (!handled.has(layerId)) {
|
|
694
|
+
this.teardownNativeModify(layerId);
|
|
695
|
+
}
|
|
696
|
+
});
|
|
697
|
+
}
|
|
698
|
+
teardownNativeModify(layerId) {
|
|
699
|
+
const entry = this.nativeModifies.get(layerId);
|
|
700
|
+
if (!entry) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
entry.keys.forEach((key) => Observable.unByKey(key));
|
|
704
|
+
this.map.removeInteraction(entry.interaction);
|
|
705
|
+
this.nativeModifies.delete(layerId);
|
|
706
|
+
}
|
|
707
|
+
handleNativeModifyStart(entry, modify, event) {
|
|
708
|
+
const items = this.resolveHitItemsFromFeatures(entry, event.features);
|
|
709
|
+
if (items.length === 0) {
|
|
710
|
+
return;
|
|
711
|
+
}
|
|
712
|
+
if (modify.state) {
|
|
713
|
+
this.applyState(items, modify.state, true);
|
|
714
|
+
}
|
|
715
|
+
if (modify.onStart) {
|
|
716
|
+
this.isHandled(modify.onStart({ item: items[0], ctx: this.ctx, event }));
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
handleNativeModifyEnd(entry, modify, event) {
|
|
720
|
+
const items = this.resolveHitItemsFromFeatures(entry, event.features);
|
|
721
|
+
if (items.length === 0) {
|
|
722
|
+
return;
|
|
723
|
+
}
|
|
724
|
+
this.runInteractionMutation(() => {
|
|
725
|
+
items.forEach((item) => {
|
|
726
|
+
const geometry = item.feature.getGeometry();
|
|
727
|
+
if (!geometry) {
|
|
728
|
+
return;
|
|
729
|
+
}
|
|
730
|
+
const id = entry.descriptor.feature.id(item.model);
|
|
731
|
+
entry.api.mutate(id, (prev) => entry.descriptor.feature.geometry.applyGeometryToModel(prev, geometry), 'modify');
|
|
732
|
+
});
|
|
733
|
+
});
|
|
734
|
+
if (modify.onEnd) {
|
|
735
|
+
this.isHandled(modify.onEnd({ item: items[0], ctx: this.ctx, event }));
|
|
736
|
+
}
|
|
737
|
+
if (modify.state) {
|
|
738
|
+
this.applyState(items, modify.state, false);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
resolveHitItemsFromFeatures(entry, features) {
|
|
742
|
+
return features
|
|
743
|
+
.getArray()
|
|
744
|
+
.map((feature) => {
|
|
745
|
+
const model = entry.api.getModelByFeature(feature);
|
|
746
|
+
if (!model) {
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
return { model, feature };
|
|
750
|
+
})
|
|
751
|
+
.filter((item) => item !== null);
|
|
752
|
+
}
|
|
753
|
+
hasCursorInteraction(interactions) {
|
|
754
|
+
const candidates = [
|
|
755
|
+
interactions.hover,
|
|
756
|
+
interactions.select,
|
|
757
|
+
interactions.click,
|
|
758
|
+
interactions.doubleClick,
|
|
759
|
+
interactions.translate,
|
|
760
|
+
interactions.modify,
|
|
761
|
+
];
|
|
762
|
+
return candidates.some((interaction) => interaction?.cursor && this.isEnabled(interaction.enabled));
|
|
763
|
+
}
|
|
764
|
+
toggleListener(type, enabled, handler) {
|
|
765
|
+
if (enabled) {
|
|
766
|
+
if (!this.listenerKeys.has(type)) {
|
|
767
|
+
const key = this.map.on(type, handler);
|
|
768
|
+
this.listenerKeys.set(type, key);
|
|
769
|
+
}
|
|
770
|
+
return;
|
|
771
|
+
}
|
|
772
|
+
this.removeListener(type);
|
|
773
|
+
}
|
|
774
|
+
removeListener(type) {
|
|
775
|
+
const key = this.listenerKeys.get(type);
|
|
776
|
+
if (key) {
|
|
777
|
+
Observable.unByKey(key);
|
|
778
|
+
this.listenerKeys.delete(type);
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
isListening(type) {
|
|
782
|
+
return this.listenerKeys.has(type);
|
|
783
|
+
}
|
|
784
|
+
applyDisabledCleanup(nextState) {
|
|
785
|
+
this.enabledState.forEach((prev, layerId) => {
|
|
786
|
+
const next = nextState.get(layerId) ?? {
|
|
787
|
+
click: false,
|
|
788
|
+
doubleClick: false,
|
|
789
|
+
hover: false,
|
|
790
|
+
select: false,
|
|
791
|
+
translate: false,
|
|
792
|
+
modify: false,
|
|
793
|
+
};
|
|
794
|
+
const entry = this.getLayerEntry(layerId);
|
|
795
|
+
if (!entry) {
|
|
796
|
+
return;
|
|
797
|
+
}
|
|
798
|
+
if (prev.hover && !next.hover) {
|
|
799
|
+
this.clearHoverState(entry);
|
|
800
|
+
}
|
|
801
|
+
if (prev.select && !next.select) {
|
|
802
|
+
this.clearSelectState(entry);
|
|
803
|
+
}
|
|
804
|
+
if (prev.translate && !next.translate) {
|
|
805
|
+
this.cancelTranslate(entry);
|
|
806
|
+
}
|
|
807
|
+
if (prev.modify && !next.modify) {
|
|
808
|
+
this.cancelModify(entry);
|
|
809
|
+
}
|
|
810
|
+
});
|
|
811
|
+
this.setCursor(undefined);
|
|
812
|
+
}
|
|
813
|
+
clearHoverState(entry) {
|
|
814
|
+
const prev = this.hoverItems.get(entry.descriptor.id);
|
|
815
|
+
if (!prev) {
|
|
816
|
+
return;
|
|
817
|
+
}
|
|
818
|
+
const hover = entry.descriptor.feature.interactions?.hover;
|
|
819
|
+
if (hover?.state) {
|
|
820
|
+
this.applyState(Array.from(prev.values()), hover.state, false);
|
|
821
|
+
}
|
|
822
|
+
this.hoverItems.delete(entry.descriptor.id);
|
|
823
|
+
}
|
|
824
|
+
clearSelectState(entry) {
|
|
825
|
+
const prev = this.selectedItems.get(entry.descriptor.id);
|
|
826
|
+
if (!prev) {
|
|
827
|
+
return;
|
|
828
|
+
}
|
|
829
|
+
const select = entry.descriptor.feature.interactions?.select;
|
|
830
|
+
if (select?.state) {
|
|
831
|
+
this.applyState(Array.from(prev.values()), select.state, false);
|
|
832
|
+
}
|
|
833
|
+
this.selectedItems.delete(entry.descriptor.id);
|
|
834
|
+
}
|
|
835
|
+
cancelTranslate(entry) {
|
|
836
|
+
const active = this.activeTranslates.get(entry.descriptor.id);
|
|
837
|
+
if (active) {
|
|
838
|
+
this.finishTranslate(entry, active, active.lastItem);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
cancelModify(entry) {
|
|
842
|
+
const active = this.activeModifies.get(entry.descriptor.id);
|
|
843
|
+
if (active) {
|
|
844
|
+
this.finishModify(entry, active, active.lastItem);
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
updateCursor(event) {
|
|
848
|
+
const activeCursor = this.getActiveSessionCursor();
|
|
849
|
+
if (activeCursor !== undefined) {
|
|
850
|
+
this.setCursor(activeCursor ?? undefined);
|
|
851
|
+
return;
|
|
852
|
+
}
|
|
853
|
+
if (!event) {
|
|
854
|
+
this.setCursor(undefined);
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
for (const entry of this.getOrderedLayers()) {
|
|
858
|
+
const interactions = entry.descriptor.feature.interactions;
|
|
859
|
+
if (!interactions) {
|
|
860
|
+
continue;
|
|
861
|
+
}
|
|
862
|
+
const cursor = this.getLayerCursor(interactions);
|
|
863
|
+
if (!cursor) {
|
|
864
|
+
continue;
|
|
865
|
+
}
|
|
866
|
+
const hitTolerance = this.getCursorHitTolerance(interactions);
|
|
867
|
+
const { items } = this.hitTest({
|
|
868
|
+
layerId: entry.descriptor.id,
|
|
869
|
+
layer: entry.layer,
|
|
870
|
+
api: entry.api,
|
|
871
|
+
descriptor: entry.descriptor,
|
|
872
|
+
event,
|
|
873
|
+
hitTolerance,
|
|
874
|
+
});
|
|
875
|
+
if (items.length > 0) {
|
|
876
|
+
this.setCursor(cursor);
|
|
877
|
+
return;
|
|
878
|
+
}
|
|
879
|
+
}
|
|
880
|
+
this.setCursor(undefined);
|
|
881
|
+
}
|
|
882
|
+
getActiveSessionCursor() {
|
|
883
|
+
for (const entry of this.getOrderedLayers()) {
|
|
884
|
+
const activeTranslate = this.activeTranslates.get(entry.descriptor.id);
|
|
885
|
+
if (activeTranslate) {
|
|
886
|
+
return activeTranslate.translate.cursor ?? null;
|
|
887
|
+
}
|
|
888
|
+
const activeModify = this.activeModifies.get(entry.descriptor.id);
|
|
889
|
+
if (activeModify) {
|
|
890
|
+
return activeModify.modify.cursor ?? null;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
return undefined;
|
|
894
|
+
}
|
|
895
|
+
getLayerCursor(interactions) {
|
|
896
|
+
const candidates = [
|
|
897
|
+
interactions.hover,
|
|
898
|
+
interactions.select,
|
|
899
|
+
interactions.click,
|
|
900
|
+
interactions.doubleClick,
|
|
901
|
+
interactions.translate,
|
|
902
|
+
interactions.modify,
|
|
903
|
+
];
|
|
904
|
+
for (const interaction of candidates) {
|
|
905
|
+
if (!interaction || !interaction.cursor) {
|
|
906
|
+
continue;
|
|
907
|
+
}
|
|
908
|
+
if (!this.isEnabled(interaction.enabled)) {
|
|
909
|
+
continue;
|
|
910
|
+
}
|
|
911
|
+
return interaction.cursor;
|
|
912
|
+
}
|
|
913
|
+
return undefined;
|
|
914
|
+
}
|
|
915
|
+
getCursorHitTolerance(interactions) {
|
|
916
|
+
const candidates = [
|
|
917
|
+
interactions.hover,
|
|
918
|
+
interactions.select,
|
|
919
|
+
interactions.click,
|
|
920
|
+
interactions.doubleClick,
|
|
921
|
+
interactions.translate,
|
|
922
|
+
interactions.modify,
|
|
923
|
+
].filter((interaction) => interaction && interaction.cursor && this.isEnabled(interaction.enabled));
|
|
924
|
+
if (candidates.length === 0) {
|
|
925
|
+
return this.getHitTolerance(undefined);
|
|
926
|
+
}
|
|
927
|
+
const tolerances = candidates
|
|
928
|
+
.map((interaction) => interaction?.hitTolerance)
|
|
929
|
+
.filter((value) => value !== undefined);
|
|
930
|
+
if (tolerances.length === 0) {
|
|
931
|
+
return this.getHitTolerance(undefined);
|
|
932
|
+
}
|
|
933
|
+
return Math.max(...tolerances);
|
|
934
|
+
}
|
|
935
|
+
setCursor(cursor) {
|
|
936
|
+
if (this.currentCursor === cursor) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
const target = this.getTargetElement();
|
|
940
|
+
if (!target) {
|
|
941
|
+
return;
|
|
942
|
+
}
|
|
943
|
+
target.style.cursor = cursor ?? '';
|
|
944
|
+
this.currentCursor = cursor;
|
|
945
|
+
}
|
|
946
|
+
getTargetElement() {
|
|
947
|
+
const targetElement = this.map.getTargetElement?.();
|
|
948
|
+
if (targetElement) {
|
|
949
|
+
return targetElement;
|
|
950
|
+
}
|
|
951
|
+
const target = this.map.getTarget?.();
|
|
952
|
+
if (!target) {
|
|
953
|
+
return null;
|
|
954
|
+
}
|
|
955
|
+
if (typeof target === 'string') {
|
|
956
|
+
if (typeof document === 'undefined') {
|
|
957
|
+
return null;
|
|
958
|
+
}
|
|
959
|
+
return document.getElementById(target);
|
|
960
|
+
}
|
|
961
|
+
return target;
|
|
962
|
+
}
|
|
963
|
+
getLayerEntry(layerId) {
|
|
964
|
+
const index = this.schema.layers.findIndex((layer) => layer.id === layerId);
|
|
965
|
+
if (index < 0) {
|
|
966
|
+
return null;
|
|
967
|
+
}
|
|
968
|
+
const descriptor = this.schema.layers[index];
|
|
969
|
+
const layer = this.layers[layerId];
|
|
970
|
+
const api = this.apis[layerId];
|
|
971
|
+
if (!layer || !api) {
|
|
972
|
+
return null;
|
|
973
|
+
}
|
|
974
|
+
return { descriptor, layer, api, index };
|
|
975
|
+
}
|
|
976
|
+
applyTranslateMove(entry, active, event) {
|
|
977
|
+
const translate = active.translate;
|
|
978
|
+
const resolved = this.resolveTarget(entry, active.targetKey);
|
|
979
|
+
if (!resolved) {
|
|
980
|
+
this.finishTranslate(entry, active, active.lastItem);
|
|
981
|
+
return;
|
|
982
|
+
}
|
|
983
|
+
const nextCoordinate = event.coordinate;
|
|
984
|
+
const delta = [
|
|
985
|
+
nextCoordinate[0] - active.lastCoordinate[0],
|
|
986
|
+
nextCoordinate[1] - active.lastCoordinate[1],
|
|
987
|
+
];
|
|
988
|
+
active.lastCoordinate = nextCoordinate;
|
|
989
|
+
active.lastItem = resolved;
|
|
990
|
+
const geometry = resolved.feature.getGeometry();
|
|
991
|
+
if (!geometry) {
|
|
992
|
+
return;
|
|
993
|
+
}
|
|
994
|
+
const translated = geometry.clone();
|
|
995
|
+
translated.translate(delta[0], delta[1]);
|
|
996
|
+
this.runInteractionMutation(() => entry.api.mutate(active.targetKey, (prev) => entry.descriptor.feature.geometry.applyGeometryToModel(prev, translated), 'translate'));
|
|
997
|
+
if (translate.onChange) {
|
|
998
|
+
const translateEvent = this.createTranslateEvent(event, active.startCoordinate, 'translating', [resolved]);
|
|
999
|
+
active.lastHandled = this.isHandled(translate.onChange({ item: resolved, ctx: this.ctx, event: translateEvent }));
|
|
1000
|
+
}
|
|
1001
|
+
else {
|
|
1002
|
+
active.lastHandled = false;
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
applyModifyChange(entry, active, event) {
|
|
1006
|
+
const modify = active.modify;
|
|
1007
|
+
const resolved = this.resolveTarget(entry, active.targetKey);
|
|
1008
|
+
if (!resolved) {
|
|
1009
|
+
this.finishModify(entry, active, active.lastItem);
|
|
1010
|
+
return;
|
|
1011
|
+
}
|
|
1012
|
+
active.lastItem = resolved;
|
|
1013
|
+
const geometry = resolved.feature.getGeometry();
|
|
1014
|
+
if (!geometry) {
|
|
1015
|
+
return;
|
|
1016
|
+
}
|
|
1017
|
+
const nextGeometry = geometry.clone();
|
|
1018
|
+
this.runInteractionMutation(() => entry.api.mutate(active.targetKey, (prev) => entry.descriptor.feature.geometry.applyGeometryToModel(prev, nextGeometry), 'modify'));
|
|
1019
|
+
if (modify.onChange) {
|
|
1020
|
+
const modifyEvent = this.createModifyEvent(event, 'modifying', [resolved]);
|
|
1021
|
+
active.lastHandled = this.isHandled(modify.onChange({ item: resolved, ctx: this.ctx, event: modifyEvent }));
|
|
1022
|
+
}
|
|
1023
|
+
else {
|
|
1024
|
+
active.lastHandled = false;
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
finishTranslate(entry, active, item) {
|
|
1028
|
+
const finalItem = item ?? active.lastItem;
|
|
1029
|
+
if (active.throttleTimer) {
|
|
1030
|
+
clearTimeout(active.throttleTimer);
|
|
1031
|
+
active.throttleTimer = undefined;
|
|
1032
|
+
active.pendingEvent = undefined;
|
|
1033
|
+
}
|
|
1034
|
+
if (active.translate.state && finalItem) {
|
|
1035
|
+
this.applyState([finalItem], active.translate.state, false);
|
|
1036
|
+
}
|
|
1037
|
+
this.activeTranslates.delete(entry.descriptor.id);
|
|
1038
|
+
this.unlockDragPan();
|
|
1039
|
+
}
|
|
1040
|
+
finishModify(entry, active, item) {
|
|
1041
|
+
const finalItem = item ?? active.lastItem;
|
|
1042
|
+
if (active.throttleTimer) {
|
|
1043
|
+
clearTimeout(active.throttleTimer);
|
|
1044
|
+
active.throttleTimer = undefined;
|
|
1045
|
+
active.pendingEvent = undefined;
|
|
1046
|
+
}
|
|
1047
|
+
if (active.modify.state && finalItem) {
|
|
1048
|
+
this.applyState([finalItem], active.modify.state, false);
|
|
1049
|
+
}
|
|
1050
|
+
this.activeModifies.delete(entry.descriptor.id);
|
|
1051
|
+
this.unlockDragPan();
|
|
1052
|
+
}
|
|
1053
|
+
resolveTarget(entry, targetKey) {
|
|
1054
|
+
const source = entry.layer.getSource();
|
|
1055
|
+
if (!source) {
|
|
1056
|
+
return null;
|
|
1057
|
+
}
|
|
1058
|
+
const resolvedSource = source instanceof ClusterSource ? source.getSource() : source;
|
|
1059
|
+
if (!resolvedSource) {
|
|
1060
|
+
return null;
|
|
1061
|
+
}
|
|
1062
|
+
const feature = resolvedSource.getFeatureById(targetKey);
|
|
1063
|
+
if (!(feature instanceof Feature)) {
|
|
1064
|
+
return null;
|
|
1065
|
+
}
|
|
1066
|
+
const model = entry.api.getModelByFeature(feature);
|
|
1067
|
+
if (!model) {
|
|
1068
|
+
return null;
|
|
1069
|
+
}
|
|
1070
|
+
return { model, feature };
|
|
1071
|
+
}
|
|
1072
|
+
lockDragPan() {
|
|
1073
|
+
this.dragPanLocks += 1;
|
|
1074
|
+
if (this.dragPanLocks !== 1) {
|
|
1075
|
+
return;
|
|
1076
|
+
}
|
|
1077
|
+
const interactions = this.map.getInteractions?.();
|
|
1078
|
+
if (!interactions) {
|
|
1079
|
+
return;
|
|
1080
|
+
}
|
|
1081
|
+
const dragPans = interactions
|
|
1082
|
+
.getArray()
|
|
1083
|
+
.filter((interaction) => interaction instanceof DragPan);
|
|
1084
|
+
if (dragPans.length === 0) {
|
|
1085
|
+
return;
|
|
1086
|
+
}
|
|
1087
|
+
if (!this.dragPanStates) {
|
|
1088
|
+
this.dragPanStates = new Map();
|
|
1089
|
+
}
|
|
1090
|
+
dragPans.forEach((interaction) => {
|
|
1091
|
+
if (!this.dragPanStates?.has(interaction)) {
|
|
1092
|
+
this.dragPanStates?.set(interaction, interaction.getActive());
|
|
1093
|
+
}
|
|
1094
|
+
interaction.setActive(false);
|
|
1095
|
+
});
|
|
1096
|
+
}
|
|
1097
|
+
unlockDragPan() {
|
|
1098
|
+
if (this.dragPanLocks === 0) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
this.dragPanLocks -= 1;
|
|
1102
|
+
if (this.dragPanLocks !== 0) {
|
|
1103
|
+
return;
|
|
1104
|
+
}
|
|
1105
|
+
if (!this.dragPanStates) {
|
|
1106
|
+
return;
|
|
1107
|
+
}
|
|
1108
|
+
this.dragPanStates.forEach((wasActive, interaction) => {
|
|
1109
|
+
interaction.setActive(wasActive);
|
|
1110
|
+
});
|
|
1111
|
+
this.dragPanStates = undefined;
|
|
1112
|
+
}
|
|
1113
|
+
createTranslateEvent(event, startCoordinate, type, items) {
|
|
1114
|
+
const features = new Collection(items.map((item) => item.feature));
|
|
1115
|
+
return {
|
|
1116
|
+
type,
|
|
1117
|
+
features,
|
|
1118
|
+
coordinate: event.coordinate,
|
|
1119
|
+
startCoordinate,
|
|
1120
|
+
mapBrowserEvent: event,
|
|
1121
|
+
};
|
|
1122
|
+
}
|
|
1123
|
+
createModifyEvent(event, type, items) {
|
|
1124
|
+
const features = new Collection(items.map((item) => item.feature));
|
|
1125
|
+
return {
|
|
1126
|
+
type,
|
|
1127
|
+
features,
|
|
1128
|
+
mapBrowserEvent: event,
|
|
1129
|
+
};
|
|
1130
|
+
}
|
|
1131
|
+
createDefaultHitTest() {
|
|
1132
|
+
return ({ layer, api, event, hitTolerance }) => {
|
|
1133
|
+
const items = [];
|
|
1134
|
+
let clusterHit;
|
|
1135
|
+
this.map.forEachFeatureAtPixel(event.pixel, (feature) => {
|
|
1136
|
+
if (!(feature instanceof Feature)) {
|
|
1137
|
+
return;
|
|
1138
|
+
}
|
|
1139
|
+
const clusterFeatures = getClusterFeatures(feature);
|
|
1140
|
+
if (clusterFeatures) {
|
|
1141
|
+
if (clusterFeatures.length > 1) {
|
|
1142
|
+
if (!clusterHit) {
|
|
1143
|
+
clusterHit = {
|
|
1144
|
+
feature: feature,
|
|
1145
|
+
features: clusterFeatures,
|
|
1146
|
+
size: clusterFeatures.length,
|
|
1147
|
+
};
|
|
1148
|
+
}
|
|
1149
|
+
return;
|
|
1150
|
+
}
|
|
1151
|
+
const singleFeature = clusterFeatures[0];
|
|
1152
|
+
const singleModel = api.getModelByFeature(singleFeature);
|
|
1153
|
+
if (!singleModel) {
|
|
1154
|
+
return;
|
|
1155
|
+
}
|
|
1156
|
+
items.push({ model: singleModel, feature: singleFeature });
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const model = api.getModelByFeature(feature);
|
|
1160
|
+
if (!model) {
|
|
1161
|
+
return;
|
|
1162
|
+
}
|
|
1163
|
+
items.push({ model, feature });
|
|
1164
|
+
}, {
|
|
1165
|
+
layerFilter: (candidateLayer) => candidateLayer === layer,
|
|
1166
|
+
hitTolerance,
|
|
1167
|
+
});
|
|
1168
|
+
return { items, cluster: clusterHit };
|
|
1169
|
+
};
|
|
1170
|
+
}
|
|
1171
|
+
collectFeaturePopupItems(entry, items, event) {
|
|
1172
|
+
const popup = entry.descriptor.feature.popup;
|
|
1173
|
+
if (!popup || !this.isEnabled(popup.enabled)) {
|
|
1174
|
+
return [];
|
|
1175
|
+
}
|
|
1176
|
+
return items.map((item) => {
|
|
1177
|
+
const popupItem = popup.item({
|
|
1178
|
+
model: item.model,
|
|
1179
|
+
feature: item.feature,
|
|
1180
|
+
ctx: this.ctx,
|
|
1181
|
+
event,
|
|
1182
|
+
});
|
|
1183
|
+
return {
|
|
1184
|
+
...popupItem,
|
|
1185
|
+
source: popupItem.source ?? 'feature',
|
|
1186
|
+
dedupKey: popupItem.dedupKey ?? entry.descriptor.feature.id(item.model),
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
1189
|
+
}
|
|
1190
|
+
collectPopupItems(entry, hitResult, event) {
|
|
1191
|
+
if (entry.descriptor.clustering && entry.api.isClusteringEnabled?.() && hitResult.cluster) {
|
|
1192
|
+
const clustering = entry.descriptor.clustering;
|
|
1193
|
+
if (clustering?.popup && this.isEnabled(clustering.popup.enabled)) {
|
|
1194
|
+
const models = hitResult.cluster.features
|
|
1195
|
+
.map((clusterFeature) => entry.api.getModelByFeature(clusterFeature))
|
|
1196
|
+
.filter((model) => model !== undefined);
|
|
1197
|
+
const popupItem = clustering.popup.item({
|
|
1198
|
+
models,
|
|
1199
|
+
size: hitResult.cluster.size,
|
|
1200
|
+
ctx: this.ctx,
|
|
1201
|
+
event,
|
|
1202
|
+
});
|
|
1203
|
+
return [
|
|
1204
|
+
{
|
|
1205
|
+
...popupItem,
|
|
1206
|
+
source: popupItem.source ?? 'cluster',
|
|
1207
|
+
dedupKey: popupItem.dedupKey ?? this.getClusterDedupKey(entry, models),
|
|
1208
|
+
},
|
|
1209
|
+
];
|
|
1210
|
+
}
|
|
1211
|
+
return [];
|
|
1212
|
+
}
|
|
1213
|
+
return this.collectFeaturePopupItems(entry, hitResult.items, event);
|
|
1214
|
+
}
|
|
1215
|
+
getClusterDedupKey(entry, models) {
|
|
1216
|
+
const ids = models
|
|
1217
|
+
.map((model) => String(entry.descriptor.feature.id(model)))
|
|
1218
|
+
.sort((a, b) => a.localeCompare(b))
|
|
1219
|
+
.join('|');
|
|
1220
|
+
return `cluster:${entry.descriptor.id}:${ids}`;
|
|
1221
|
+
}
|
|
1222
|
+
getOrderedLayers() {
|
|
1223
|
+
return this.schema.layers
|
|
1224
|
+
.map((descriptor, index) => ({
|
|
1225
|
+
descriptor,
|
|
1226
|
+
index,
|
|
1227
|
+
layer: this.layers[descriptor.id],
|
|
1228
|
+
api: this.apis[descriptor.id],
|
|
1229
|
+
}))
|
|
1230
|
+
.filter((entry) => entry.layer && entry.api)
|
|
1231
|
+
.sort((a, b) => {
|
|
1232
|
+
const aZ = a.layer.getZIndex() ?? 0;
|
|
1233
|
+
const bZ = b.layer.getZIndex() ?? 0;
|
|
1234
|
+
if (aZ !== bZ) {
|
|
1235
|
+
return bZ - aZ;
|
|
1236
|
+
}
|
|
1237
|
+
return a.index - b.index;
|
|
1238
|
+
});
|
|
1239
|
+
}
|
|
1240
|
+
getHitTolerance(hitTolerance) {
|
|
1241
|
+
if (hitTolerance !== undefined) {
|
|
1242
|
+
return hitTolerance;
|
|
1243
|
+
}
|
|
1244
|
+
if (this.schema.options?.hitTolerance !== undefined) {
|
|
1245
|
+
return this.schema.options.hitTolerance;
|
|
1246
|
+
}
|
|
1247
|
+
return 0;
|
|
1248
|
+
}
|
|
1249
|
+
isEnabled(enabled) {
|
|
1250
|
+
if (enabled === undefined) {
|
|
1251
|
+
return true;
|
|
1252
|
+
}
|
|
1253
|
+
if (typeof enabled === 'function') {
|
|
1254
|
+
return enabled();
|
|
1255
|
+
}
|
|
1256
|
+
return enabled;
|
|
1257
|
+
}
|
|
1258
|
+
processHover(entry, hover, items, event) {
|
|
1259
|
+
const prev = this.hoverItems.get(entry.descriptor.id) ?? new Map();
|
|
1260
|
+
const next = this.itemsToMap(entry, items);
|
|
1261
|
+
const entered = Array.from(next.entries())
|
|
1262
|
+
.filter(([id]) => !prev.has(id))
|
|
1263
|
+
.map(([, item]) => item);
|
|
1264
|
+
const left = Array.from(prev.entries())
|
|
1265
|
+
.filter(([id]) => !next.has(id))
|
|
1266
|
+
.map(([, item]) => item);
|
|
1267
|
+
let handled = false;
|
|
1268
|
+
if (left.length > 0 && hover.onLeave) {
|
|
1269
|
+
handled = this.isHandled(hover.onLeave({ items: left, ctx: this.ctx, event }));
|
|
1270
|
+
}
|
|
1271
|
+
if (entered.length > 0 && hover.onEnter) {
|
|
1272
|
+
handled = this.isHandled(hover.onEnter({ items: entered, ctx: this.ctx, event })) || handled;
|
|
1273
|
+
}
|
|
1274
|
+
if (hover.state) {
|
|
1275
|
+
this.applyState(left, hover.state, false);
|
|
1276
|
+
this.applyState(entered, hover.state, true);
|
|
1277
|
+
}
|
|
1278
|
+
this.hoverItems.set(entry.descriptor.id, next);
|
|
1279
|
+
return handled;
|
|
1280
|
+
}
|
|
1281
|
+
processSelect(entry, select, items, event) {
|
|
1282
|
+
if (items.length === 0) {
|
|
1283
|
+
const prev = this.selectedItems.get(entry.descriptor.id);
|
|
1284
|
+
this.selectedItems.set(entry.descriptor.id, new Map());
|
|
1285
|
+
if (select.onClear) {
|
|
1286
|
+
const handled = this.isHandled(select.onClear({ ctx: this.ctx, event }));
|
|
1287
|
+
if (select.state && prev) {
|
|
1288
|
+
this.applyState(Array.from(prev.values()), select.state, false);
|
|
1289
|
+
}
|
|
1290
|
+
return handled;
|
|
1291
|
+
}
|
|
1292
|
+
if (select.state && prev) {
|
|
1293
|
+
this.applyState(Array.from(prev.values()), select.state, false);
|
|
1294
|
+
}
|
|
1295
|
+
return false;
|
|
1296
|
+
}
|
|
1297
|
+
const prev = this.selectedItems.get(entry.descriptor.id) ?? new Map();
|
|
1298
|
+
const next = this.itemsToMap(entry, items);
|
|
1299
|
+
const handled = select.onSelect
|
|
1300
|
+
? this.isHandled(select.onSelect({ items, ctx: this.ctx, event }))
|
|
1301
|
+
: false;
|
|
1302
|
+
if (select.state) {
|
|
1303
|
+
const added = Array.from(next.entries())
|
|
1304
|
+
.filter(([id]) => !prev.has(id))
|
|
1305
|
+
.map(([, item]) => item);
|
|
1306
|
+
const removed = Array.from(prev.entries())
|
|
1307
|
+
.filter(([id]) => !next.has(id))
|
|
1308
|
+
.map(([, item]) => item);
|
|
1309
|
+
this.applyState(removed, select.state, false);
|
|
1310
|
+
this.applyState(added, select.state, true);
|
|
1311
|
+
}
|
|
1312
|
+
this.selectedItems.set(entry.descriptor.id, next);
|
|
1313
|
+
return handled;
|
|
1314
|
+
}
|
|
1315
|
+
processClick(entry, click, items, event) {
|
|
1316
|
+
if (items.length === 0) {
|
|
1317
|
+
return false;
|
|
1318
|
+
}
|
|
1319
|
+
return this.isHandled(click.onClick({ items, ctx: this.ctx, event }));
|
|
1320
|
+
}
|
|
1321
|
+
processDoubleClick(entry, doubleClick, items, event) {
|
|
1322
|
+
if (items.length === 0) {
|
|
1323
|
+
return false;
|
|
1324
|
+
}
|
|
1325
|
+
return this.isHandled(doubleClick.onDoubleClick({ items, ctx: this.ctx, event }));
|
|
1326
|
+
}
|
|
1327
|
+
itemsToMap(entry, items) {
|
|
1328
|
+
const next = new Map();
|
|
1329
|
+
items.forEach((item) => {
|
|
1330
|
+
const id = entry.descriptor.feature.id(item.model);
|
|
1331
|
+
next.set(id, item);
|
|
1332
|
+
});
|
|
1333
|
+
return next;
|
|
1334
|
+
}
|
|
1335
|
+
applyState(items, state, enabled) {
|
|
1336
|
+
const states = Array.isArray(state) ? state : [state];
|
|
1337
|
+
items.forEach((item) => {
|
|
1338
|
+
const current = new Set(getFeatureStates(item.feature));
|
|
1339
|
+
states.forEach((entry) => {
|
|
1340
|
+
if (enabled) {
|
|
1341
|
+
current.add(entry);
|
|
1342
|
+
}
|
|
1343
|
+
else {
|
|
1344
|
+
current.delete(entry);
|
|
1345
|
+
}
|
|
1346
|
+
});
|
|
1347
|
+
setFeatureStates(item.feature, Array.from(current));
|
|
1348
|
+
});
|
|
1349
|
+
}
|
|
1350
|
+
isHandled(result) {
|
|
1351
|
+
return result === true;
|
|
1352
|
+
}
|
|
1353
|
+
shouldStopPropagation(base) {
|
|
1354
|
+
return base.propagation !== 'continue';
|
|
1355
|
+
}
|
|
1356
|
+
shouldContinuePropagation(base) {
|
|
1357
|
+
return base.propagation === 'continue';
|
|
1358
|
+
}
|
|
1359
|
+
handleClusterClick(entry, clusterHit, event, manualPopupPush) {
|
|
1360
|
+
const clustering = entry.descriptor.clustering;
|
|
1361
|
+
if (!clustering) {
|
|
1362
|
+
return false;
|
|
1363
|
+
}
|
|
1364
|
+
const models = clusterHit.features
|
|
1365
|
+
.map((clusterFeature) => entry.api.getModelByFeature(clusterFeature))
|
|
1366
|
+
.filter((model) => model !== undefined);
|
|
1367
|
+
let handled = false;
|
|
1368
|
+
if (clustering.expandOnClick) {
|
|
1369
|
+
this.expandCluster(clusterHit, clustering.expandOnClick);
|
|
1370
|
+
clustering.expandOnClick.onExpanded?.({ models, ctx: this.ctx });
|
|
1371
|
+
handled = true;
|
|
1372
|
+
}
|
|
1373
|
+
if (clustering.popup && this.isEnabled(clustering.popup.enabled)) {
|
|
1374
|
+
if (manualPopupPush || !this.ctx.popupHost) {
|
|
1375
|
+
const item = clustering.popup.item({ models, size: clusterHit.size, ctx: this.ctx, event });
|
|
1376
|
+
if (manualPopupPush && this.ctx.popupHost) {
|
|
1377
|
+
this.ctx.popupHost.push([
|
|
1378
|
+
{
|
|
1379
|
+
...item,
|
|
1380
|
+
source: item.source ?? 'cluster',
|
|
1381
|
+
dedupKey: item.dedupKey ?? this.getClusterDedupKey(entry, models),
|
|
1382
|
+
},
|
|
1383
|
+
]);
|
|
1384
|
+
}
|
|
1385
|
+
}
|
|
1386
|
+
handled = true;
|
|
1387
|
+
}
|
|
1388
|
+
return handled;
|
|
1389
|
+
}
|
|
1390
|
+
expandCluster(clusterHit, expand) {
|
|
1391
|
+
const view = this.map.getView();
|
|
1392
|
+
const duration = expand.durationMs;
|
|
1393
|
+
if (expand.mode === 'zoomIn') {
|
|
1394
|
+
const currentZoom = view.getZoom() ?? 0;
|
|
1395
|
+
const delta = expand.zoomDelta ?? 1;
|
|
1396
|
+
const targetZoom = currentZoom + delta;
|
|
1397
|
+
const nextZoom = expand.maxZoom !== undefined ? Math.min(expand.maxZoom, targetZoom) : targetZoom;
|
|
1398
|
+
view.animate({ zoom: nextZoom, duration });
|
|
1399
|
+
return;
|
|
1400
|
+
}
|
|
1401
|
+
const extent = createEmpty();
|
|
1402
|
+
clusterHit.features.forEach((feature) => {
|
|
1403
|
+
const geometry = feature.getGeometry();
|
|
1404
|
+
if (geometry) {
|
|
1405
|
+
extend(extent, geometry.getExtent());
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
if (isEmpty(extent)) {
|
|
1409
|
+
return;
|
|
1410
|
+
}
|
|
1411
|
+
const padding = toOlPadding(expand.padding, undefined);
|
|
1412
|
+
view.fit(extent, {
|
|
1413
|
+
padding,
|
|
1414
|
+
duration,
|
|
1415
|
+
maxZoom: expand.maxZoom,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
shouldStopClusterPropagation() {
|
|
1419
|
+
return true;
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
|
|
1423
|
+
class StyleCache {
|
|
1424
|
+
stringCache = new Map();
|
|
1425
|
+
get(key) {
|
|
1426
|
+
return this.stringCache.get(key);
|
|
1427
|
+
}
|
|
1428
|
+
set(key, value) {
|
|
1429
|
+
this.stringCache.set(key, value);
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
|
|
1433
|
+
const resolveMaybeFn = (value, args) => {
|
|
1434
|
+
if (typeof value === 'function') {
|
|
1435
|
+
return value(...args);
|
|
1436
|
+
}
|
|
1437
|
+
return value;
|
|
1438
|
+
};
|
|
1439
|
+
const applyPatch = (opts, patch) => {
|
|
1440
|
+
const patchValue = typeof patch === 'function' ? patch(opts) : patch;
|
|
1441
|
+
return { ...opts, ...patchValue };
|
|
1442
|
+
};
|
|
1443
|
+
const orderStates = (states, priority) => {
|
|
1444
|
+
if (!priority || priority.length === 0) {
|
|
1445
|
+
return states;
|
|
1446
|
+
}
|
|
1447
|
+
const prioritySet = new Set(priority);
|
|
1448
|
+
const ordered = priority.filter((state) => states.includes(state));
|
|
1449
|
+
const rest = states.filter((state) => !prioritySet.has(state));
|
|
1450
|
+
return [...ordered, ...rest];
|
|
1451
|
+
};
|
|
1452
|
+
const createStyleFunction = (options) => {
|
|
1453
|
+
const { descriptor, registryGetModel, map } = options;
|
|
1454
|
+
const { style } = descriptor;
|
|
1455
|
+
const cache = new StyleCache();
|
|
1456
|
+
return (feature, resolution) => {
|
|
1457
|
+
if (!(feature instanceof Feature)) {
|
|
1458
|
+
return [];
|
|
1459
|
+
}
|
|
1460
|
+
const model = registryGetModel(feature);
|
|
1461
|
+
if (!model) {
|
|
1462
|
+
return [];
|
|
1463
|
+
}
|
|
1464
|
+
const view = {
|
|
1465
|
+
resolution,
|
|
1466
|
+
zoom: map?.getView().getZoom(),
|
|
1467
|
+
};
|
|
1468
|
+
let opts = resolveMaybeFn(style.base, [model, view]);
|
|
1469
|
+
if (style.states) {
|
|
1470
|
+
const featureStates = orderStates(getFeatureStates(feature), style.statePriority);
|
|
1471
|
+
featureStates.forEach((state) => {
|
|
1472
|
+
const patchFactory = style.states?.[state];
|
|
1473
|
+
if (!patchFactory) {
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1476
|
+
const patch = resolveMaybeFn(patchFactory, [model, view]);
|
|
1477
|
+
opts = applyPatch(opts, patch);
|
|
1478
|
+
});
|
|
1479
|
+
}
|
|
1480
|
+
if (style.cacheKey) {
|
|
1481
|
+
const key = style.cacheKey(opts, view);
|
|
1482
|
+
if (key) {
|
|
1483
|
+
const cached = cache.get(key);
|
|
1484
|
+
if (cached) {
|
|
1485
|
+
return cached;
|
|
1486
|
+
}
|
|
1487
|
+
const rendered = style.render(opts, view);
|
|
1488
|
+
cache.set(key, rendered);
|
|
1489
|
+
return rendered;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
return style.render(opts, view);
|
|
1493
|
+
};
|
|
1494
|
+
};
|
|
1495
|
+
const createClusterStyleFunction = (options) => {
|
|
1496
|
+
const { clustering, registryGetModel, map } = options;
|
|
1497
|
+
const baseStyle = createStyleFunction(options);
|
|
1498
|
+
const cache = new StyleCache();
|
|
1499
|
+
return (feature, resolution) => {
|
|
1500
|
+
if (!(feature instanceof Feature)) {
|
|
1501
|
+
return [];
|
|
1502
|
+
}
|
|
1503
|
+
const clusterFeatures = getClusterFeatures(feature);
|
|
1504
|
+
if (!clusterFeatures) {
|
|
1505
|
+
return baseStyle(feature, resolution);
|
|
1506
|
+
}
|
|
1507
|
+
const size = clusterFeatures.length;
|
|
1508
|
+
if (size === 1) {
|
|
1509
|
+
return baseStyle(clusterFeatures[0], resolution);
|
|
1510
|
+
}
|
|
1511
|
+
const models = clusterFeatures
|
|
1512
|
+
.map((clusterFeature) => registryGetModel(clusterFeature))
|
|
1513
|
+
.filter((model) => model !== undefined);
|
|
1514
|
+
const view = {
|
|
1515
|
+
resolution,
|
|
1516
|
+
zoom: map?.getView().getZoom(),
|
|
1517
|
+
};
|
|
1518
|
+
if (clustering.clusterStyle.cacheKey) {
|
|
1519
|
+
const key = clustering.clusterStyle.cacheKey({ models, size, view });
|
|
1520
|
+
if (key) {
|
|
1521
|
+
const cached = cache.get(key);
|
|
1522
|
+
if (cached) {
|
|
1523
|
+
return cached;
|
|
1524
|
+
}
|
|
1525
|
+
const rendered = clustering.clusterStyle.render({ models, size, view });
|
|
1526
|
+
cache.set(key, rendered);
|
|
1527
|
+
return rendered;
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
return clustering.clusterStyle.render({ models, size, view });
|
|
1531
|
+
};
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
class FeatureRegistry {
|
|
1535
|
+
idToModel = new Map();
|
|
1536
|
+
idToFeature = new Map();
|
|
1537
|
+
featureToId = new WeakMap();
|
|
1538
|
+
set(id, model, feature) {
|
|
1539
|
+
this.idToModel.set(id, model);
|
|
1540
|
+
this.idToFeature.set(id, feature);
|
|
1541
|
+
this.featureToId.set(feature, id);
|
|
1542
|
+
}
|
|
1543
|
+
updateModel(id, model) {
|
|
1544
|
+
if (!this.idToModel.has(id)) {
|
|
1545
|
+
return;
|
|
1546
|
+
}
|
|
1547
|
+
this.idToModel.set(id, model);
|
|
1548
|
+
}
|
|
1549
|
+
getModel(id) {
|
|
1550
|
+
return this.idToModel.get(id);
|
|
1551
|
+
}
|
|
1552
|
+
getFeature(id) {
|
|
1553
|
+
return this.idToFeature.get(id);
|
|
1554
|
+
}
|
|
1555
|
+
getIdByFeature(feature) {
|
|
1556
|
+
return this.featureToId.get(feature);
|
|
1557
|
+
}
|
|
1558
|
+
getModelByFeature(feature) {
|
|
1559
|
+
const id = this.featureToId.get(feature);
|
|
1560
|
+
if (id === undefined) {
|
|
1561
|
+
return undefined;
|
|
1562
|
+
}
|
|
1563
|
+
return this.idToModel.get(id);
|
|
1564
|
+
}
|
|
1565
|
+
has(id) {
|
|
1566
|
+
return this.idToModel.has(id);
|
|
1567
|
+
}
|
|
1568
|
+
remove(id) {
|
|
1569
|
+
const feature = this.idToFeature.get(id);
|
|
1570
|
+
if (!feature) {
|
|
1571
|
+
return undefined;
|
|
1572
|
+
}
|
|
1573
|
+
this.idToModel.delete(id);
|
|
1574
|
+
this.idToFeature.delete(id);
|
|
1575
|
+
this.featureToId.delete(feature);
|
|
1576
|
+
return feature;
|
|
1577
|
+
}
|
|
1578
|
+
forEachId(fn) {
|
|
1579
|
+
this.idToModel.forEach((_model, id) => fn(id));
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
|
|
1583
|
+
class VectorLayerBase {
|
|
1584
|
+
descriptor;
|
|
1585
|
+
layer;
|
|
1586
|
+
source;
|
|
1587
|
+
registry = new FeatureRegistry();
|
|
1588
|
+
scheduleInvalidate;
|
|
1589
|
+
ctx;
|
|
1590
|
+
changeHandlers = new Set();
|
|
1591
|
+
constructor(options) {
|
|
1592
|
+
this.descriptor = options.descriptor.feature;
|
|
1593
|
+
this.layer = options.layer;
|
|
1594
|
+
this.source = options.source;
|
|
1595
|
+
this.ctx = options.ctx;
|
|
1596
|
+
this.scheduleInvalidate = options.scheduleInvalidate;
|
|
1597
|
+
}
|
|
1598
|
+
setModels(models) {
|
|
1599
|
+
this.setModelsInternal(models);
|
|
1600
|
+
}
|
|
1601
|
+
invalidate() {
|
|
1602
|
+
this.scheduleInvalidate();
|
|
1603
|
+
}
|
|
1604
|
+
syncFeatureFromModel(model) {
|
|
1605
|
+
const id = this.descriptor.id(model);
|
|
1606
|
+
const feature = this.registry.getFeature(id);
|
|
1607
|
+
if (!feature) {
|
|
1608
|
+
return;
|
|
1609
|
+
}
|
|
1610
|
+
feature.setGeometry(this.descriptor.geometry.fromModel(model));
|
|
1611
|
+
}
|
|
1612
|
+
getModelByFeature(feature) {
|
|
1613
|
+
return this.registry.getModelByFeature(feature);
|
|
1614
|
+
}
|
|
1615
|
+
mutate(id, update, reason = 'mutate') {
|
|
1616
|
+
const prev = this.registry.getModel(id);
|
|
1617
|
+
if (!prev) {
|
|
1618
|
+
return;
|
|
1619
|
+
}
|
|
1620
|
+
const next = update(prev);
|
|
1621
|
+
if (next === prev) {
|
|
1622
|
+
return;
|
|
1623
|
+
}
|
|
1624
|
+
this.registry.updateModel(id, next);
|
|
1625
|
+
this.syncFeatureFromModel(next);
|
|
1626
|
+
this.scheduleInvalidate();
|
|
1627
|
+
this.emitModelChanges([{ prev, next, reason }]);
|
|
1628
|
+
}
|
|
1629
|
+
mutateMany(ids, update, reason = 'mutate') {
|
|
1630
|
+
const changes = [];
|
|
1631
|
+
ids.forEach((id) => {
|
|
1632
|
+
const prev = this.registry.getModel(id);
|
|
1633
|
+
if (!prev) {
|
|
1634
|
+
return;
|
|
1635
|
+
}
|
|
1636
|
+
const next = update(prev);
|
|
1637
|
+
if (next === prev) {
|
|
1638
|
+
return;
|
|
1639
|
+
}
|
|
1640
|
+
this.registry.updateModel(id, next);
|
|
1641
|
+
this.syncFeatureFromModel(next);
|
|
1642
|
+
changes.push({ prev, next, reason });
|
|
1643
|
+
});
|
|
1644
|
+
if (changes.length === 0) {
|
|
1645
|
+
return;
|
|
1646
|
+
}
|
|
1647
|
+
this.scheduleInvalidate();
|
|
1648
|
+
this.emitModelChanges(changes);
|
|
1649
|
+
}
|
|
1650
|
+
onModelsChanged(cb) {
|
|
1651
|
+
this.changeHandlers.add(cb);
|
|
1652
|
+
return () => this.changeHandlers.delete(cb);
|
|
1653
|
+
}
|
|
1654
|
+
/** Fit view to all features on the layer. No-op if extent is empty. */
|
|
1655
|
+
centerOnAllModels(opts) {
|
|
1656
|
+
const current = this.getCenterOnAllModelsSource();
|
|
1657
|
+
const extent = current?.getExtent?.();
|
|
1658
|
+
if (!extent || isEmpty(extent)) {
|
|
1659
|
+
return;
|
|
1660
|
+
}
|
|
1661
|
+
this.ctx.map.getView().fit(extent, toOlFitOptions(opts));
|
|
1662
|
+
}
|
|
1663
|
+
/** Fit view to a single feature by id. No-op if feature/geometry is missing. */
|
|
1664
|
+
centerOnModel(id, opts) {
|
|
1665
|
+
const feature = this.registry.getFeature(id);
|
|
1666
|
+
const geom = feature?.getGeometry();
|
|
1667
|
+
if (!geom) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
this.ctx.map.getView().fit(geom.getExtent(), toOlFitOptions(opts));
|
|
1671
|
+
}
|
|
1672
|
+
/**
|
|
1673
|
+
* Fit view to a subset of features by ids (combined extent).
|
|
1674
|
+
* Missing ids are ignored. No-op if none found.
|
|
1675
|
+
*/
|
|
1676
|
+
centerOnModels(ids, opts) {
|
|
1677
|
+
const extent = createEmpty();
|
|
1678
|
+
let found = false;
|
|
1679
|
+
for (const id of ids) {
|
|
1680
|
+
const feature = this.registry.getFeature(id);
|
|
1681
|
+
const geom = feature?.getGeometry();
|
|
1682
|
+
if (!geom)
|
|
1683
|
+
continue;
|
|
1684
|
+
extend(extent, geom.getExtent());
|
|
1685
|
+
found = true;
|
|
1686
|
+
}
|
|
1687
|
+
if (!found || isEmpty(extent)) {
|
|
1688
|
+
return;
|
|
1689
|
+
}
|
|
1690
|
+
this.ctx.map.getView().fit(extent, toOlFitOptions(opts));
|
|
1691
|
+
}
|
|
1692
|
+
setVisible(visible) {
|
|
1693
|
+
this.layer.setVisible(visible);
|
|
1694
|
+
}
|
|
1695
|
+
isVisible() {
|
|
1696
|
+
return this.layer.getVisible();
|
|
1697
|
+
}
|
|
1698
|
+
setOpacity(opacity) {
|
|
1699
|
+
this.layer.setOpacity(opacity);
|
|
1700
|
+
}
|
|
1701
|
+
getOpacity() {
|
|
1702
|
+
return this.layer.getOpacity();
|
|
1703
|
+
}
|
|
1704
|
+
getZIndex() {
|
|
1705
|
+
return this.layer.getZIndex();
|
|
1706
|
+
}
|
|
1707
|
+
setZIndex(z) {
|
|
1708
|
+
this.layer.setZIndex(z);
|
|
1709
|
+
}
|
|
1710
|
+
getModelById(id) {
|
|
1711
|
+
return this.registry.getModel(id);
|
|
1712
|
+
}
|
|
1713
|
+
hasModel(id) {
|
|
1714
|
+
return this.registry.getFeature(id) != null;
|
|
1715
|
+
}
|
|
1716
|
+
getAllModels() {
|
|
1717
|
+
const out = [];
|
|
1718
|
+
this.registry.forEachId((id) => {
|
|
1719
|
+
const model = this.registry.getModel(id);
|
|
1720
|
+
if (model !== undefined) {
|
|
1721
|
+
out.push(model);
|
|
1722
|
+
}
|
|
1723
|
+
});
|
|
1724
|
+
return out;
|
|
1725
|
+
}
|
|
1726
|
+
getAllModelIds() {
|
|
1727
|
+
const out = [];
|
|
1728
|
+
this.registry.forEachId((id) => out.push(id));
|
|
1729
|
+
return out;
|
|
1730
|
+
}
|
|
1731
|
+
setFeatureStates(ids, states) {
|
|
1732
|
+
const targetIds = Array.isArray(ids) ? ids : [ids];
|
|
1733
|
+
const nextStates = states ? (Array.isArray(states) ? states : [states]) : [];
|
|
1734
|
+
targetIds.forEach((id) => {
|
|
1735
|
+
const feature = this.registry.getFeature(id);
|
|
1736
|
+
if (feature) {
|
|
1737
|
+
setFeatureStates(feature, nextStates);
|
|
1738
|
+
}
|
|
1739
|
+
});
|
|
1740
|
+
this.scheduleInvalidate();
|
|
1741
|
+
}
|
|
1742
|
+
setModelsInternal(models) {
|
|
1743
|
+
const nextIds = new Set();
|
|
1744
|
+
models.forEach((model) => {
|
|
1745
|
+
const id = this.descriptor.id(model);
|
|
1746
|
+
nextIds.add(id);
|
|
1747
|
+
const existingFeature = this.registry.getFeature(id);
|
|
1748
|
+
if (existingFeature) {
|
|
1749
|
+
const prevModel = this.registry.getModel(id);
|
|
1750
|
+
if (prevModel !== model) {
|
|
1751
|
+
this.registry.updateModel(id, model);
|
|
1752
|
+
existingFeature.setGeometry(this.descriptor.geometry.fromModel(model));
|
|
1753
|
+
}
|
|
1754
|
+
return;
|
|
1755
|
+
}
|
|
1756
|
+
const feature = new Feature({
|
|
1757
|
+
geometry: this.descriptor.geometry.fromModel(model),
|
|
1758
|
+
});
|
|
1759
|
+
feature.setId(id);
|
|
1760
|
+
this.registry.set(id, model, feature);
|
|
1761
|
+
this.source.addFeature(feature);
|
|
1762
|
+
this.descriptor.geometry.onCreate?.({ feature, model, ctx: this.ctx });
|
|
1763
|
+
});
|
|
1764
|
+
const toRemove = [];
|
|
1765
|
+
this.registry.forEachId((id) => {
|
|
1766
|
+
if (!nextIds.has(id)) {
|
|
1767
|
+
toRemove.push(id);
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
toRemove.forEach((id) => {
|
|
1771
|
+
const feature = this.registry.remove(id);
|
|
1772
|
+
if (feature) {
|
|
1773
|
+
this.source.removeFeature(feature);
|
|
1774
|
+
}
|
|
1775
|
+
});
|
|
1776
|
+
}
|
|
1777
|
+
getCenterOnAllModelsSource() {
|
|
1778
|
+
return this.source;
|
|
1779
|
+
}
|
|
1780
|
+
emitModelChanges(changes) {
|
|
1781
|
+
if (changes.length === 0) {
|
|
1782
|
+
return;
|
|
1783
|
+
}
|
|
1784
|
+
this.changeHandlers.forEach((handler) => handler(changes));
|
|
1785
|
+
}
|
|
1786
|
+
}
|
|
1787
|
+
|
|
1788
|
+
class ClusteredVectorLayer extends VectorLayerBase {
|
|
1789
|
+
clusterSource;
|
|
1790
|
+
clusteringEnabled;
|
|
1791
|
+
constructor(options) {
|
|
1792
|
+
super(options);
|
|
1793
|
+
this.clusterSource = options.clusterSource;
|
|
1794
|
+
this.clusteringEnabled = options.descriptor.clustering?.enabledByDefault ?? false;
|
|
1795
|
+
this.layer.setStyle(createClusterStyleFunction({
|
|
1796
|
+
descriptor: this.descriptor,
|
|
1797
|
+
clustering: options.descriptor.clustering,
|
|
1798
|
+
ctx: this.ctx,
|
|
1799
|
+
registryGetModel: (feature) => this.registry.getModelByFeature(feature),
|
|
1800
|
+
map: this.ctx.map,
|
|
1801
|
+
}));
|
|
1802
|
+
this.layer.setSource(this.clusteringEnabled ? this.clusterSource : this.source);
|
|
1803
|
+
}
|
|
1804
|
+
setModels(models) {
|
|
1805
|
+
this.setModelsInternal(models);
|
|
1806
|
+
this.scheduleInvalidate();
|
|
1807
|
+
}
|
|
1808
|
+
setClusteringEnabled(enabled) {
|
|
1809
|
+
if (this.clusteringEnabled === enabled) {
|
|
1810
|
+
return;
|
|
1811
|
+
}
|
|
1812
|
+
this.clusteringEnabled = enabled;
|
|
1813
|
+
this.clearInteractionStates();
|
|
1814
|
+
this.layer.setSource(enabled ? this.clusterSource : this.source);
|
|
1815
|
+
if (enabled) {
|
|
1816
|
+
const view = this.ctx.map.getView();
|
|
1817
|
+
const resolution = view.getResolution() ?? 1;
|
|
1818
|
+
this.clusterSource.loadFeatures(createEmpty(), resolution, view.getProjection());
|
|
1819
|
+
this.clusterSource.refresh();
|
|
1820
|
+
}
|
|
1821
|
+
this.scheduleInvalidate();
|
|
1822
|
+
}
|
|
1823
|
+
isClusteringEnabled() {
|
|
1824
|
+
return this.clusteringEnabled;
|
|
1825
|
+
}
|
|
1826
|
+
clearInteractionStates() {
|
|
1827
|
+
this.registry.forEachId((id) => {
|
|
1828
|
+
const feature = this.registry.getFeature(id);
|
|
1829
|
+
if (feature) {
|
|
1830
|
+
clearFeatureStates(feature);
|
|
1831
|
+
}
|
|
1832
|
+
});
|
|
1833
|
+
}
|
|
1834
|
+
getCenterOnAllModelsSource() {
|
|
1835
|
+
return this.layer.getSource();
|
|
1836
|
+
}
|
|
1837
|
+
}
|
|
1838
|
+
|
|
1839
|
+
class PlainVectorLayer extends VectorLayerBase {
|
|
1840
|
+
constructor(options) {
|
|
1841
|
+
super(options);
|
|
1842
|
+
this.layer.setStyle(createStyleFunction({
|
|
1843
|
+
descriptor: this.descriptor,
|
|
1844
|
+
ctx: this.ctx,
|
|
1845
|
+
registryGetModel: (feature) => this.registry.getModelByFeature(feature),
|
|
1846
|
+
map: this.ctx.map,
|
|
1847
|
+
}));
|
|
1848
|
+
}
|
|
1849
|
+
}
|
|
1850
|
+
|
|
1851
|
+
class PopupHost {
|
|
1852
|
+
options;
|
|
1853
|
+
items = [];
|
|
1854
|
+
container = null;
|
|
1855
|
+
target = null;
|
|
1856
|
+
constructor(options = {}) {
|
|
1857
|
+
this.options = options;
|
|
1858
|
+
if (options.mount) {
|
|
1859
|
+
this.mount(options.mount);
|
|
1860
|
+
}
|
|
1861
|
+
}
|
|
1862
|
+
push(items) {
|
|
1863
|
+
if (!this.ensureEnabled()) {
|
|
1864
|
+
return;
|
|
1865
|
+
}
|
|
1866
|
+
this.items = this.prepareItems([...this.items, ...items]);
|
|
1867
|
+
this.render();
|
|
1868
|
+
}
|
|
1869
|
+
set(items) {
|
|
1870
|
+
if (!this.ensureEnabled()) {
|
|
1871
|
+
return;
|
|
1872
|
+
}
|
|
1873
|
+
this.items = this.prepareItems(items);
|
|
1874
|
+
this.render();
|
|
1875
|
+
}
|
|
1876
|
+
clear() {
|
|
1877
|
+
if (!this.ensureEnabled()) {
|
|
1878
|
+
return;
|
|
1879
|
+
}
|
|
1880
|
+
this.items = [];
|
|
1881
|
+
this.render();
|
|
1882
|
+
}
|
|
1883
|
+
remove(key) {
|
|
1884
|
+
if (!this.ensureEnabled()) {
|
|
1885
|
+
return;
|
|
1886
|
+
}
|
|
1887
|
+
this.items = this.items.filter((item) => this.getDedupKey(item) !== key);
|
|
1888
|
+
this.render();
|
|
1889
|
+
}
|
|
1890
|
+
getItems() {
|
|
1891
|
+
if (!this.isEnabled()) {
|
|
1892
|
+
return [];
|
|
1893
|
+
}
|
|
1894
|
+
return [...this.items];
|
|
1895
|
+
}
|
|
1896
|
+
mount(target) {
|
|
1897
|
+
if (!this.ensureEnabled()) {
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
const element = typeof target === 'function' ? target() : target;
|
|
1901
|
+
if (!element) {
|
|
1902
|
+
return;
|
|
1903
|
+
}
|
|
1904
|
+
if (this.target !== element) {
|
|
1905
|
+
this.unmount();
|
|
1906
|
+
}
|
|
1907
|
+
this.target = element;
|
|
1908
|
+
this.render();
|
|
1909
|
+
}
|
|
1910
|
+
dispose() {
|
|
1911
|
+
this.unmount();
|
|
1912
|
+
this.items = [];
|
|
1913
|
+
}
|
|
1914
|
+
ensureEnabled() {
|
|
1915
|
+
if (this.isEnabled()) {
|
|
1916
|
+
return true;
|
|
1917
|
+
}
|
|
1918
|
+
this.items = [];
|
|
1919
|
+
this.unmount();
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
isEnabled() {
|
|
1923
|
+
const enabled = this.options.enabled;
|
|
1924
|
+
if (enabled === undefined) {
|
|
1925
|
+
return true;
|
|
1926
|
+
}
|
|
1927
|
+
return typeof enabled === 'function' ? enabled() : enabled;
|
|
1928
|
+
}
|
|
1929
|
+
unmount() {
|
|
1930
|
+
if (this.container && this.container.parentElement) {
|
|
1931
|
+
this.container.parentElement.removeChild(this.container);
|
|
1932
|
+
}
|
|
1933
|
+
this.container = null;
|
|
1934
|
+
this.target = null;
|
|
1935
|
+
}
|
|
1936
|
+
prepareItems(items) {
|
|
1937
|
+
const deduped = this.dedup(items);
|
|
1938
|
+
const sorted = this.applySort(deduped);
|
|
1939
|
+
return this.applyLimit(sorted);
|
|
1940
|
+
}
|
|
1941
|
+
dedup(items) {
|
|
1942
|
+
const result = [];
|
|
1943
|
+
const indices = new Map();
|
|
1944
|
+
items.forEach((item) => {
|
|
1945
|
+
const key = this.getDedupKey(item);
|
|
1946
|
+
if (key === undefined) {
|
|
1947
|
+
result.push(item);
|
|
1948
|
+
return;
|
|
1949
|
+
}
|
|
1950
|
+
const existingIndex = indices.get(key);
|
|
1951
|
+
if (existingIndex !== undefined) {
|
|
1952
|
+
result[existingIndex] = item;
|
|
1953
|
+
return;
|
|
1954
|
+
}
|
|
1955
|
+
indices.set(key, result.length);
|
|
1956
|
+
result.push(item);
|
|
1957
|
+
});
|
|
1958
|
+
return result;
|
|
1959
|
+
}
|
|
1960
|
+
applySort(items) {
|
|
1961
|
+
const comparator = this.options.sort ?? this.defaultSort;
|
|
1962
|
+
const withIndex = items.map((item, index) => ({ item, index }));
|
|
1963
|
+
withIndex.sort((a, b) => {
|
|
1964
|
+
const result = comparator(a.item, b.item);
|
|
1965
|
+
if (result !== 0) {
|
|
1966
|
+
return result;
|
|
1967
|
+
}
|
|
1968
|
+
return a.index - b.index;
|
|
1969
|
+
});
|
|
1970
|
+
return withIndex.map((entry) => entry.item);
|
|
1971
|
+
}
|
|
1972
|
+
applyLimit(items) {
|
|
1973
|
+
if (this.options.maxItems === undefined) {
|
|
1974
|
+
return items;
|
|
1975
|
+
}
|
|
1976
|
+
return items.slice(0, this.options.maxItems);
|
|
1977
|
+
}
|
|
1978
|
+
getDedupKey(item) {
|
|
1979
|
+
if (item.dedupKey !== undefined) {
|
|
1980
|
+
return item.dedupKey;
|
|
1981
|
+
}
|
|
1982
|
+
const model = item.model;
|
|
1983
|
+
if (model && (typeof model.id === 'string' || typeof model.id === 'number')) {
|
|
1984
|
+
return model.id;
|
|
1985
|
+
}
|
|
1986
|
+
return undefined;
|
|
1987
|
+
}
|
|
1988
|
+
defaultSort(a, b) {
|
|
1989
|
+
const aPriority = a.priority ?? 0;
|
|
1990
|
+
const bPriority = b.priority ?? 0;
|
|
1991
|
+
return bPriority - aPriority;
|
|
1992
|
+
}
|
|
1993
|
+
render() {
|
|
1994
|
+
if (!this.target) {
|
|
1995
|
+
return;
|
|
1996
|
+
}
|
|
1997
|
+
if (!this.container) {
|
|
1998
|
+
this.container = document.createElement('div');
|
|
1999
|
+
this.container.className = 'mf-popup-host';
|
|
2000
|
+
this.target.appendChild(this.container);
|
|
2001
|
+
}
|
|
2002
|
+
this.container.innerHTML = '';
|
|
2003
|
+
this.items.forEach((item) => {
|
|
2004
|
+
const card = document.createElement('div');
|
|
2005
|
+
card.className = 'mf-popup-item';
|
|
2006
|
+
if (item.className) {
|
|
2007
|
+
card.classList.add(item.className);
|
|
2008
|
+
}
|
|
2009
|
+
if (typeof item.content === 'string') {
|
|
2010
|
+
card.textContent = item.content;
|
|
2011
|
+
}
|
|
2012
|
+
else {
|
|
2013
|
+
card.appendChild(item.content);
|
|
2014
|
+
}
|
|
2015
|
+
const key = this.getDedupKey(item);
|
|
2016
|
+
if (key !== undefined) {
|
|
2017
|
+
const close = document.createElement('button');
|
|
2018
|
+
close.type = 'button';
|
|
2019
|
+
close.dataset['popupClose'] = String(key);
|
|
2020
|
+
close.textContent = '×';
|
|
2021
|
+
close.addEventListener('click', () => this.remove(key));
|
|
2022
|
+
card.appendChild(close);
|
|
2023
|
+
}
|
|
2024
|
+
this.container.appendChild(card);
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
}
|
|
2028
|
+
|
|
2029
|
+
class LayerManager {
|
|
2030
|
+
map;
|
|
2031
|
+
layers = {};
|
|
2032
|
+
apis = {};
|
|
2033
|
+
interactions;
|
|
2034
|
+
ctx;
|
|
2035
|
+
constructor(map, schema) {
|
|
2036
|
+
this.map = map;
|
|
2037
|
+
const popupHost = schema.options?.popupHost ? new PopupHost(schema.options.popupHost) : undefined;
|
|
2038
|
+
const scheduler = new FlushScheduler(schema.options?.scheduler?.policy ?? 'microtask');
|
|
2039
|
+
const ctx = createMapContext(this.map, this.apis, popupHost, scheduler);
|
|
2040
|
+
this.ctx = ctx;
|
|
2041
|
+
schema.layers.forEach((descriptor) => {
|
|
2042
|
+
const source = new VectorSource();
|
|
2043
|
+
const clusterSource = descriptor.clustering
|
|
2044
|
+
? new ClusterSource({
|
|
2045
|
+
source,
|
|
2046
|
+
distance: descriptor.clustering.distance,
|
|
2047
|
+
})
|
|
2048
|
+
: null;
|
|
2049
|
+
const layer = new VectorLayer({ source });
|
|
2050
|
+
if (descriptor.zIndex !== undefined) {
|
|
2051
|
+
layer.setZIndex(descriptor.zIndex);
|
|
2052
|
+
}
|
|
2053
|
+
if (descriptor.visibleByDefault !== undefined) {
|
|
2054
|
+
layer.setVisible(descriptor.visibleByDefault);
|
|
2055
|
+
}
|
|
2056
|
+
if (descriptor.title) {
|
|
2057
|
+
layer.set('title', descriptor.title);
|
|
2058
|
+
}
|
|
2059
|
+
layer.set('id', descriptor.id);
|
|
2060
|
+
const scheduleInvalidate = () => scheduler.schedule(layer, () => layer.changed());
|
|
2061
|
+
const api = descriptor.clustering
|
|
2062
|
+
? new ClusteredVectorLayer({
|
|
2063
|
+
descriptor,
|
|
2064
|
+
layer,
|
|
2065
|
+
source,
|
|
2066
|
+
clusterSource: clusterSource,
|
|
2067
|
+
ctx,
|
|
2068
|
+
scheduleInvalidate,
|
|
2069
|
+
})
|
|
2070
|
+
: new PlainVectorLayer({
|
|
2071
|
+
descriptor,
|
|
2072
|
+
layer,
|
|
2073
|
+
source,
|
|
2074
|
+
ctx,
|
|
2075
|
+
scheduleInvalidate,
|
|
2076
|
+
});
|
|
2077
|
+
this.layers[descriptor.id] = layer;
|
|
2078
|
+
this.apis[descriptor.id] = api;
|
|
2079
|
+
this.map.addLayer(layer);
|
|
2080
|
+
});
|
|
2081
|
+
this.interactions = new InteractionManager({
|
|
2082
|
+
ctx,
|
|
2083
|
+
map: this.map,
|
|
2084
|
+
schema,
|
|
2085
|
+
layers: this.layers,
|
|
2086
|
+
apis: this.apis,
|
|
2087
|
+
});
|
|
2088
|
+
}
|
|
2089
|
+
static create(map, schema) {
|
|
2090
|
+
return new LayerManager(map, schema);
|
|
2091
|
+
}
|
|
2092
|
+
getLayer(id) {
|
|
2093
|
+
return this.layers[id];
|
|
2094
|
+
}
|
|
2095
|
+
getApi(id) {
|
|
2096
|
+
return this.apis[id];
|
|
2097
|
+
}
|
|
2098
|
+
getApis() {
|
|
2099
|
+
return { ...this.apis };
|
|
2100
|
+
}
|
|
2101
|
+
getContext() {
|
|
2102
|
+
return this.ctx;
|
|
2103
|
+
}
|
|
2104
|
+
refreshEnabled() {
|
|
2105
|
+
this.interactions.refreshEnabled();
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2108
|
+
|
|
2109
|
+
/*
|
|
2110
|
+
* Public API Surface of lib
|
|
2111
|
+
*/
|
|
2112
|
+
|
|
2113
|
+
/**
|
|
2114
|
+
* Generated bundle index. Do not edit.
|
|
2115
|
+
*/
|
|
2116
|
+
|
|
2117
|
+
export { LayerManager, LibComponent, LibService };
|
|
2118
|
+
//# sourceMappingURL=aur-openlayers.mjs.map
|