@vcmap/core 6.0.7 → 6.1.0-rc.2

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.
Files changed (146) hide show
  1. package/dist/cesium.d.ts +3 -0
  2. package/dist/index.d.ts +16 -1
  3. package/dist/index.js +16 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/ol.d.ts +8 -1
  6. package/dist/src/featureProvider/featureProviderSymbols.d.ts +5 -0
  7. package/dist/src/featureProvider/featureProviderSymbols.js +5 -1
  8. package/dist/src/featureProvider/featureProviderSymbols.js.map +1 -1
  9. package/dist/src/interaction/featureAtPixelInteraction.js +58 -62
  10. package/dist/src/interaction/featureAtPixelInteraction.js.map +1 -1
  11. package/dist/src/interaction/featureProviderInteraction.js +25 -13
  12. package/dist/src/interaction/featureProviderInteraction.js.map +1 -1
  13. package/dist/src/layer/cesium/sourceVectorContextSync.d.ts +27 -0
  14. package/dist/src/layer/cesium/sourceVectorContextSync.js +94 -0
  15. package/dist/src/layer/cesium/sourceVectorContextSync.js.map +1 -0
  16. package/dist/src/layer/cesium/vectorCesiumImpl.d.ts +4 -27
  17. package/dist/src/layer/cesium/vectorCesiumImpl.js +15 -107
  18. package/dist/src/layer/cesium/vectorCesiumImpl.js.map +1 -1
  19. package/dist/src/layer/cesium/vectorContext.d.ts +12 -1
  20. package/dist/src/layer/cesium/vectorContext.js +6 -0
  21. package/dist/src/layer/cesium/vectorContext.js.map +1 -1
  22. package/dist/src/layer/layerSymbols.js +1 -1
  23. package/dist/src/layer/layerSymbols.js.map +1 -1
  24. package/dist/src/layer/oblique/sourceObliqueSync.d.ts +18 -0
  25. package/dist/src/layer/oblique/sourceObliqueSync.js +319 -0
  26. package/dist/src/layer/oblique/sourceObliqueSync.js.map +1 -0
  27. package/dist/src/layer/oblique/vectorObliqueImpl.d.ts +2 -40
  28. package/dist/src/layer/oblique/vectorObliqueImpl.js +8 -283
  29. package/dist/src/layer/oblique/vectorObliqueImpl.js.map +1 -1
  30. package/dist/src/layer/vectorLayer.d.ts +10 -1
  31. package/dist/src/layer/vectorLayer.js +23 -1
  32. package/dist/src/layer/vectorLayer.js.map +1 -1
  33. package/dist/src/map/baseOLMap.js +8 -1
  34. package/dist/src/map/baseOLMap.js.map +1 -1
  35. package/dist/src/map/cesiumMap.d.ts +2 -0
  36. package/dist/src/map/cesiumMap.js +26 -1
  37. package/dist/src/map/cesiumMap.js.map +1 -1
  38. package/dist/src/map/vcsMap.d.ts +24 -12
  39. package/dist/src/map/vcsMap.js +92 -38
  40. package/dist/src/map/vcsMap.js.map +1 -1
  41. package/dist/src/ol/source/ClusterEnhancedVectorSource.d.ts +6 -4
  42. package/dist/src/ol/source/ClusterEnhancedVectorSource.js +4 -9
  43. package/dist/src/ol/source/ClusterEnhancedVectorSource.js.map +1 -1
  44. package/dist/src/ol/source/VcsCluster.d.ts +10 -10
  45. package/dist/src/ol/source/VcsCluster.js +23 -7
  46. package/dist/src/ol/source/VcsCluster.js.map +1 -1
  47. package/dist/src/util/clipping/clippingPolygonHelper.d.ts +7 -0
  48. package/dist/src/util/clipping/clippingPolygonHelper.js +53 -0
  49. package/dist/src/util/clipping/clippingPolygonHelper.js.map +1 -0
  50. package/dist/src/util/clipping/clippingPolygonObject.d.ts +59 -0
  51. package/dist/src/util/clipping/clippingPolygonObject.js +158 -0
  52. package/dist/src/util/clipping/clippingPolygonObject.js.map +1 -0
  53. package/dist/src/util/clipping/clippingPolygonObjectCollection.d.ts +18 -0
  54. package/dist/src/util/clipping/clippingPolygonObjectCollection.js +167 -0
  55. package/dist/src/util/clipping/clippingPolygonObjectCollection.js.map +1 -0
  56. package/dist/src/util/layerCollection.d.ts +11 -1
  57. package/dist/src/util/layerCollection.js +67 -12
  58. package/dist/src/util/layerCollection.js.map +1 -1
  59. package/dist/src/util/mapCollection.d.ts +16 -1
  60. package/dist/src/util/mapCollection.js +37 -3
  61. package/dist/src/util/mapCollection.js.map +1 -1
  62. package/dist/src/util/renderScreenshot.d.ts +9 -0
  63. package/dist/src/util/renderScreenshot.js +162 -0
  64. package/dist/src/util/renderScreenshot.js.map +1 -0
  65. package/dist/src/util/rotation.d.ts +30 -0
  66. package/dist/src/util/rotation.js +145 -0
  67. package/dist/src/util/rotation.js.map +1 -0
  68. package/dist/src/util/vcsTemplate.d.ts +7 -0
  69. package/dist/src/util/vcsTemplate.js +248 -0
  70. package/dist/src/util/vcsTemplate.js.map +1 -0
  71. package/dist/src/vcsApp.d.ts +7 -0
  72. package/dist/src/vcsApp.js +29 -0
  73. package/dist/src/vcsApp.js.map +1 -1
  74. package/dist/src/vcsModule.d.ts +6 -2
  75. package/dist/src/vcsModule.js.map +1 -1
  76. package/dist/src/vectorCluster/vectorClusterCesiumContext.d.ts +18 -0
  77. package/dist/src/{layer/cesium/clusterContext.js → vectorCluster/vectorClusterCesiumContext.js} +28 -42
  78. package/dist/src/vectorCluster/vectorClusterCesiumContext.js.map +1 -0
  79. package/dist/src/vectorCluster/vectorClusterGroup.d.ts +96 -0
  80. package/dist/src/vectorCluster/vectorClusterGroup.js +320 -0
  81. package/dist/src/vectorCluster/vectorClusterGroup.js.map +1 -0
  82. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.d.ts +20 -0
  83. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js +115 -0
  84. package/dist/src/vectorCluster/vectorClusterGroupCesiumImpl.js.map +1 -0
  85. package/dist/src/vectorCluster/vectorClusterGroupCollection.d.ts +19 -0
  86. package/dist/src/vectorCluster/vectorClusterGroupCollection.js +37 -0
  87. package/dist/src/vectorCluster/vectorClusterGroupCollection.js.map +1 -0
  88. package/dist/src/vectorCluster/vectorClusterGroupImpl.d.ts +31 -0
  89. package/dist/src/vectorCluster/vectorClusterGroupImpl.js +76 -0
  90. package/dist/src/vectorCluster/vectorClusterGroupImpl.js.map +1 -0
  91. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.d.ts +17 -0
  92. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js +62 -0
  93. package/dist/src/vectorCluster/vectorClusterGroupObliqueImpl.js.map +1 -0
  94. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.d.ts +17 -0
  95. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js +62 -0
  96. package/dist/src/vectorCluster/vectorClusterGroupOpenlayersImpl.js.map +1 -0
  97. package/dist/src/vectorCluster/vectorClusterStyleItem.d.ts +110 -0
  98. package/dist/src/vectorCluster/vectorClusterStyleItem.js +374 -0
  99. package/dist/src/vectorCluster/vectorClusterStyleItem.js.map +1 -0
  100. package/dist/src/vectorCluster/vectorClusterSymbols.d.ts +1 -0
  101. package/dist/src/vectorCluster/vectorClusterSymbols.js +3 -0
  102. package/dist/src/vectorCluster/vectorClusterSymbols.js.map +1 -0
  103. package/index.ts +42 -1
  104. package/package.json +3 -1
  105. package/src/cesium/cesium.d.ts +3 -0
  106. package/src/featureProvider/featureProviderSymbols.ts +6 -1
  107. package/src/interaction/featureAtPixelInteraction.ts +109 -84
  108. package/src/interaction/featureProviderInteraction.ts +42 -28
  109. package/src/layer/cesium/sourceVectorContextSync.ts +134 -0
  110. package/src/layer/cesium/vcsTile/vcsDebugTile.ts +1 -1
  111. package/src/layer/cesium/vcsTile/vcsVectorTile.ts +1 -1
  112. package/src/layer/cesium/vectorCesiumImpl.ts +30 -144
  113. package/src/layer/cesium/vectorContext.ts +17 -1
  114. package/src/layer/layerSymbols.ts +1 -1
  115. package/src/layer/oblique/sourceObliqueSync.ts +436 -0
  116. package/src/layer/oblique/vectorObliqueImpl.ts +11 -397
  117. package/src/layer/vectorLayer.ts +35 -2
  118. package/src/map/baseOLMap.ts +8 -1
  119. package/src/map/cesiumMap.ts +36 -3
  120. package/src/map/vcsMap.ts +121 -47
  121. package/src/ol/ol.d.ts +8 -1
  122. package/src/ol/source/{ClusterEnhancedVectorSource.js → ClusterEnhancedVectorSource.ts} +7 -10
  123. package/src/ol/source/VcsCluster.ts +58 -0
  124. package/src/util/clipping/clippingPolygonHelper.ts +86 -0
  125. package/src/util/clipping/clippingPolygonObject.ts +223 -0
  126. package/src/util/clipping/clippingPolygonObjectCollection.ts +249 -0
  127. package/src/util/layerCollection.ts +90 -12
  128. package/src/util/mapCollection.ts +53 -2
  129. package/src/util/renderScreenshot.ts +193 -0
  130. package/src/util/rotation.ts +215 -0
  131. package/src/util/vcsTemplate.ts +373 -0
  132. package/src/vcsApp.ts +65 -0
  133. package/src/vcsModule.ts +6 -2
  134. package/src/vectorCluster/vectorClusterCesiumContext.ts +123 -0
  135. package/src/vectorCluster/vectorClusterGroup.ts +463 -0
  136. package/src/vectorCluster/vectorClusterGroupCesiumImpl.ts +176 -0
  137. package/src/vectorCluster/vectorClusterGroupCollection.ts +43 -0
  138. package/src/vectorCluster/vectorClusterGroupImpl.ts +107 -0
  139. package/src/vectorCluster/vectorClusterGroupObliqueImpl.ts +84 -0
  140. package/src/vectorCluster/vectorClusterGroupOpenlayersImpl.ts +81 -0
  141. package/src/vectorCluster/vectorClusterStyleItem.ts +490 -0
  142. package/src/vectorCluster/vectorClusterSymbols.ts +2 -0
  143. package/dist/src/layer/cesium/clusterContext.d.ts +0 -20
  144. package/dist/src/layer/cesium/clusterContext.js.map +0 -1
  145. package/src/layer/cesium/clusterContext.ts +0 -140
  146. 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
- this._exclusiveMapControlsRemoved = removed;
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,193 @@
1
+ import { Size } from 'ol/size.js';
2
+ import { getLogger } from '@vcsuite/logger';
3
+ import CesiumMap from '../map/cesiumMap.js';
4
+ import OpenlayersMap from '../map/openlayersMap.js';
5
+ import ObliqueMap from '../map/obliqueMap.js';
6
+ import VcsApp from '../vcsApp.js';
7
+ /**
8
+ * Prepares the cesium map for the screenshot
9
+ * @param map - The cesium map
10
+ * @param scale - The factor to scale the map according to the required resolution
11
+ * @returns The function to reset the applied scale.
12
+ */
13
+ function prepareCesiumMap(map: CesiumMap, scale: number): () => void {
14
+ const viewer = map.getCesiumWidget()!;
15
+ const { resolutionScale } = viewer;
16
+ viewer.resolutionScale = scale;
17
+
18
+ return function resetCesiumMap() {
19
+ viewer.resolutionScale = resolutionScale;
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Returns a function to reset the OLMap to the original state.
25
+ * @param map - The map instance.
26
+ * @param renderSize - The size to set for rendering.
27
+ * @returns The function to reset the map.
28
+ */
29
+ function prepareOlMap(
30
+ map: OpenlayersMap | ObliqueMap,
31
+ renderSize: Size,
32
+ ): () => void {
33
+ const { olMap } = map;
34
+ if (olMap) {
35
+ const olSize = olMap.getSize();
36
+ const extent = olMap.getView().calculateExtent(olSize);
37
+ const originalMinZoom = olMap.getView().getMinZoom();
38
+ const originalMaxZoom = olMap.getView().getMaxZoom();
39
+ olMap.setSize(renderSize);
40
+ olMap.getView().setMinZoom(0);
41
+ olMap.getView().setMaxZoom(28);
42
+ olMap.getView().fit(extent, { size: renderSize });
43
+ olMap.set('vcs_scale', renderSize, true);
44
+
45
+ return function resetOlMap() {
46
+ olMap.setSize(olSize);
47
+ olMap.getView().setMinZoom(originalMinZoom);
48
+ olMap.getView().setMaxZoom(originalMaxZoom);
49
+ olMap.getView().fit(extent, { size: olSize });
50
+ olMap.unset('vcs_scale', true);
51
+ olMap.renderSync();
52
+ };
53
+ }
54
+ return () => {};
55
+ }
56
+
57
+ /**
58
+ * Copies Cesium content on the given canvas.
59
+ * @param map - The Cesium map instance.
60
+ * @returns A promise that resolves to the canvas element.
61
+ */
62
+ async function getImageFromCesium(map: CesiumMap): Promise<HTMLCanvasElement> {
63
+ const { scene } = map.getCesiumWidget()!;
64
+
65
+ return new Promise((resolve) => {
66
+ const removePreListener = scene.preUpdate.addEventListener(() => {
67
+ const { canvas } = scene;
68
+ const removePostListener = scene.postRender.addEventListener(() => {
69
+ resolve(canvas);
70
+ removePostListener();
71
+ });
72
+ removePreListener();
73
+ });
74
+ });
75
+ }
76
+
77
+ /**
78
+ * Copies Openlayers content on the given canvas
79
+ * @param map - The map instance.
80
+ * @param canvasSize - The size of the canvas.
81
+ * @returns A promise that resolves to the canvas element.
82
+ */
83
+ async function getImageFromOpenlayers(
84
+ map: OpenlayersMap | ObliqueMap,
85
+ canvasSize: Size,
86
+ ): Promise<HTMLCanvasElement> {
87
+ const canvas = document.createElement('canvas');
88
+ canvas.width = canvasSize[0];
89
+ canvas.height = canvasSize[1];
90
+ const { olMap } = map;
91
+ const canvasContext = canvas.getContext('2d')!;
92
+ // fill canvas with white so transparent pixels are not printed as black when exporting as jpeg.
93
+ canvasContext.fillStyle = 'white';
94
+ canvasContext.fillRect(0, 0, canvas.width, canvas.height);
95
+ await new Promise<void>((resolve) => {
96
+ if (olMap) {
97
+ olMap.once('rendercomplete', () => {
98
+ const olLayerCanvasList = Array.from(
99
+ olMap
100
+ .getViewport()
101
+ .querySelectorAll<HTMLCanvasElement>('.ol-layer canvas'),
102
+ );
103
+ olLayerCanvasList.forEach((layerCanvas) => {
104
+ if (layerCanvas.width > 0) {
105
+ const opacity =
106
+ (layerCanvas.parentNode instanceof HTMLElement
107
+ ? layerCanvas.parentNode.style.opacity
108
+ : '') || layerCanvas.style.opacity;
109
+ canvasContext.globalAlpha = opacity === '' ? 1 : Number(opacity);
110
+ const { transform } = layerCanvas.style;
111
+ const matrix = transform
112
+ .match(/^matrix\(([^(]*)\)$/)?.[1]
113
+ .split(',')
114
+ .map(Number) as
115
+ | [number, number, number, number, number, number]
116
+ | undefined;
117
+ if (matrix) {
118
+ canvasContext.setTransform(...matrix);
119
+ }
120
+ canvasContext.drawImage(layerCanvas, 0, 0);
121
+ }
122
+ });
123
+ resolve();
124
+ });
125
+ }
126
+ });
127
+ return canvas;
128
+ }
129
+
130
+ /**
131
+ * Renders a screenshot of the active map in the VcsApp.
132
+ *
133
+ * @param app - The VcsApp instance containing the active map.
134
+ * @param width - The desired width of the screenshot.
135
+ * @returns A promise that resolves to the canvas element containing the screenshot.
136
+ */
137
+ export default async function renderScreenshot(
138
+ app: VcsApp,
139
+ width: number,
140
+ ): Promise<HTMLCanvasElement> {
141
+ let screenshotCanvas: HTMLCanvasElement;
142
+
143
+ function calcRenderSize(mapSize: Size, screenshotWidth: number): Size {
144
+ const aspectRatio = mapSize[0] / mapSize[1];
145
+ return [screenshotWidth, screenshotWidth / aspectRatio];
146
+ }
147
+
148
+ function checkChromeMaxPixel(renderSize: Size, threshold: number): void {
149
+ const totalPixelCount = renderSize[0] * renderSize[1];
150
+ if (totalPixelCount > threshold && 'chrome' in window) {
151
+ getLogger('@vcmap/print').warning(
152
+ `The created image might have black bars at some of the edges. This is due to a behavior of chromium based browsers
153
+ that occurs when the total pixel count of the cesium map exceeds a threshold of thirty-three million pixels.
154
+ In order to avoid this either reduce the resolution or switch to Mozilla Firefox browser.`,
155
+ );
156
+ }
157
+ }
158
+ /**
159
+ * Function for resetting map after screenshot processes finished.
160
+ */
161
+ let resetMap: () => void;
162
+ const map = app.maps.activeMap;
163
+
164
+ if (map instanceof CesiumMap) {
165
+ const { canvas } = map.getCesiumWidget()!.scene;
166
+ const canvasWidth = canvas.width;
167
+ const canvasHeight = canvas.height;
168
+
169
+ // check if render size is above chromium threshold
170
+ checkChromeMaxPixel(
171
+ calcRenderSize([canvasWidth, canvasHeight], width),
172
+ 33000000,
173
+ );
174
+ const scale = width / canvasWidth;
175
+ resetMap = prepareCesiumMap(map, scale);
176
+ screenshotCanvas = await getImageFromCesium(map);
177
+ } else if (map instanceof OpenlayersMap || map instanceof ObliqueMap) {
178
+ const biggestCanvas = Array.from(
179
+ map
180
+ .olMap!.getViewport()
181
+ .querySelectorAll<HTMLCanvasElement>('.ol-layer canvas'),
182
+ ).reduce((acc, val) => (acc.width > val.width ? val : acc));
183
+ const canvasWidth = biggestCanvas.width;
184
+ const canvasHeight = biggestCanvas.height;
185
+ const renderSize = calcRenderSize([canvasWidth, canvasHeight], width);
186
+ resetMap = prepareOlMap(map, renderSize);
187
+ screenshotCanvas = await getImageFromOpenlayers(map, renderSize);
188
+ } else {
189
+ throw new Error('wrong Map');
190
+ }
191
+ resetMap();
192
+ return screenshotCanvas;
193
+ }
@@ -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
+ }