@vcmap/core 6.2.0-rc.1 → 6.2.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 (66) hide show
  1. package/dist/cesium.d.ts +5 -0
  2. package/dist/index.d.ts +5 -0
  3. package/dist/index.js +4 -0
  4. package/dist/index.js.map +1 -1
  5. package/dist/src/interaction/coordinateAtPixel.js +2 -2
  6. package/dist/src/interaction/coordinateAtPixel.js.map +1 -1
  7. package/dist/src/layer/cesium/cogCesiumImpl.d.ts +14 -0
  8. package/dist/src/layer/cesium/cogCesiumImpl.js +28 -0
  9. package/dist/src/layer/cesium/cogCesiumImpl.js.map +1 -0
  10. package/dist/src/layer/cesium/cogImageryProvider.d.ts +31 -0
  11. package/dist/src/layer/cesium/cogImageryProvider.js +258 -0
  12. package/dist/src/layer/cesium/cogImageryProvider.js.map +1 -0
  13. package/dist/src/layer/cogLayer.d.ts +37 -0
  14. package/dist/src/layer/cogLayer.js +119 -0
  15. package/dist/src/layer/cogLayer.js.map +1 -0
  16. package/dist/src/layer/openlayers/cogOpenlayersImpl.d.ts +23 -0
  17. package/dist/src/layer/openlayers/cogOpenlayersImpl.js +54 -0
  18. package/dist/src/layer/openlayers/cogOpenlayersImpl.js.map +1 -0
  19. package/dist/src/layer/openlayers/layerOpenlayersImpl.d.ts +3 -1
  20. package/dist/src/layer/openlayers/layerOpenlayersImpl.js +5 -3
  21. package/dist/src/layer/openlayers/layerOpenlayersImpl.js.map +1 -1
  22. package/dist/src/layer/panoramaDatasetLayer.d.ts +0 -1
  23. package/dist/src/layer/panoramaDatasetLayer.js +1 -10
  24. package/dist/src/layer/panoramaDatasetLayer.js.map +1 -1
  25. package/dist/src/map/panoramaMap.js +6 -1
  26. package/dist/src/map/panoramaMap.js.map +1 -1
  27. package/dist/src/panorama/panoramaImageView.js +12 -15
  28. package/dist/src/panorama/panoramaImageView.js.map +1 -1
  29. package/dist/src/panorama/panoramaTile.d.ts +8 -4
  30. package/dist/src/panorama/panoramaTile.js +53 -9
  31. package/dist/src/panorama/panoramaTile.js.map +1 -1
  32. package/dist/src/panorama/panoramaTileMaterial.d.ts +0 -6
  33. package/dist/src/panorama/panoramaTileMaterial.js +0 -12
  34. package/dist/src/panorama/panoramaTileMaterial.js.map +1 -1
  35. package/dist/src/panorama/panoramaTileProvider.js +7 -8
  36. package/dist/src/panorama/panoramaTileProvider.js.map +1 -1
  37. package/dist/src/util/flight/flightRecorder.d.ts +10 -0
  38. package/dist/src/util/flight/flightRecorder.js +172 -0
  39. package/dist/src/util/flight/flightRecorder.js.map +1 -0
  40. package/dist/src/util/math.d.ts +4 -1
  41. package/dist/src/util/math.js +20 -1
  42. package/dist/src/util/math.js.map +1 -1
  43. package/dist/tests/data/cog/test_grey_world.tif +0 -0
  44. package/dist/tests/data/cog/test_rgb.tif +0 -0
  45. package/dist/tests/data/cog/test_rgb_world.tif +0 -0
  46. package/index.ts +9 -0
  47. package/package.json +1 -1
  48. package/src/cesium/cesium.d.ts +5 -0
  49. package/src/interaction/coordinateAtPixel.ts +2 -2
  50. package/src/layer/cesium/cogCesiumImpl.ts +36 -0
  51. package/src/layer/cesium/cogImageryProvider.ts +389 -0
  52. package/src/layer/cogLayer.ts +162 -0
  53. package/src/layer/openlayers/cogOpenlayersImpl.ts +75 -0
  54. package/src/layer/openlayers/layerOpenlayersImpl.ts +7 -4
  55. package/src/layer/panoramaDatasetLayer.ts +1 -12
  56. package/src/map/panoramaMap.ts +6 -1
  57. package/src/panorama/panoramaImageView.ts +15 -18
  58. package/src/panorama/panoramaTile.ts +81 -16
  59. package/src/panorama/panoramaTileMaterial.ts +0 -14
  60. package/src/panorama/panoramaTileProvider.ts +7 -10
  61. package/src/util/flight/flightRecorder.ts +234 -0
  62. package/src/util/math.ts +34 -0
  63. package/dist/src/panorama/panoramaImageCache.d.ts +0 -8
  64. package/dist/src/panorama/panoramaImageCache.js +0 -18
  65. package/dist/src/panorama/panoramaImageCache.js.map +0 -1
  66. package/src/panorama/panoramaImageCache.ts +0 -19
@@ -236,9 +236,12 @@ export default class PanoramaMap extends VcsMap {
236
236
 
237
237
  if (closestImage) {
238
238
  if (viewpoint.heading != null) {
239
- this._cesiumWidget.camera.setView({
239
+ const { camera } = this._cesiumWidget;
240
+ camera.setView({
240
241
  orientation: {
241
242
  heading: CesiumMath.toRadians(viewpoint.heading),
243
+ pitch: camera.pitch,
244
+ roll: camera.roll,
242
245
  },
243
246
  });
244
247
  }
@@ -318,7 +321,9 @@ export default class PanoramaMap extends VcsMap {
318
321
  */
319
322
  setCurrentImage(image?: PanoramaImage): void {
320
323
  if (this._currentImage !== image) {
324
+ const currentImage = this._currentImage;
321
325
  this._currentImage = image;
326
+ currentImage?.destroy();
322
327
  this.currentImageChanged.raiseEvent(image);
323
328
  }
324
329
  }
@@ -1,4 +1,4 @@
1
- import type { Camera } from '@vcmap-cesium/engine';
1
+ import type { Camera, Primitive } from '@vcmap-cesium/engine';
2
2
  import { Cartesian2, Matrix4, Cartesian3 } from '@vcmap-cesium/engine';
3
3
  import { getWidth } from 'ol/extent.js';
4
4
  import type { Size } from 'ol/size.js';
@@ -164,16 +164,13 @@ function setupDepthHandling(
164
164
 
165
165
  /**
166
166
  * The image wrapper is responsible for managing the panorama image tiles and rendering them.
167
- * @param image
168
- * @param primitiveCollection
169
- * @param camera
170
- * @param canvas
171
167
  */
172
168
  function createImageWrapper(
173
169
  image: PanoramaImage,
174
170
  primitiveCollection: PanoramaTilePrimitiveCollection,
175
171
  camera: Camera,
176
172
  canvas: HTMLCanvasElement,
173
+ map: PanoramaMap,
177
174
  ): ImageWrapper {
178
175
  const { tileSize, maxLevel, minLevel, hasDepth } = image;
179
176
  const baseTileCoordinates = createMinLevelTiles(minLevel);
@@ -181,7 +178,7 @@ function createImageWrapper(
181
178
  ? setupDepthHandling(image, primitiveCollection, camera, canvas)
182
179
  : (): void => {};
183
180
 
184
- const currentTiles = new Map<string, PanoramaTile>();
181
+ const currentTiles = new Map<string, Primitive>();
185
182
  let currentLevel = minLevel;
186
183
  let currentTileCoordinates: PanoramaTileCoordinate[] = [
187
184
  ...baseTileCoordinates,
@@ -193,14 +190,15 @@ function createImageWrapper(
193
190
  .createVisibleTiles(currentTileCoordinates)
194
191
  .forEach((tile) => {
195
192
  if (!currentTiles.has(tile.tileCoordinate.key)) {
193
+ const primitive = tile.getPrimitive(map);
196
194
  if (
197
195
  tile.tileCoordinate.level === minLevel &&
198
196
  !(tile as PanoramaTile & { [baseLevelScaled]?: boolean })[
199
197
  baseLevelScaled
200
198
  ]
201
199
  ) {
202
- tile.primitive.modelMatrix = Matrix4.multiplyByScale(
203
- tile.primitive.modelMatrix,
200
+ primitive.modelMatrix = Matrix4.multiplyByScale(
201
+ primitive.modelMatrix,
204
202
  new Cartesian3(1.01, 1.01, 1.01),
205
203
  new Matrix4(),
206
204
  );
@@ -209,8 +207,8 @@ function createImageWrapper(
209
207
  ] = true;
210
208
  }
211
209
 
212
- currentTiles.set(tile.tileCoordinate.key, tile);
213
- primitiveCollection.add(tile.primitive);
210
+ currentTiles.set(tile.tileCoordinate.key, primitive);
211
+ primitiveCollection.add(primitive);
214
212
  }
215
213
  });
216
214
  };
@@ -290,12 +288,10 @@ function createImageWrapper(
290
288
 
291
289
  // push base tile coordinates onto the back of the array, so they are always loaded first.
292
290
  currentTileCoordinates.push(...baseTileCoordinates);
293
- currentTiles.forEach((tile) => {
294
- if (
295
- !currentTileCoordinates.find((c) => c.key === tile.tileCoordinate.key)
296
- ) {
297
- primitiveCollection.remove(tile.primitive);
298
- currentTiles.delete(tile.tileCoordinate.key);
291
+ currentTiles.forEach((primitive, key) => {
292
+ if (!currentTileCoordinates.find((c) => c.key === key)) {
293
+ primitiveCollection.remove(primitive);
294
+ currentTiles.delete(key);
299
295
  }
300
296
  });
301
297
 
@@ -313,8 +309,8 @@ function createImageWrapper(
313
309
  },
314
310
  render,
315
311
  destroy(): void {
316
- currentTiles.forEach((tile) => {
317
- primitiveCollection.remove(tile.primitive);
312
+ currentTiles.forEach((primitive) => {
313
+ primitiveCollection.remove(primitive);
318
314
  });
319
315
  currentTiles.clear();
320
316
  showIntensityListener();
@@ -400,6 +396,7 @@ export function createPanoramaImageView(map: PanoramaMap): PanoramaImageView {
400
396
  primitiveCollection,
401
397
  scene.camera,
402
398
  scene.canvas,
399
+ map,
403
400
  );
404
401
  currentView.suspendTileLoading = suspendTileLoading;
405
402
  } else {
@@ -1,4 +1,3 @@
1
- import type { Matrix4 } from '@vcmap-cesium/engine';
2
1
  import {
3
2
  Cartesian3,
4
3
  EllipsoidGeometry,
@@ -6,16 +5,28 @@ import {
6
5
  MaterialAppearance,
7
6
  Primitive,
8
7
  VertexFormat,
8
+ type Matrix4,
9
9
  } from '@vcmap-cesium/engine';
10
10
  import type { Size } from 'ol/size.js';
11
11
  import PanoramaTileMaterial from './panoramaTileMaterial.js';
12
12
  import type { PanoramaTileCoordinate } from './panoramaTileCoordinate.js';
13
13
  import { tileSizeInRadians } from './panoramaTileCoordinate.js';
14
+ import type PanoramaMap from '../map/panoramaMap.js';
15
+ import type {
16
+ PanoramaResourceData,
17
+ PanoramaResourceType,
18
+ } from './panoramaTileProvider.js';
14
19
 
15
20
  export type PanoramaTile = {
16
- readonly primitive: Primitive;
21
+ hasResource(type: PanoramaResourceType): boolean;
22
+ setResource<T extends PanoramaResourceType>(
23
+ type: T,
24
+ resource: PanoramaResourceData<T>,
25
+ ): void;
26
+ getDepthAtPixel(x: number, y: number): number | undefined;
17
27
  readonly tileCoordinate: PanoramaTileCoordinate;
18
- readonly material: PanoramaTileMaterial;
28
+ getPrimitive(map: PanoramaMap): Primitive;
29
+ getMaterial(map: PanoramaMap): PanoramaTileMaterial | undefined;
19
30
  destroy(): void;
20
31
  };
21
32
 
@@ -64,26 +75,80 @@ export function createPanoramaTile(
64
75
  modelMatrix: Matrix4,
65
76
  tileSize: Size,
66
77
  ): PanoramaTile {
67
- const material = new PanoramaTileMaterial(tileCoordinate, tileSize);
68
- const primitive = createPanoramaTilePrimitive(
69
- tileCoordinate,
70
- modelMatrix,
71
- material,
72
- );
78
+ let destroyed = false;
79
+ const primitives = new Map<
80
+ PanoramaMap,
81
+ { primitive: Primitive; material: PanoramaTileMaterial }
82
+ >();
83
+
84
+ let resources: { [K in PanoramaResourceType]?: PanoramaResourceData<K> } = {};
73
85
 
74
86
  return {
75
- get primitive(): Primitive {
76
- return primitive;
77
- },
78
87
  get tileCoordinate(): PanoramaTileCoordinate {
79
88
  return tileCoordinate;
80
89
  },
81
- get material(): PanoramaTileMaterial {
82
- return material;
90
+ hasResource(type: PanoramaResourceType): boolean {
91
+ return resources[type] != null;
92
+ },
93
+ setResource<T extends PanoramaResourceType>(
94
+ type: T,
95
+ resource: PanoramaResourceData<T>,
96
+ ): void {
97
+ if (this.hasResource(type)) {
98
+ throw new Error(
99
+ `Resource of type "${type}" already set for this tile. Cannot overwrite.`,
100
+ );
101
+ }
102
+ resources[type] = resource as ImageBitmap & Float32Array;
103
+ primitives.forEach(({ material }) => {
104
+ material.setTexture(type, resource);
105
+ });
106
+ },
107
+ getPrimitive(map: PanoramaMap): Primitive {
108
+ if (destroyed) {
109
+ throw new Error('Cannot get primitive from destroyed panorama tile.');
110
+ }
111
+ if (primitives.has(map)) {
112
+ return primitives.get(map)!.primitive;
113
+ }
114
+ const material = new PanoramaTileMaterial(tileCoordinate, tileSize);
115
+ Object.entries(resources).forEach(([type, resource]) => {
116
+ material.setTexture(type as PanoramaResourceType, resource);
117
+ });
118
+ const primitive = createPanoramaTilePrimitive(
119
+ tileCoordinate,
120
+ modelMatrix,
121
+ material,
122
+ );
123
+ primitives.set(map, { primitive, material });
124
+ return primitive;
125
+ },
126
+ getMaterial(map: PanoramaMap): PanoramaTileMaterial | undefined {
127
+ if (primitives.has(map)) {
128
+ return primitives.get(map)!.material;
129
+ }
130
+ return undefined;
131
+ },
132
+ /**
133
+ * Returns the normalized depth value [0, 1] at the given pixel coordinates in the panorama tile.
134
+ * @param x
135
+ * @param y
136
+ */
137
+ getDepthAtPixel(x: number, y: number): number | undefined {
138
+ if (!resources.depth) {
139
+ return undefined;
140
+ }
141
+
142
+ const index = y * tileSize[0] + x;
143
+ return resources.depth[index];
83
144
  },
84
145
  destroy(): void {
85
- primitive.destroy();
86
- material.destroy();
146
+ destroyed = true;
147
+ resources = {};
148
+ primitives.forEach(({ primitive, material }) => {
149
+ primitive.destroy();
150
+ material.destroy();
151
+ });
87
152
  },
88
153
  };
89
154
  }
@@ -258,20 +258,6 @@ export default class PanoramaTileMaterial extends Material {
258
258
  return this.uniforms[uniform] !== Material.DefaultImageId;
259
259
  }
260
260
 
261
- /**
262
- * Returns the normalized depth value [0, 1] at the given pixel coordinates in the panorama tile.
263
- * @param x
264
- * @param y
265
- */
266
- getDepthAtPixel(x: number, y: number): number | undefined {
267
- if (!this._depthData) {
268
- return undefined;
269
- }
270
-
271
- const index = y * this._tileSize[0] + x;
272
- return this._depthData[index];
273
- }
274
-
275
261
  /**
276
262
  * Internal cesium API to update the material.
277
263
  * @param context
@@ -168,7 +168,7 @@ function createPanoramaResourceProvider(
168
168
  ): Promise<void> => {
169
169
  const { tile, resource } = request;
170
170
  const { type } = resource;
171
- if (!tile.material.hasTexture(type)) {
171
+ if (!tile.hasResource(type)) {
172
172
  const { levelImages } = resource;
173
173
  const { tileCoordinate } = tile;
174
174
  const levelImage = levelImages[tileCoordinate.level - minLevel];
@@ -181,7 +181,7 @@ function createPanoramaResourceProvider(
181
181
  poolOrDecoder,
182
182
  );
183
183
 
184
- tile.material.setTexture(
184
+ tile.setResource(
185
185
  type,
186
186
  resourceData.data as unknown as PanoramaResourceData<PanoramaResourceType>,
187
187
  );
@@ -257,17 +257,14 @@ function createPanoramaResourceProvider(
257
257
  const createOrUpdateQueue = (panoramaTile: PanoramaTile[]): void => {
258
258
  const newResources = panoramaTile.flatMap((tile) => {
259
259
  const resourceRequests: TileResourceRequest<PanoramaResourceType>[] = [];
260
- if (!tile.material.hasTexture('rgb') && !loadingTiles.get(tile)?.rgb) {
260
+ if (!tile.hasResource('rgb') && !loadingTiles.get(tile)?.rgb) {
261
261
  resourceRequests.push({
262
262
  tile,
263
263
  resource: resources.rgb,
264
264
  });
265
265
  }
266
266
  if (resources.depth) {
267
- if (
268
- !tile.material.hasTexture('depth') &&
269
- !loadingTiles.get(tile)?.depth
270
- ) {
267
+ if (!tile.hasResource('depth') && !loadingTiles.get(tile)?.depth) {
271
268
  resourceRequests.push({
272
269
  tile,
273
270
  resource: resources.depth,
@@ -276,7 +273,7 @@ function createPanoramaResourceProvider(
276
273
  }
277
274
  if (showIntensity && resources.intensity) {
278
275
  if (
279
- !tile.material.hasTexture('intensity') &&
276
+ !tile.hasResource('intensity') &&
280
277
  !loadingTiles.get(tile)?.intensity
281
278
  ) {
282
279
  resourceRequests.push({
@@ -329,7 +326,7 @@ function createPanoramaResourceProvider(
329
326
  throw new Error(`Resource type ${type} not found`);
330
327
  }
331
328
 
332
- if (tile.material.hasTexture(type)) {
329
+ if (tile.hasResource(type)) {
333
330
  return Promise.resolve();
334
331
  }
335
332
 
@@ -447,7 +444,7 @@ export function createPanoramaTileProvider(
447
444
  const x = tileSize[0] - Math.floor((offsetX / width) * tileSize[0]);
448
445
  const y = Math.floor((offsetY / height) * tileSize[1]);
449
446
 
450
- const depthValue = tile.material.getDepthAtPixel(x, y);
447
+ const depthValue = tile.getDepthAtPixel(x, y);
451
448
  if (depthValue) {
452
449
  return interpolateDepth(
453
450
  depthValue,
@@ -0,0 +1,234 @@
1
+ import type VcsApp from '../../vcsApp.js';
2
+ import type Layer from '../../layer/layer.js';
3
+ import CesiumMap from '../../map/cesiumMap.js';
4
+ import CesiumTilesetLayer from '../../layer/cesiumTilesetLayer.js';
5
+ import { type FlightPlayer } from './flightPlayer.js';
6
+
7
+ export type FlightPathRecorderOptions = {
8
+ fps?: number;
9
+ highDefinition?: boolean;
10
+ };
11
+
12
+ async function getFlightFrames(
13
+ app: VcsApp,
14
+ player: FlightPlayer,
15
+ fps: number,
16
+ cancelToken: { cancelled: boolean },
17
+ maxNumberOfFrames = 100,
18
+ ): Promise<ImageBitmap[]> {
19
+ const scene = (app.maps.activeMap as CesiumMap).getScene()!;
20
+ const { canvas, globe } = scene;
21
+ let recordedTime = player.clock.currentTime - 1;
22
+ let numberOfFrames = Math.ceil(
23
+ (player.clock.endTime - player.clock.currentTime) * fps,
24
+ );
25
+ if (numberOfFrames > maxNumberOfFrames) {
26
+ numberOfFrames = maxNumberOfFrames;
27
+ }
28
+ const images = new Array<Promise<ImageBitmap>>(numberOfFrames);
29
+ const tilesetImpl = [...app.layers]
30
+ .filter(
31
+ (l: Layer): l is CesiumTilesetLayer =>
32
+ l instanceof CesiumTilesetLayer && l.active,
33
+ )
34
+ .flatMap((tileset) =>
35
+ tileset.getImplementationsForMap(app.maps.activeMap!),
36
+ );
37
+
38
+ const layerListeners = [
39
+ app.layers.stateChanged.addEventListener((layer) => {
40
+ if (layer instanceof CesiumTilesetLayer) {
41
+ const implementations = layer.getImplementationsForMap(
42
+ app.maps.activeMap!,
43
+ );
44
+ if (layer.active) {
45
+ tilesetImpl.push(...implementations);
46
+ } else {
47
+ implementations.forEach((impl) => {
48
+ tilesetImpl.splice(
49
+ tilesetImpl.findIndex((i) => i === impl),
50
+ 1,
51
+ );
52
+ });
53
+ }
54
+ }
55
+ }),
56
+ ];
57
+
58
+ let currentFrame = 0;
59
+ await new Promise<void>((resolve) => {
60
+ const handler = scene.postRender.addEventListener(() => {
61
+ if (cancelToken.cancelled) {
62
+ handler();
63
+ return;
64
+ }
65
+
66
+ if (globe.tilesLoaded) {
67
+ if (tilesetImpl.some((impl) => !impl.cesium3DTileset?.tilesLoaded)) {
68
+ return;
69
+ }
70
+ if (player.clock.currentTime === recordedTime) {
71
+ return;
72
+ }
73
+
74
+ recordedTime = player.clock.currentTime;
75
+ images[currentFrame] = createImageBitmap(canvas);
76
+ if (player.clock.currentTime === player.clock.endTime) {
77
+ handler();
78
+ resolve();
79
+ }
80
+
81
+ let nextTick = player.clock.currentTime + 1 / fps;
82
+ if (nextTick >= player.clock.endTime) {
83
+ nextTick = player.clock.endTime;
84
+ }
85
+ player.goToTime(nextTick);
86
+ currentFrame += 1;
87
+ if (currentFrame >= numberOfFrames) {
88
+ handler();
89
+ resolve();
90
+ }
91
+ }
92
+ });
93
+ });
94
+
95
+ layerListeners.forEach((listener) => {
96
+ listener();
97
+ });
98
+
99
+ return Promise.all(images);
100
+ }
101
+
102
+ export function createFlightMovie(
103
+ app: VcsApp,
104
+ player: FlightPlayer,
105
+ options?: FlightPathRecorderOptions,
106
+ ): { start: () => Promise<Blob>; cancel: () => void } {
107
+ if (!(app.maps.activeMap instanceof CesiumMap)) {
108
+ throw new Error('No active Cesium map found');
109
+ }
110
+
111
+ const cancelToken = { cancelled: false };
112
+ const videoBitsPerSecond = options?.highDefinition ? 12000000 : 6000000;
113
+
114
+ const map = app.maps.activeMap;
115
+ const scene = map.getScene()!;
116
+ const { canvas } = scene;
117
+
118
+ const destinationCanvas = document.createElement('canvas');
119
+ destinationCanvas.width = canvas.width;
120
+ destinationCanvas.height = canvas.height;
121
+ const context = destinationCanvas.getContext('2d')!;
122
+
123
+ const stream = destinationCanvas.captureStream(options?.fps ?? 30);
124
+ const recorder = new MediaRecorder(stream, {
125
+ mimeType: 'video/webm',
126
+ videoBitsPerSecond,
127
+ });
128
+
129
+ const resetMapControls = app.maps.requestExclusiveMapControls(
130
+ { apiCalls: true, keyEvents: true, pointerEvents: true },
131
+ () => {},
132
+ );
133
+
134
+ const finished = new Promise<Blob>((resolve, reject) => {
135
+ const chunksBuffer: Blob[] = [];
136
+
137
+ recorder.ondataavailable = (event): void => {
138
+ if (event.data.size > 0) {
139
+ chunksBuffer.push(event.data);
140
+ }
141
+ };
142
+
143
+ recorder.onerror = reject;
144
+ recorder.onstop = (): void => {
145
+ resolve(new Blob(chunksBuffer, { type: 'video/webm' }));
146
+ };
147
+ });
148
+
149
+ const renderBuffer = (buffer: ImageBitmap[]): Promise<void> => {
150
+ let interval: ReturnType<typeof setInterval>;
151
+ const promise = new Promise<void>((resolve) => {
152
+ const frameDuration = 1000 / (options?.fps || 30);
153
+ let currentFrame = 0;
154
+ interval = setInterval(() => {
155
+ if (cancelToken.cancelled) {
156
+ resolve();
157
+ }
158
+ const image = buffer[currentFrame];
159
+ if (image) {
160
+ context.drawImage(image, 0, 0);
161
+ currentFrame += 1;
162
+ if (currentFrame >= buffer.length) {
163
+ resolve();
164
+ }
165
+ } else {
166
+ resolve();
167
+ }
168
+ }, frameDuration);
169
+ });
170
+
171
+ return promise
172
+ .then(() => {
173
+ clearInterval(interval);
174
+ })
175
+ .catch((err: unknown) => {
176
+ clearInterval(interval);
177
+ throw err;
178
+ });
179
+ };
180
+
181
+ const requestBuffer = async (): Promise<boolean> => {
182
+ const images = await getFlightFrames(
183
+ app,
184
+ player,
185
+ options?.fps ?? 30,
186
+ cancelToken,
187
+ );
188
+
189
+ map.getCesiumWidget()!.useDefaultRenderLoop = false;
190
+ if (recorder.state === 'paused') {
191
+ recorder.resume();
192
+ } else {
193
+ recorder.start();
194
+ }
195
+
196
+ await renderBuffer(images);
197
+ map.getCesiumWidget()!.useDefaultRenderLoop = true;
198
+ if (
199
+ player.clock.currentTime === player.clock.endTime ||
200
+ cancelToken.cancelled
201
+ ) {
202
+ return false;
203
+ }
204
+ recorder.pause();
205
+ recorder.requestData();
206
+ return true;
207
+ };
208
+
209
+ const reset = (): void => {
210
+ map.getCesiumWidget()!.useDefaultRenderLoop = true;
211
+ resetMapControls?.();
212
+ };
213
+
214
+ async function start(): Promise<Blob> {
215
+ let buffering = true;
216
+ while (buffering) {
217
+ // eslint-disable-next-line no-await-in-loop
218
+ buffering = await requestBuffer();
219
+ }
220
+ stream.getTracks().forEach((track) => {
221
+ track.stop();
222
+ });
223
+ reset();
224
+ return finished;
225
+ }
226
+
227
+ const cancel = (): void => {
228
+ cancelToken.cancelled = true;
229
+ reset();
230
+ recorder.stop();
231
+ };
232
+
233
+ return { start, cancel };
234
+ }
package/src/util/math.ts CHANGED
@@ -2,9 +2,11 @@ import {
2
2
  Math as CesiumMath,
3
3
  Cartesian3,
4
4
  Cartographic,
5
+ Rectangle,
5
6
  } from '@vcmap-cesium/engine';
6
7
  import type { Coordinate } from 'ol/coordinate.js';
7
8
  import { getDistance as haversineDistance } from 'ol/sphere.js';
9
+ import { type Extent, getBottomLeft, getTopRight } from 'ol/extent.js';
8
10
  import Projection from './projection.js';
9
11
 
10
12
  /**
@@ -177,6 +179,38 @@ export function cartesianToMercator(cartesian: Cartesian3): Coordinate {
177
179
  return Projection.wgs84ToMercator(wgs84);
178
180
  }
179
181
 
182
+ export function mercatorExtentToRectangle(
183
+ extent: Extent,
184
+ result?: Rectangle,
185
+ ): Rectangle {
186
+ const bottomLeft = getBottomLeft(extent);
187
+ const topRight = getTopRight(extent);
188
+
189
+ Projection.mercatorToWgs84(bottomLeft, true);
190
+ Projection.mercatorToWgs84(topRight, true);
191
+
192
+ return Rectangle.fromDegrees(
193
+ bottomLeft[0],
194
+ bottomLeft[1],
195
+ topRight[0],
196
+ topRight[1],
197
+ result,
198
+ );
199
+ }
200
+
201
+ export function rectangleToMercatorExtent(rectangle: Rectangle): Extent {
202
+ const bottomLeft = Projection.wgs84ToMercator([
203
+ CesiumMath.toDegrees(rectangle.west),
204
+ CesiumMath.toDegrees(rectangle.south),
205
+ ]);
206
+ const topRight = Projection.wgs84ToMercator([
207
+ CesiumMath.toDegrees(rectangle.east),
208
+ CesiumMath.toDegrees(rectangle.north),
209
+ ]);
210
+
211
+ return [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
212
+ }
213
+
180
214
  export function getMidPoint(p1: Coordinate, p2: Coordinate): Coordinate {
181
215
  const stride = p1.length;
182
216
  const output = new Array<number>(stride);
@@ -1,8 +0,0 @@
1
- import LRUCache from 'ol/structs/LRUCache.js';
2
- import type { PanoramaImage } from './panoramaImage.js';
3
- /**
4
- * A specialized LRU cache
5
- */
6
- export declare class PanoramaImageCache extends LRUCache<Promise<PanoramaImage>> {
7
- deleteOldest(): void;
8
- }
@@ -1,18 +0,0 @@
1
- import LRUCache from 'ol/structs/LRUCache.js';
2
- /**
3
- * A specialized LRU cache
4
- */
5
- export class PanoramaImageCache extends LRUCache {
6
- deleteOldest() {
7
- const entry = this.pop();
8
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
9
- if (entry) {
10
- entry
11
- .then((image) => {
12
- image.destroy();
13
- })
14
- .catch(() => { });
15
- }
16
- }
17
- }
18
- //# sourceMappingURL=panoramaImageCache.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"panoramaImageCache.js","sourceRoot":"","sources":["../../../src/panorama/panoramaImageCache.ts"],"names":[],"mappings":"AAAA,OAAO,QAAQ,MAAM,wBAAwB,CAAC;AAG9C;;GAEG;AACH,MAAM,OAAO,kBAAmB,SAAQ,QAAgC;IACtE,YAAY;QACV,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;QACzB,kEAAkE;QAClE,IAAI,KAAK,EAAE,CAAC;YACV,KAAK;iBACF,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE;gBACd,KAAK,CAAC,OAAO,EAAE,CAAC;YAClB,CAAC,CAAC;iBACD,KAAK,CAAC,GAAG,EAAE,GAAE,CAAC,CAAC,CAAC;QACrB,CAAC;IACH,CAAC;CACF"}
@@ -1,19 +0,0 @@
1
- import LRUCache from 'ol/structs/LRUCache.js';
2
- import type { PanoramaImage } from './panoramaImage.js';
3
-
4
- /**
5
- * A specialized LRU cache
6
- */
7
- export class PanoramaImageCache extends LRUCache<Promise<PanoramaImage>> {
8
- deleteOldest(): void {
9
- const entry = this.pop();
10
- // eslint-disable-next-line @typescript-eslint/no-misused-promises
11
- if (entry) {
12
- entry
13
- .then((image) => {
14
- image.destroy();
15
- })
16
- .catch(() => {});
17
- }
18
- }
19
- }