@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.
- package/dist/cesium.d.ts +5 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -1
- package/dist/src/interaction/coordinateAtPixel.js +2 -2
- package/dist/src/interaction/coordinateAtPixel.js.map +1 -1
- package/dist/src/layer/cesium/cogCesiumImpl.d.ts +14 -0
- package/dist/src/layer/cesium/cogCesiumImpl.js +28 -0
- package/dist/src/layer/cesium/cogCesiumImpl.js.map +1 -0
- package/dist/src/layer/cesium/cogImageryProvider.d.ts +31 -0
- package/dist/src/layer/cesium/cogImageryProvider.js +258 -0
- package/dist/src/layer/cesium/cogImageryProvider.js.map +1 -0
- package/dist/src/layer/cogLayer.d.ts +37 -0
- package/dist/src/layer/cogLayer.js +119 -0
- package/dist/src/layer/cogLayer.js.map +1 -0
- package/dist/src/layer/openlayers/cogOpenlayersImpl.d.ts +23 -0
- package/dist/src/layer/openlayers/cogOpenlayersImpl.js +54 -0
- package/dist/src/layer/openlayers/cogOpenlayersImpl.js.map +1 -0
- package/dist/src/layer/openlayers/layerOpenlayersImpl.d.ts +3 -1
- package/dist/src/layer/openlayers/layerOpenlayersImpl.js +5 -3
- package/dist/src/layer/openlayers/layerOpenlayersImpl.js.map +1 -1
- package/dist/src/layer/panoramaDatasetLayer.d.ts +0 -1
- package/dist/src/layer/panoramaDatasetLayer.js +1 -10
- package/dist/src/layer/panoramaDatasetLayer.js.map +1 -1
- package/dist/src/map/panoramaMap.js +6 -1
- package/dist/src/map/panoramaMap.js.map +1 -1
- package/dist/src/panorama/panoramaImageView.js +12 -15
- package/dist/src/panorama/panoramaImageView.js.map +1 -1
- package/dist/src/panorama/panoramaTile.d.ts +8 -4
- package/dist/src/panorama/panoramaTile.js +53 -9
- package/dist/src/panorama/panoramaTile.js.map +1 -1
- package/dist/src/panorama/panoramaTileMaterial.d.ts +0 -6
- package/dist/src/panorama/panoramaTileMaterial.js +0 -12
- package/dist/src/panorama/panoramaTileMaterial.js.map +1 -1
- package/dist/src/panorama/panoramaTileProvider.js +7 -8
- package/dist/src/panorama/panoramaTileProvider.js.map +1 -1
- package/dist/src/util/flight/flightRecorder.d.ts +10 -0
- package/dist/src/util/flight/flightRecorder.js +172 -0
- package/dist/src/util/flight/flightRecorder.js.map +1 -0
- package/dist/src/util/math.d.ts +4 -1
- package/dist/src/util/math.js +20 -1
- package/dist/src/util/math.js.map +1 -1
- package/dist/tests/data/cog/test_grey_world.tif +0 -0
- package/dist/tests/data/cog/test_rgb.tif +0 -0
- package/dist/tests/data/cog/test_rgb_world.tif +0 -0
- package/index.ts +9 -0
- package/package.json +1 -1
- package/src/cesium/cesium.d.ts +5 -0
- package/src/interaction/coordinateAtPixel.ts +2 -2
- package/src/layer/cesium/cogCesiumImpl.ts +36 -0
- package/src/layer/cesium/cogImageryProvider.ts +389 -0
- package/src/layer/cogLayer.ts +162 -0
- package/src/layer/openlayers/cogOpenlayersImpl.ts +75 -0
- package/src/layer/openlayers/layerOpenlayersImpl.ts +7 -4
- package/src/layer/panoramaDatasetLayer.ts +1 -12
- package/src/map/panoramaMap.ts +6 -1
- package/src/panorama/panoramaImageView.ts +15 -18
- package/src/panorama/panoramaTile.ts +81 -16
- package/src/panorama/panoramaTileMaterial.ts +0 -14
- package/src/panorama/panoramaTileProvider.ts +7 -10
- package/src/util/flight/flightRecorder.ts +234 -0
- package/src/util/math.ts +34 -0
- package/dist/src/panorama/panoramaImageCache.d.ts +0 -8
- package/dist/src/panorama/panoramaImageCache.js +0 -18
- package/dist/src/panorama/panoramaImageCache.js.map +0 -1
- package/src/panorama/panoramaImageCache.ts +0 -19
package/src/map/panoramaMap.ts
CHANGED
|
@@ -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
|
|
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,
|
|
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
|
-
|
|
203
|
-
|
|
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,
|
|
213
|
-
primitiveCollection.add(
|
|
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((
|
|
294
|
-
if (
|
|
295
|
-
|
|
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((
|
|
317
|
-
primitiveCollection.remove(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
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
|
-
|
|
82
|
-
return
|
|
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
|
-
|
|
86
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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,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
|
-
}
|