@vcmap/core 6.0.7 → 6.1.0-rc.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/dist/cesium.d.ts +3 -0
- package/dist/index.d.ts +13 -1
- package/dist/index.js +13 -1
- package/dist/index.js.map +1 -1
- package/dist/ol.d.ts +3 -0
- package/dist/src/interaction/abstractInteraction.d.ts +4 -0
- package/dist/src/interaction/abstractInteraction.js.map +1 -1
- package/dist/src/interaction/featureAtPixelInteraction.d.ts +3 -0
- package/dist/src/interaction/featureAtPixelInteraction.js +98 -62
- package/dist/src/interaction/featureAtPixelInteraction.js.map +1 -1
- package/dist/src/interaction/featureProviderInteraction.js +16 -13
- package/dist/src/interaction/featureProviderInteraction.js.map +1 -1
- package/dist/src/layer/cesium/sourceVectorContextSync.d.ts +27 -0
- package/dist/src/layer/cesium/sourceVectorContextSync.js +94 -0
- package/dist/src/layer/cesium/sourceVectorContextSync.js.map +1 -0
- package/dist/src/layer/cesium/vectorCesiumImpl.d.ts +4 -27
- package/dist/src/layer/cesium/vectorCesiumImpl.js +15 -107
- package/dist/src/layer/cesium/vectorCesiumImpl.js.map +1 -1
- package/dist/src/layer/cesium/vectorContext.d.ts +12 -1
- package/dist/src/layer/cesium/vectorContext.js +6 -0
- package/dist/src/layer/cesium/vectorContext.js.map +1 -1
- package/dist/src/layer/layerSymbols.js +1 -1
- package/dist/src/layer/layerSymbols.js.map +1 -1
- package/dist/src/layer/oblique/sourceObliqueSync.d.ts +18 -0
- package/dist/src/layer/oblique/sourceObliqueSync.js +319 -0
- package/dist/src/layer/oblique/sourceObliqueSync.js.map +1 -0
- package/dist/src/layer/oblique/vectorObliqueImpl.d.ts +2 -40
- package/dist/src/layer/oblique/vectorObliqueImpl.js +8 -283
- package/dist/src/layer/oblique/vectorObliqueImpl.js.map +1 -1
- package/dist/src/layer/vectorLayer.d.ts +10 -1
- package/dist/src/layer/vectorLayer.js +23 -1
- package/dist/src/layer/vectorLayer.js.map +1 -1
- package/dist/src/map/baseOLMap.js +8 -1
- package/dist/src/map/baseOLMap.js.map +1 -1
- package/dist/src/map/cesiumMap.d.ts +2 -0
- package/dist/src/map/cesiumMap.js +26 -1
- package/dist/src/map/cesiumMap.js.map +1 -1
- package/dist/src/map/vcsMap.d.ts +16 -12
- package/dist/src/map/vcsMap.js +78 -38
- package/dist/src/map/vcsMap.js.map +1 -1
- package/dist/src/ol/source/ClusterEnhancedVectorSource.d.ts +6 -4
- package/dist/src/ol/source/ClusterEnhancedVectorSource.js +4 -9
- package/dist/src/ol/source/ClusterEnhancedVectorSource.js.map +1 -1
- package/dist/src/ol/source/VcsCluster.d.ts +10 -10
- package/dist/src/ol/source/VcsCluster.js +23 -7
- package/dist/src/ol/source/VcsCluster.js.map +1 -1
- package/dist/src/util/layerCollection.d.ts +11 -1
- package/dist/src/util/layerCollection.js +67 -12
- package/dist/src/util/layerCollection.js.map +1 -1
- package/dist/src/util/mapCollection.d.ts +16 -1
- package/dist/src/util/mapCollection.js +37 -3
- package/dist/src/util/mapCollection.js.map +1 -1
- package/dist/src/util/rotation.d.ts +30 -0
- package/dist/src/util/rotation.js +145 -0
- package/dist/src/util/rotation.js.map +1 -0
- package/dist/src/util/vcsTemplate.d.ts +7 -0
- package/dist/src/util/vcsTemplate.js +248 -0
- package/dist/src/util/vcsTemplate.js.map +1 -0
- package/dist/src/vcsApp.d.ts +4 -0
- package/dist/src/vcsApp.js +10 -0
- package/dist/src/vcsApp.js.map +1 -1
- package/dist/src/vcsModule.d.ts +4 -2
- package/dist/src/vcsModule.js.map +1 -1
- package/dist/src/vectorCluster/vectorClusterCesiumContext.d.ts +18 -0
- package/dist/src/{layer/cesium/clusterContext.js → vectorCluster/vectorClusterCesiumContext.js} +28 -42
- package/dist/src/vectorCluster/vectorClusterCesiumContext.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroup.d.ts +96 -0
- package/dist/src/vectorCluster/vectorClusterGroup.js +320 -0
- package/dist/src/vectorCluster/vectorClusterGroup.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.d.ts +20 -0
- package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js +115 -0
- package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroupCollection.d.ts +19 -0
- package/dist/src/vectorCluster/vectorClusterGroupCollection.js +37 -0
- package/dist/src/vectorCluster/vectorClusterGroupCollection.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroupImpl.d.ts +31 -0
- package/dist/src/vectorCluster/vectorClusterGroupImpl.js +76 -0
- package/dist/src/vectorCluster/vectorClusterGroupImpl.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.d.ts +17 -0
- package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js +62 -0
- package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.d.ts +17 -0
- package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js +62 -0
- package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterStyleItem.d.ts +110 -0
- package/dist/src/vectorCluster/vectorClusterStyleItem.js +374 -0
- package/dist/src/vectorCluster/vectorClusterStyleItem.js.map +1 -0
- package/dist/src/vectorCluster/vectorClusterSymbols.d.ts +1 -0
- package/dist/src/vectorCluster/vectorClusterSymbols.js +3 -0
- package/dist/src/vectorCluster/vectorClusterSymbols.js.map +1 -0
- package/index.ts +35 -1
- package/package.json +3 -1
- package/src/cesium/cesium.d.ts +3 -0
- package/src/interaction/abstractInteraction.ts +4 -0
- package/src/interaction/featureAtPixelInteraction.ts +158 -84
- package/src/interaction/featureProviderInteraction.ts +31 -28
- package/src/layer/cesium/sourceVectorContextSync.ts +134 -0
- package/src/layer/cesium/vcsTile/vcsDebugTile.ts +1 -1
- package/src/layer/cesium/vcsTile/vcsVectorTile.ts +1 -1
- package/src/layer/cesium/vectorCesiumImpl.ts +30 -144
- package/src/layer/cesium/vectorContext.ts +17 -1
- package/src/layer/layerSymbols.ts +1 -1
- package/src/layer/oblique/sourceObliqueSync.ts +436 -0
- package/src/layer/oblique/vectorObliqueImpl.ts +11 -397
- package/src/layer/vectorLayer.ts +35 -2
- package/src/map/baseOLMap.ts +8 -1
- package/src/map/cesiumMap.ts +36 -3
- package/src/map/vcsMap.ts +102 -47
- package/src/ol/ol.d.ts +3 -0
- package/src/ol/source/{ClusterEnhancedVectorSource.js → ClusterEnhancedVectorSource.ts} +7 -10
- package/src/ol/source/VcsCluster.ts +58 -0
- package/src/util/layerCollection.ts +90 -12
- package/src/util/mapCollection.ts +53 -2
- package/src/util/rotation.ts +215 -0
- package/src/util/vcsTemplate.ts +373 -0
- package/src/vcsApp.ts +29 -0
- package/src/vcsModule.ts +4 -2
- package/src/vectorCluster/vectorClusterCesiumContext.ts +123 -0
- package/src/vectorCluster/vectorClusterGroup.ts +463 -0
- package/src/vectorCluster/vectorClusterGroupCesiumImpl.ts +176 -0
- package/src/vectorCluster/vectorClusterGroupCollection.ts +43 -0
- package/src/vectorCluster/vectorClusterGroupImpl.ts +107 -0
- package/src/vectorCluster/vectorClusterGroupObliqueImpl.ts +84 -0
- package/src/vectorCluster/vectorClusterGroupOpenlayersImpl.ts +81 -0
- package/src/vectorCluster/vectorClusterStyleItem.ts +490 -0
- package/src/vectorCluster/vectorClusterSymbols.ts +2 -0
- package/dist/src/layer/cesium/clusterContext.d.ts +0 -20
- package/dist/src/layer/cesium/clusterContext.js.map +0 -1
- package/src/layer/cesium/clusterContext.ts +0 -140
- package/src/ol/source/VcsCluster.js +0 -37
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { check, maybe, oneOf } from '@vcsuite/check';
|
|
2
2
|
import { getLogger } from '@vcsuite/logger';
|
|
3
|
+
import { v4 as uuidv4 } from 'uuid';
|
|
3
4
|
import VcsEvent from '../vcsEvent.js';
|
|
4
5
|
import Collection from './collection.js';
|
|
5
6
|
import EventHandler from '../interaction/eventHandler.js';
|
|
@@ -129,6 +130,13 @@ class MapCollection extends Collection<VcsMap> {
|
|
|
129
130
|
// eslint-disable-next-line class-methods-use-this
|
|
130
131
|
private _exclusiveMapControlsRemoved: () => void = () => {};
|
|
131
132
|
|
|
133
|
+
private _exclusiveMapControlsChanged = new VcsEvent<{
|
|
134
|
+
options: DisableMapControlOptions;
|
|
135
|
+
id?: string | symbol;
|
|
136
|
+
}>();
|
|
137
|
+
|
|
138
|
+
private _exclusiveMapControlsId: string | symbol | undefined;
|
|
139
|
+
|
|
132
140
|
constructor() {
|
|
133
141
|
super();
|
|
134
142
|
|
|
@@ -202,6 +210,23 @@ class MapCollection extends Collection<VcsMap> {
|
|
|
202
210
|
return this._postRender;
|
|
203
211
|
}
|
|
204
212
|
|
|
213
|
+
/**
|
|
214
|
+
* Event raised when exclusive map controls are changed. If the Id is empty the default map controls have been reinstated
|
|
215
|
+
*/
|
|
216
|
+
get exclusiveMapControlsChanged(): VcsEvent<{
|
|
217
|
+
options: DisableMapControlOptions;
|
|
218
|
+
id?: string | symbol;
|
|
219
|
+
}> {
|
|
220
|
+
return this._exclusiveMapControlsChanged;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
/**
|
|
224
|
+
* The id of the current exclusive map controls owner.
|
|
225
|
+
*/
|
|
226
|
+
get exclusiveMapControlsId(): string | symbol | undefined {
|
|
227
|
+
return this._exclusiveMapControlsId;
|
|
228
|
+
}
|
|
229
|
+
|
|
205
230
|
/**
|
|
206
231
|
* Adds a map to the collection. This will set the collections target
|
|
207
232
|
* and the collections on the map.
|
|
@@ -399,30 +424,56 @@ class MapCollection extends Collection<VcsMap> {
|
|
|
399
424
|
* Manages the disabling of map navigation controls. By calling this function the map navigation controls passed in the options are disabled. The remove function passed by the previous caller is executed.
|
|
400
425
|
* @param options - which of the movement controls should be disabled.
|
|
401
426
|
* @param removed - the callback for when the interaction is forcefully removed.
|
|
427
|
+
* @param id - an optional id to identify the owner of the exclusive map controls.
|
|
402
428
|
* @returns function to reset map controls.
|
|
403
429
|
*/
|
|
404
430
|
requestExclusiveMapControls(
|
|
405
431
|
options: DisableMapControlOptions,
|
|
406
432
|
removed: () => void,
|
|
433
|
+
id?: string | symbol,
|
|
407
434
|
): () => void {
|
|
408
435
|
this._exclusiveMapControlsRemoved();
|
|
409
436
|
if (this._activeMap) {
|
|
410
437
|
this._activeMap.disableMovement(options);
|
|
411
438
|
}
|
|
412
439
|
|
|
413
|
-
|
|
440
|
+
if (options.pointerEvents || options.keyEvents || options.apiCalls) {
|
|
441
|
+
this._exclusiveMapControlsId = id || uuidv4();
|
|
442
|
+
this._exclusiveMapControlsRemoved = removed;
|
|
443
|
+
} else {
|
|
444
|
+
this._exclusiveMapControlsRemoved = (): void => {};
|
|
445
|
+
this._exclusiveMapControlsId = undefined;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
this._exclusiveMapControlsChanged.raiseEvent({
|
|
449
|
+
options,
|
|
450
|
+
id: this._exclusiveMapControlsId,
|
|
451
|
+
});
|
|
414
452
|
|
|
415
453
|
return () => {
|
|
416
454
|
// only reset if this function is called by the current exclusiveMapControls owner.
|
|
417
455
|
if (removed === this._exclusiveMapControlsRemoved) {
|
|
418
|
-
this._exclusiveMapControlsRemoved = (): void => {};
|
|
419
456
|
if (this._activeMap) {
|
|
420
457
|
this._activeMap.disableMovement(false);
|
|
421
458
|
}
|
|
459
|
+
|
|
460
|
+
this._exclusiveMapControlsRemoved = (): void => {};
|
|
461
|
+
this._exclusiveMapControlsId = undefined;
|
|
462
|
+
|
|
463
|
+
this._exclusiveMapControlsChanged.raiseEvent({
|
|
464
|
+
options: { apiCalls: false, keyEvents: false, pointerEvents: false },
|
|
465
|
+
});
|
|
422
466
|
}
|
|
423
467
|
};
|
|
424
468
|
}
|
|
425
469
|
|
|
470
|
+
resetExclusiveMapControls(): void {
|
|
471
|
+
this.requestExclusiveMapControls(
|
|
472
|
+
{ keyEvents: false, apiCalls: false, pointerEvents: false },
|
|
473
|
+
() => {},
|
|
474
|
+
);
|
|
475
|
+
}
|
|
476
|
+
|
|
426
477
|
destroy(): void {
|
|
427
478
|
super.destroy();
|
|
428
479
|
[...this._layerCollection].forEach((l) => {
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import {
|
|
2
|
+
Camera,
|
|
3
|
+
Cartesian2,
|
|
4
|
+
Cartesian3,
|
|
5
|
+
Cartographic,
|
|
6
|
+
Math as CesiumMath,
|
|
7
|
+
JulianDate,
|
|
8
|
+
HeadingPitchRollValues,
|
|
9
|
+
} from '@vcmap-cesium/engine';
|
|
10
|
+
import Viewpoint from './viewpoint.js';
|
|
11
|
+
import VcsApp from '../vcsApp.js';
|
|
12
|
+
import { CesiumMap } from '../../index.js';
|
|
13
|
+
import Projection from './projection.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Unique symbol for rotation.
|
|
17
|
+
* @type {symbol}
|
|
18
|
+
*/
|
|
19
|
+
export const rotationMapControlSymbol: unique symbol = Symbol(
|
|
20
|
+
'rotationMapControlSymbol',
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
export type SetViewOptions = {
|
|
24
|
+
destination: Cartesian3;
|
|
25
|
+
orientation: HeadingPitchRollValues;
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Rotates the camera to a specified viewpoint.
|
|
30
|
+
* @param {SetViewOptions} options - The options for setting the view.
|
|
31
|
+
* @param {number} [distance] - The distance to move backward.
|
|
32
|
+
* @param {CesiumMap} activeMap - The active map instance.
|
|
33
|
+
*/
|
|
34
|
+
function setUpdatedPosition(
|
|
35
|
+
options: SetViewOptions,
|
|
36
|
+
distance: number | undefined,
|
|
37
|
+
activeMap: CesiumMap,
|
|
38
|
+
): void {
|
|
39
|
+
let cameraPosition: Cartesian3;
|
|
40
|
+
|
|
41
|
+
const clonedCamera = new Camera(activeMap.getCesiumWidget()!.scene);
|
|
42
|
+
|
|
43
|
+
clonedCamera.setView(options);
|
|
44
|
+
clonedCamera.moveBackward(distance);
|
|
45
|
+
|
|
46
|
+
cameraPosition = clonedCamera.position;
|
|
47
|
+
|
|
48
|
+
const cam = activeMap.getCesiumWidget()!.scene.camera;
|
|
49
|
+
const cameraOptions = {
|
|
50
|
+
heading: options.orientation.heading,
|
|
51
|
+
pitch: options.orientation.pitch,
|
|
52
|
+
roll: options.orientation.roll,
|
|
53
|
+
};
|
|
54
|
+
cameraPosition = cameraPosition || null;
|
|
55
|
+
cam.cancelFlight();
|
|
56
|
+
|
|
57
|
+
cam.setView({
|
|
58
|
+
destination: cameraPosition,
|
|
59
|
+
orientation: cameraOptions,
|
|
60
|
+
});
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Rotates the center of the map.
|
|
65
|
+
* @param {number} heading - The current heading of the view.
|
|
66
|
+
* @param {CesiumMap} activeMap - The current Cesium Map.
|
|
67
|
+
* @param {number} timePerRotation - The current rotation speed.
|
|
68
|
+
* @param {JulianDate} [timeLastTick] - The time of the last tick.
|
|
69
|
+
* @returns {number | undefined} The new heading.
|
|
70
|
+
*/
|
|
71
|
+
export function calculateRotation(
|
|
72
|
+
heading: number,
|
|
73
|
+
activeMap: CesiumMap,
|
|
74
|
+
timePerRotation: number,
|
|
75
|
+
timeLastTick?: JulianDate,
|
|
76
|
+
): number {
|
|
77
|
+
let localHeading = heading;
|
|
78
|
+
|
|
79
|
+
const { clock } = activeMap.getCesiumWidget()!;
|
|
80
|
+
let timeDifference = timeLastTick
|
|
81
|
+
? clock.currentTime.secondsOfDay - timeLastTick.secondsOfDay
|
|
82
|
+
: 1 / 60;
|
|
83
|
+
if (timeDifference <= 0 || timeDifference > 1) {
|
|
84
|
+
timeDifference = 1 / 60;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const timeFactor = timeDifference / (1 / 60);
|
|
88
|
+
const headingDiff = CesiumMath.TWO_PI / ((timePerRotation * 60) / timeFactor);
|
|
89
|
+
localHeading += headingDiff;
|
|
90
|
+
localHeading = CesiumMath.zeroToTwoPi(localHeading);
|
|
91
|
+
return localHeading;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Starts the rotation of the map.
|
|
96
|
+
* @param app - The VCS application instance.
|
|
97
|
+
* @param [viewpoint] - The optional viewpoint to start the rotation from, if no viewpoint is provided the current Viewpoint is used.
|
|
98
|
+
* @param [timePerRotation=60] - The duration of a single full rotation in seconds.
|
|
99
|
+
* @returns A function to stop the rotation.
|
|
100
|
+
*/
|
|
101
|
+
export async function startRotation(
|
|
102
|
+
app: VcsApp,
|
|
103
|
+
viewpoint?: Viewpoint,
|
|
104
|
+
timePerRotation = 60,
|
|
105
|
+
): Promise<() => void> {
|
|
106
|
+
const { activeMap } = app.maps;
|
|
107
|
+
|
|
108
|
+
let localViewpoint: Viewpoint | undefined;
|
|
109
|
+
|
|
110
|
+
let rotationListener: (() => void) | undefined;
|
|
111
|
+
let resetMapControls: (() => void) | undefined;
|
|
112
|
+
|
|
113
|
+
const stopRotation: () => void = () => {
|
|
114
|
+
if (rotationListener) {
|
|
115
|
+
rotationListener();
|
|
116
|
+
rotationListener = undefined;
|
|
117
|
+
}
|
|
118
|
+
if (resetMapControls) {
|
|
119
|
+
resetMapControls();
|
|
120
|
+
}
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
if (activeMap instanceof CesiumMap) {
|
|
124
|
+
const scene = activeMap.getScene();
|
|
125
|
+
if (scene) {
|
|
126
|
+
if (!(viewpoint instanceof Viewpoint)) {
|
|
127
|
+
localViewpoint = activeMap.getViewpointSync() || undefined;
|
|
128
|
+
} else {
|
|
129
|
+
localViewpoint = viewpoint.clone();
|
|
130
|
+
}
|
|
131
|
+
if (localViewpoint) {
|
|
132
|
+
if (!localViewpoint.animate) {
|
|
133
|
+
localViewpoint.animate = true;
|
|
134
|
+
|
|
135
|
+
localViewpoint.duration = 0.00000001;
|
|
136
|
+
}
|
|
137
|
+
await activeMap.gotoViewpoint(localViewpoint);
|
|
138
|
+
|
|
139
|
+
const newCenter = scene.pickPosition(
|
|
140
|
+
new Cartesian2(scene.canvas.width / 2, scene.canvas.height / 2),
|
|
141
|
+
);
|
|
142
|
+
if (newCenter) {
|
|
143
|
+
const cartographic = Cartographic.fromCartesian(newCenter);
|
|
144
|
+
localViewpoint.groundPosition = [
|
|
145
|
+
CesiumMath.toDegrees(cartographic.longitude),
|
|
146
|
+
CesiumMath.toDegrees(cartographic.latitude),
|
|
147
|
+
cartographic.height,
|
|
148
|
+
];
|
|
149
|
+
localViewpoint.distance = Cartesian3.distance(
|
|
150
|
+
newCenter,
|
|
151
|
+
scene.camera.position,
|
|
152
|
+
);
|
|
153
|
+
} else {
|
|
154
|
+
throw new Error('new ground position could not be determined');
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
const { distance } = localViewpoint;
|
|
158
|
+
const heading = CesiumMath.toRadians(localViewpoint.heading);
|
|
159
|
+
const pitch = CesiumMath.toRadians(localViewpoint.pitch);
|
|
160
|
+
const roll = CesiumMath.toRadians(localViewpoint.roll);
|
|
161
|
+
|
|
162
|
+
const groundPositionCoords = localViewpoint.groundPosition;
|
|
163
|
+
if (!groundPositionCoords[2]) {
|
|
164
|
+
const positions = await activeMap.getHeightFromTerrain([
|
|
165
|
+
Projection.wgs84ToMercator(groundPositionCoords),
|
|
166
|
+
]);
|
|
167
|
+
groundPositionCoords[2] = positions[0][2];
|
|
168
|
+
}
|
|
169
|
+
const groundPosition = Cartesian3.fromDegrees(
|
|
170
|
+
groundPositionCoords[0],
|
|
171
|
+
groundPositionCoords[1],
|
|
172
|
+
groundPositionCoords[2],
|
|
173
|
+
);
|
|
174
|
+
|
|
175
|
+
const options = {
|
|
176
|
+
destination: groundPosition,
|
|
177
|
+
orientation: {
|
|
178
|
+
heading,
|
|
179
|
+
pitch,
|
|
180
|
+
roll,
|
|
181
|
+
},
|
|
182
|
+
};
|
|
183
|
+
|
|
184
|
+
let timeLastTick: JulianDate | undefined;
|
|
185
|
+
|
|
186
|
+
resetMapControls = app.maps.requestExclusiveMapControls(
|
|
187
|
+
{ apiCalls: true, keyEvents: true, pointerEvents: true },
|
|
188
|
+
stopRotation,
|
|
189
|
+
rotationMapControlSymbol,
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
rotationListener = activeMap
|
|
193
|
+
.getCesiumWidget()!
|
|
194
|
+
.scene.postRender.addEventListener(() => {
|
|
195
|
+
options.orientation.heading = calculateRotation(
|
|
196
|
+
options.orientation.heading,
|
|
197
|
+
activeMap,
|
|
198
|
+
timePerRotation,
|
|
199
|
+
timeLastTick,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
if (options) {
|
|
203
|
+
setUpdatedPosition(options, distance, activeMap);
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
timeLastTick = activeMap.getCesiumWidget()!.clock.currentTime;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
return (): void => {
|
|
213
|
+
stopRotation();
|
|
214
|
+
};
|
|
215
|
+
}
|
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
import {
|
|
2
|
+
BooleanType,
|
|
3
|
+
newParsingContext,
|
|
4
|
+
StringType,
|
|
5
|
+
NoneType,
|
|
6
|
+
LiteralValue,
|
|
7
|
+
} from 'ol/expr/expression.js';
|
|
8
|
+
import { buildExpression, newEvaluationContext } from 'ol/expr/cpu.js';
|
|
9
|
+
import { is } from '@vcsuite/check';
|
|
10
|
+
|
|
11
|
+
type Block = {
|
|
12
|
+
opening: RegExpExecArray;
|
|
13
|
+
closing: RegExpExecArray;
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
type ConditionalBlock = Block & {
|
|
17
|
+
elseStatement?: RegExpExecArray;
|
|
18
|
+
elseIfs: RegExpExecArray[];
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* @param {string} expressionString
|
|
23
|
+
* @param {Record<string, unknown>} data
|
|
24
|
+
* @param {number} evaluationType
|
|
25
|
+
* @returns {*}
|
|
26
|
+
*/
|
|
27
|
+
function evaluateExpression(
|
|
28
|
+
expressionString: string,
|
|
29
|
+
data: Record<string, unknown>,
|
|
30
|
+
evaluationType: number,
|
|
31
|
+
): LiteralValue {
|
|
32
|
+
const parsed = expressionString.startsWith('[')
|
|
33
|
+
? (JSON.parse(expressionString) as any[])
|
|
34
|
+
: [
|
|
35
|
+
'get',
|
|
36
|
+
...expressionString
|
|
37
|
+
.replace(/\[([^\]]+)]/g, '.$1')
|
|
38
|
+
.split('.')
|
|
39
|
+
.filter((f) => f),
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
const compiledExpression = buildExpression(
|
|
43
|
+
parsed,
|
|
44
|
+
evaluationType,
|
|
45
|
+
newParsingContext(),
|
|
46
|
+
);
|
|
47
|
+
const evaluationContext = newEvaluationContext();
|
|
48
|
+
evaluationContext.properties = data;
|
|
49
|
+
return compiledExpression(evaluationContext);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Replaces template strings by provided attributes, e.g. {{myAttribute}}
|
|
54
|
+
*/
|
|
55
|
+
function replaceAttributes(
|
|
56
|
+
template: string,
|
|
57
|
+
data: Record<string, unknown>,
|
|
58
|
+
): string {
|
|
59
|
+
const pattern = /\{\{([^}]+)}}/g;
|
|
60
|
+
return template.replace(pattern, (_p, value) => {
|
|
61
|
+
return (
|
|
62
|
+
(evaluateExpression(
|
|
63
|
+
(value as string).trim(),
|
|
64
|
+
data,
|
|
65
|
+
StringType,
|
|
66
|
+
) as string) ?? ''
|
|
67
|
+
);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function regexHits(regexp: RegExp, string: string): RegExpExecArray[] {
|
|
72
|
+
const hits = [];
|
|
73
|
+
let hit;
|
|
74
|
+
// eslint-disable-next-line no-cond-assign
|
|
75
|
+
while ((hit = regexp.exec(string))) {
|
|
76
|
+
hits.push(hit);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return hits;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function findTopLevelBlock(
|
|
83
|
+
openings: RegExpExecArray[],
|
|
84
|
+
closings: RegExpExecArray[],
|
|
85
|
+
accepted: (b: Block) => void,
|
|
86
|
+
rejected: (b: Block) => void,
|
|
87
|
+
): void {
|
|
88
|
+
const localOpenings = openings.slice();
|
|
89
|
+
const localClosings = closings.slice();
|
|
90
|
+
while (localOpenings.length > 0) {
|
|
91
|
+
let matchingClosing;
|
|
92
|
+
let matchingOpening;
|
|
93
|
+
while (!matchingClosing && localClosings.length > 0) {
|
|
94
|
+
const currentClosing = localClosings.shift()!;
|
|
95
|
+
const openingDistances = localOpenings.map(
|
|
96
|
+
(o) => currentClosing.index - o.index,
|
|
97
|
+
);
|
|
98
|
+
const minDistance = openingDistances.reduce((min, currentDistance) => {
|
|
99
|
+
if (currentDistance > 0 && currentDistance < min) {
|
|
100
|
+
return currentDistance;
|
|
101
|
+
}
|
|
102
|
+
return min;
|
|
103
|
+
}, Infinity);
|
|
104
|
+
const matchingOpeningIndex = openingDistances.indexOf(minDistance);
|
|
105
|
+
matchingOpening = localOpenings[matchingOpeningIndex];
|
|
106
|
+
|
|
107
|
+
if (matchingOpeningIndex === 0) {
|
|
108
|
+
matchingClosing = currentClosing;
|
|
109
|
+
} else {
|
|
110
|
+
rejected({ opening: matchingOpening, closing: currentClosing });
|
|
111
|
+
}
|
|
112
|
+
localOpenings.splice(matchingOpeningIndex, 1);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (matchingOpening && matchingClosing) {
|
|
116
|
+
accepted({ opening: matchingOpening, closing: matchingClosing });
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
function tagWithinBlock(tag: RegExpExecArray, block: Block): boolean {
|
|
122
|
+
return tag.index > block.opening.index && tag.index < block.closing.index;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function getForEachBlocks(template: string): Block[] {
|
|
126
|
+
const forEachBlocks = [] as Block[];
|
|
127
|
+
const forEachOpenings = regexHits(
|
|
128
|
+
/\s*{{#each\s\(([^.)]+)\)\sin\s([^}]+)}}\s*/g,
|
|
129
|
+
template,
|
|
130
|
+
);
|
|
131
|
+
const forEachClosings = regexHits(/\s*{{\/each}}\s*/g, template);
|
|
132
|
+
|
|
133
|
+
if (forEachClosings.length > forEachOpenings.length) {
|
|
134
|
+
throw new Error(
|
|
135
|
+
'Template failed to render, missing opening tag for each statements',
|
|
136
|
+
);
|
|
137
|
+
} else if (forEachClosings.length < forEachOpenings.length) {
|
|
138
|
+
throw new Error(
|
|
139
|
+
'Template failed to render, missing closing tag for each statements',
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
findTopLevelBlock(
|
|
144
|
+
forEachOpenings,
|
|
145
|
+
forEachClosings,
|
|
146
|
+
(block) => {
|
|
147
|
+
forEachBlocks.push(block);
|
|
148
|
+
},
|
|
149
|
+
() => {},
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
return forEachBlocks;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function getConditionalBlocks(
|
|
156
|
+
template: string,
|
|
157
|
+
forEachBlocks: Block[],
|
|
158
|
+
): ConditionalBlock[] {
|
|
159
|
+
const conditionalBlocks = [] as ConditionalBlock[];
|
|
160
|
+
let conditionalOpenings = regexHits(/\s*{{#if\s([^}]*)}}\s*/g, template);
|
|
161
|
+
let conditionalClosings = regexHits(/\s*{{\/if}}\s*/g, template);
|
|
162
|
+
let elseIfs = regexHits(/\s*{{elseif\s([^}]*)}}\s*/g, template);
|
|
163
|
+
let elses = regexHits(/\s*{{else}}\s*/g, template);
|
|
164
|
+
|
|
165
|
+
const withinForEachBlock = (tag: RegExpExecArray): Block | undefined =>
|
|
166
|
+
forEachBlocks.find((block) => tagWithinBlock(tag, block));
|
|
167
|
+
|
|
168
|
+
// conditionals within a for each blocks are rendered with the for each block, ignore
|
|
169
|
+
conditionalOpenings = conditionalOpenings.filter(
|
|
170
|
+
(t) => !withinForEachBlock(t),
|
|
171
|
+
);
|
|
172
|
+
conditionalClosings = conditionalClosings.filter(
|
|
173
|
+
(t) => !withinForEachBlock(t),
|
|
174
|
+
);
|
|
175
|
+
|
|
176
|
+
if (conditionalClosings.length > conditionalOpenings.length) {
|
|
177
|
+
throw new Error(
|
|
178
|
+
'Template failed to render, missing closing tag for if statements',
|
|
179
|
+
);
|
|
180
|
+
} else if (conditionalClosings.length < conditionalOpenings.length) {
|
|
181
|
+
throw new Error(
|
|
182
|
+
'Template failed to render, missing opening tag for if statements',
|
|
183
|
+
);
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const filterElseIfElse = (block: Block): void => {
|
|
187
|
+
elseIfs = elseIfs.filter((tag) => !tagWithinBlock(tag, block));
|
|
188
|
+
elses = elses.filter((tag) => !tagWithinBlock(tag, block));
|
|
189
|
+
};
|
|
190
|
+
|
|
191
|
+
findTopLevelBlock(
|
|
192
|
+
conditionalOpenings,
|
|
193
|
+
conditionalClosings,
|
|
194
|
+
(block) => {
|
|
195
|
+
const blockElseIfs = elseIfs.filter((tag) => tagWithinBlock(tag, block));
|
|
196
|
+
const elseStatement = elses.find((tag) => tagWithinBlock(tag, block));
|
|
197
|
+
if (
|
|
198
|
+
elseStatement &&
|
|
199
|
+
blockElseIfs.length > 0 &&
|
|
200
|
+
elseStatement.index < blockElseIfs.at(-1)!.index
|
|
201
|
+
) {
|
|
202
|
+
throw new Error('{{else}} must be the last entry in a block');
|
|
203
|
+
}
|
|
204
|
+
conditionalBlocks.push({
|
|
205
|
+
...block,
|
|
206
|
+
elseStatement,
|
|
207
|
+
elseIfs: blockElseIfs,
|
|
208
|
+
});
|
|
209
|
+
},
|
|
210
|
+
(block) => {
|
|
211
|
+
filterElseIfElse(block);
|
|
212
|
+
},
|
|
213
|
+
);
|
|
214
|
+
|
|
215
|
+
return conditionalBlocks;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function shouldRemoveWhiteSpace(openingTag: string): boolean {
|
|
219
|
+
return /\n\s*\{/.test(openingTag) && /}\s*\n/.test(openingTag);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* This will extract the block to render separately. This will depend on the white space handling. If the
|
|
224
|
+
* opening is placed on its own line, whitespace after the opening and before the closing blocks will be removed
|
|
225
|
+
* from the sub template, up to the first new line feed.
|
|
226
|
+
*/
|
|
227
|
+
function getSubTemplateForBlock(template: string, block: Block): string {
|
|
228
|
+
const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
|
|
229
|
+
let startIndex = block.opening.index + block.opening[0].indexOf('}') + 2;
|
|
230
|
+
let endIndex = block.closing.index + block.closing[0].indexOf('{');
|
|
231
|
+
if (removeWhiteSpace) {
|
|
232
|
+
startIndex += (/}\s*\n/.exec(block.opening[0])?.[0].length ?? 1) - 1;
|
|
233
|
+
endIndex -= (/\n\s*\{/.exec(block.closing[0])?.[0].length ?? 2) - 2;
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
return template.substring(startIndex, endIndex);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* This will replace a block with a previously extracted blocks rendered template.
|
|
241
|
+
* This will depend on the white space handling. If the opening is placed on its own line,
|
|
242
|
+
* whitespace before the opening and after the closing blocks will be removed up to the first new line feed,
|
|
243
|
+
* from the new template string all together.
|
|
244
|
+
*/
|
|
245
|
+
function replaceBlock(
|
|
246
|
+
template: string,
|
|
247
|
+
block: Block,
|
|
248
|
+
replacement: string,
|
|
249
|
+
): string {
|
|
250
|
+
const removeWhiteSpace = shouldRemoveWhiteSpace(block.opening[0]);
|
|
251
|
+
let startIndex = block.opening.index + block.opening[0].indexOf('{');
|
|
252
|
+
let endIndex = block.closing.index + block.closing[0].indexOf('}') + 2;
|
|
253
|
+
if (removeWhiteSpace) {
|
|
254
|
+
startIndex -= (/\n\s*\{/.exec(block.opening[0])?.[0].length ?? 2) - 2;
|
|
255
|
+
endIndex += (/}\s*\n/.exec(block.closing[0])?.[0].length ?? 1) - 1;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
return `${template.substring(
|
|
259
|
+
0,
|
|
260
|
+
startIndex,
|
|
261
|
+
)}${replacement}${template.substring(endIndex)}`;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Replaces {{#if }} blocks
|
|
266
|
+
*/
|
|
267
|
+
function expandConditionalsAndLoops(
|
|
268
|
+
template: string,
|
|
269
|
+
data: Record<string, unknown>,
|
|
270
|
+
): string {
|
|
271
|
+
let renderedTemplate = template;
|
|
272
|
+
const forEachBlocks = getForEachBlocks(template);
|
|
273
|
+
|
|
274
|
+
getConditionalBlocks(template, forEachBlocks)
|
|
275
|
+
.reverse()
|
|
276
|
+
.forEach(
|
|
277
|
+
/** @param {ConditionalBlock} block */ (block) => {
|
|
278
|
+
const partialBlocks = [block.opening];
|
|
279
|
+
if (block.elseIfs) {
|
|
280
|
+
partialBlocks.push(...block.elseIfs);
|
|
281
|
+
}
|
|
282
|
+
let trueStatementIndex = partialBlocks.findIndex((s) =>
|
|
283
|
+
evaluateExpression(s[1].trim(), data, BooleanType),
|
|
284
|
+
);
|
|
285
|
+
if (trueStatementIndex === -1 && block.elseStatement) {
|
|
286
|
+
trueStatementIndex = partialBlocks.length;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
let renderedBlock = '';
|
|
290
|
+
if (trueStatementIndex > -1) {
|
|
291
|
+
if (block.elseStatement) {
|
|
292
|
+
partialBlocks.push(block.elseStatement);
|
|
293
|
+
}
|
|
294
|
+
partialBlocks.push(block.closing);
|
|
295
|
+
|
|
296
|
+
const blockTemplate = getSubTemplateForBlock(template, {
|
|
297
|
+
opening: partialBlocks[trueStatementIndex],
|
|
298
|
+
closing: partialBlocks[trueStatementIndex + 1],
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
renderedBlock = expandConditionalsAndLoops(blockTemplate, data);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
renderedTemplate = replaceBlock(renderedTemplate, block, renderedBlock);
|
|
305
|
+
},
|
|
306
|
+
);
|
|
307
|
+
|
|
308
|
+
// only iterate over blocks not removed by conditionals
|
|
309
|
+
getForEachBlocks(renderedTemplate)
|
|
310
|
+
.reverse()
|
|
311
|
+
.forEach((block) => {
|
|
312
|
+
const obj = evaluateExpression(block.opening[2].trim(), data, NoneType);
|
|
313
|
+
let keyValuePairs;
|
|
314
|
+
if (is(obj, Object)) {
|
|
315
|
+
keyValuePairs = Object.entries(obj);
|
|
316
|
+
} else if (Array.isArray(obj)) {
|
|
317
|
+
keyValuePairs = obj.entries();
|
|
318
|
+
}
|
|
319
|
+
const renderedBlocks = [];
|
|
320
|
+
if (keyValuePairs) {
|
|
321
|
+
let index = 0;
|
|
322
|
+
const [valueName, keyName, indexName] = block.opening[1]
|
|
323
|
+
.split(',')
|
|
324
|
+
.map((e) => e.trim())
|
|
325
|
+
.slice(0, 3);
|
|
326
|
+
|
|
327
|
+
const blockTemplate = getSubTemplateForBlock(renderedTemplate, block);
|
|
328
|
+
// eslint-disable-next-line no-restricted-syntax
|
|
329
|
+
for (const args of keyValuePairs) {
|
|
330
|
+
const forEachData = structuredClone(data);
|
|
331
|
+
forEachData[valueName] = args[1];
|
|
332
|
+
if (keyName) {
|
|
333
|
+
forEachData[keyName] = args[0];
|
|
334
|
+
}
|
|
335
|
+
if (indexName) {
|
|
336
|
+
forEachData[indexName] = index;
|
|
337
|
+
}
|
|
338
|
+
const currentBlock = expandConditionalsAndLoops(
|
|
339
|
+
blockTemplate,
|
|
340
|
+
forEachData,
|
|
341
|
+
);
|
|
342
|
+
renderedBlocks.push(replaceAttributes(currentBlock, forEachData));
|
|
343
|
+
index += 1;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
renderedTemplate = replaceBlock(
|
|
348
|
+
renderedTemplate,
|
|
349
|
+
block,
|
|
350
|
+
renderedBlocks.join(''),
|
|
351
|
+
);
|
|
352
|
+
});
|
|
353
|
+
|
|
354
|
+
return renderedTemplate;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
/**
|
|
358
|
+
* Renders a template in these steps. See {@link documentation/vcsTemplate.md} for more information.
|
|
359
|
+
* 1. expand conditional blocks. this will remove any blocks that do not match their expressions and choose from if / elseif / else block which of them to render
|
|
360
|
+
* 2. expand iterations. this will create new templates for each iteration and re-run the rendering for those blocks
|
|
361
|
+
* 3. render attributes. this will add the attributes to all the blocks not within each blocks
|
|
362
|
+
*/
|
|
363
|
+
// eslint-disable-next-line import/prefer-default-export
|
|
364
|
+
export function renderTemplate(
|
|
365
|
+
template: string | string[],
|
|
366
|
+
data: Record<string, unknown>,
|
|
367
|
+
): string {
|
|
368
|
+
const templateString = Array.isArray(template)
|
|
369
|
+
? template.join('\n')
|
|
370
|
+
: template;
|
|
371
|
+
const conditionalTemplate = expandConditionalsAndLoops(templateString, data);
|
|
372
|
+
return replaceAttributes(conditionalTemplate, data);
|
|
373
|
+
}
|