@vcmap/core 6.2.0-rc.1 → 6.2.0-rc.3

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 (74) hide show
  1. package/dist/cesium.d.ts +5 -0
  2. package/dist/index.d.ts +6 -1
  3. package/dist/index.js +5 -1
  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 +7 -2
  26. package/dist/src/map/panoramaMap.js.map +1 -1
  27. package/dist/src/panorama/panoramaImage.d.ts +7 -0
  28. package/dist/src/panorama/panoramaImage.js +5 -0
  29. package/dist/src/panorama/panoramaImage.js.map +1 -1
  30. package/dist/src/panorama/panoramaImageView.js +29 -36
  31. package/dist/src/panorama/panoramaImageView.js.map +1 -1
  32. package/dist/src/panorama/panoramaTile.d.ts +8 -4
  33. package/dist/src/panorama/panoramaTile.js +53 -9
  34. package/dist/src/panorama/panoramaTile.js.map +1 -1
  35. package/dist/src/panorama/panoramaTileMaterial.d.ts +0 -6
  36. package/dist/src/panorama/panoramaTileMaterial.js +0 -12
  37. package/dist/src/panorama/panoramaTileMaterial.js.map +1 -1
  38. package/dist/src/panorama/panoramaTileProvider.js +7 -8
  39. package/dist/src/panorama/panoramaTileProvider.js.map +1 -1
  40. package/dist/src/util/flight/flightRecorder.d.ts +10 -0
  41. package/dist/src/util/flight/flightRecorder.js +175 -0
  42. package/dist/src/util/flight/flightRecorder.js.map +1 -0
  43. package/dist/src/util/mapCollection.d.ts +2 -0
  44. package/dist/src/util/mapCollection.js +3 -0
  45. package/dist/src/util/mapCollection.js.map +1 -1
  46. package/dist/src/util/math.d.ts +4 -1
  47. package/dist/src/util/math.js +20 -1
  48. package/dist/src/util/math.js.map +1 -1
  49. package/dist/tests/data/cog/test_grey_world.tif +0 -0
  50. package/dist/tests/data/cog/test_rgb.tif +0 -0
  51. package/dist/tests/data/cog/test_rgb_world.tif +0 -0
  52. package/index.ts +10 -1
  53. package/package.json +1 -1
  54. package/src/cesium/cesium.d.ts +5 -0
  55. package/src/interaction/coordinateAtPixel.ts +2 -2
  56. package/src/layer/cesium/cogCesiumImpl.ts +36 -0
  57. package/src/layer/cesium/cogImageryProvider.ts +389 -0
  58. package/src/layer/cogLayer.ts +162 -0
  59. package/src/layer/openlayers/cogOpenlayersImpl.ts +75 -0
  60. package/src/layer/openlayers/layerOpenlayersImpl.ts +7 -4
  61. package/src/layer/panoramaDatasetLayer.ts +1 -12
  62. package/src/map/panoramaMap.ts +7 -2
  63. package/src/panorama/panoramaImage.ts +14 -0
  64. package/src/panorama/panoramaImageView.ts +32 -40
  65. package/src/panorama/panoramaTile.ts +81 -16
  66. package/src/panorama/panoramaTileMaterial.ts +0 -14
  67. package/src/panorama/panoramaTileProvider.ts +7 -10
  68. package/src/util/flight/flightRecorder.ts +237 -0
  69. package/src/util/mapCollection.ts +4 -0
  70. package/src/util/math.ts +34 -0
  71. package/dist/src/panorama/panoramaImageCache.d.ts +0 -8
  72. package/dist/src/panorama/panoramaImageCache.js +0 -18
  73. package/dist/src/panorama/panoramaImageCache.js.map +0 -1
  74. package/src/panorama/panoramaImageCache.ts +0 -19
@@ -0,0 +1,162 @@
1
+ import GeoTIFF, { type Options as GeoTIFFOptions } from 'ol/source/GeoTIFF.js';
2
+ import type { EventsKey } from 'ol/events.js';
3
+ import { unByKey } from 'ol/Observable.js';
4
+ import RasterLayer, {
5
+ type RasterLayerImplementationOptions,
6
+ type RasterLayerOptions,
7
+ TilingScheme,
8
+ } from './rasterLayer.js';
9
+ import OpenlayersMap from '../map/openlayersMap.js';
10
+ import CesiumMap from '../map/cesiumMap.js';
11
+ import { layerClassRegistry } from '../classRegistry.js';
12
+ import type VcsMap from '../map/vcsMap.js';
13
+ import COGOpenlayersImpl from './openlayers/cogOpenlayersImpl.js';
14
+ import COGCesiumImpl from './cesium/cogCesiumImpl.js';
15
+ import Extent from '../util/extent.js';
16
+ import { mercatorProjection } from '../util/projection.js';
17
+
18
+ export type COGLayerOptions = Omit<RasterLayerOptions, 'tilingSchema'> & {
19
+ /**
20
+ * Passed directly to the GeoTIFF source.
21
+ */
22
+ convertToRGB?: boolean | 'auto';
23
+ /**
24
+ * Passed directly to the GeoTIFF source.
25
+ */
26
+ normalize?: boolean;
27
+ /**
28
+ * Passed directly to the GeoTIFF source.
29
+ */
30
+ interpolate?: boolean;
31
+ };
32
+
33
+ export type COGLayerImplementationOptions = RasterLayerImplementationOptions & {
34
+ source: GeoTIFF;
35
+ };
36
+
37
+ function getTilingSchemaFromSource(source: GeoTIFF): Promise<TilingScheme> {
38
+ let key: EventsKey | undefined;
39
+ return new Promise<TilingScheme>((resolve, reject) => {
40
+ const handleChange = (): void => {
41
+ if (source.getState() === 'ready') {
42
+ if (key) {
43
+ unByKey(key);
44
+ }
45
+
46
+ const projection = source.getProjection();
47
+ if (!source.getTileGrid()) {
48
+ reject(new Error(`Missing tilegrid in GeoTIFF source not found.`));
49
+ } else if (projection?.getCode() === 'EPSG:4326') {
50
+ resolve(TilingScheme.GEOGRAPHIC);
51
+ } else if (projection?.getCode() === 'EPSG:3857') {
52
+ resolve(TilingScheme.MERCATOR);
53
+ } else {
54
+ reject(new Error(`Unexpected code projection`));
55
+ }
56
+ }
57
+ };
58
+
59
+ key = source.on('change', handleChange);
60
+ handleChange();
61
+ });
62
+ }
63
+
64
+ class COGLayer extends RasterLayer<COGOpenlayersImpl | COGCesiumImpl> {
65
+ static get className(): string {
66
+ return 'COGLayer';
67
+ }
68
+
69
+ static getDefaultOptions(): COGLayerOptions {
70
+ return {
71
+ ...RasterLayer.getDefaultOptions(),
72
+ convertToRGB: 'auto',
73
+ normalize: undefined,
74
+ interpolate: undefined,
75
+ };
76
+ }
77
+
78
+ private _sourceOptions: Partial<GeoTIFFOptions>;
79
+ private _source: GeoTIFF | undefined;
80
+
81
+ constructor(options: COGLayerOptions) {
82
+ super(options);
83
+ const defaultOptions = COGLayer.getDefaultOptions();
84
+ this._sourceOptions = {
85
+ convertToRGB: options.convertToRGB ?? defaultOptions.convertToRGB,
86
+ normalize: options.normalize,
87
+ interpolate: options.interpolate,
88
+ };
89
+
90
+ this._supportedMaps = [OpenlayersMap.className, CesiumMap.className];
91
+ }
92
+
93
+ async initialize(): Promise<void> {
94
+ if (!this.initialized) {
95
+ this._source = new GeoTIFF({
96
+ ...this._sourceOptions,
97
+ sources: [{ url: this.url }],
98
+ });
99
+ this.tilingSchema = await getTilingSchemaFromSource(this._source);
100
+ }
101
+ return super.initialize();
102
+ }
103
+
104
+ getImplementationOptions(): COGLayerImplementationOptions {
105
+ return {
106
+ ...super.getImplementationOptions(),
107
+ source: this._source!,
108
+ };
109
+ }
110
+
111
+ createImplementationsForMap(
112
+ map: VcsMap,
113
+ ): (COGOpenlayersImpl | COGCesiumImpl)[] {
114
+ if (map instanceof OpenlayersMap) {
115
+ return [new COGOpenlayersImpl(map, this.getImplementationOptions())];
116
+ } else if (map instanceof CesiumMap) {
117
+ return [new COGCesiumImpl(map, this.getImplementationOptions())];
118
+ }
119
+
120
+ return [];
121
+ }
122
+
123
+ toJSON(): COGLayerOptions {
124
+ const config: Partial<COGLayerOptions> & RasterLayerOptions =
125
+ super.toJSON();
126
+ const defaultOptions = COGLayer.getDefaultOptions();
127
+
128
+ delete config.tilingSchema;
129
+ if (this._sourceOptions.convertToRGB !== defaultOptions.convertToRGB) {
130
+ config.convertToRGB = this._sourceOptions.convertToRGB;
131
+ }
132
+
133
+ if (this._sourceOptions.normalize != null) {
134
+ config.normalize = this._sourceOptions.normalize;
135
+ }
136
+
137
+ if (this._sourceOptions.interpolate != null) {
138
+ config.interpolate = this._sourceOptions.interpolate;
139
+ }
140
+
141
+ return config;
142
+ }
143
+
144
+ destroy(): void {
145
+ this._source?.dispose();
146
+ super.destroy();
147
+ }
148
+
149
+ getZoomToExtent(): Extent | null {
150
+ const extent = this._source?.getTileGrid()?.getExtent();
151
+ if (extent) {
152
+ return new Extent({
153
+ coordinates: extent,
154
+ projection: mercatorProjection.toJSON(),
155
+ });
156
+ }
157
+ return super.getZoomToExtent();
158
+ }
159
+ }
160
+
161
+ layerClassRegistry.registerClass(COGLayer.className, COGLayer);
162
+ export default COGLayer;
@@ -0,0 +1,75 @@
1
+ import type GeoTIFFSource from 'ol/source/GeoTIFF.js';
2
+ import WebGLTile from 'ol/layer/WebGLTile.js';
3
+ import { getRenderPixel } from 'ol/render.js';
4
+ import type RenderEvent from 'ol/render/Event.js';
5
+ import { SplitDirection } from '@vcmap-cesium/engine';
6
+ import RasterLayerOpenlayersImpl from './rasterLayerOpenlayersImpl.js';
7
+ import type { COGLayerImplementationOptions } from '../cogLayer.js';
8
+ import type OpenlayersMap from '../../map/openlayersMap.js';
9
+
10
+ const vcsCleared = Symbol('vcsCleared');
11
+
12
+ declare global {
13
+ interface WebGL2RenderingContext {
14
+ [vcsCleared]: number;
15
+ }
16
+ }
17
+
18
+ /**
19
+ * COGLayer implementation for {@link OpenlayersMap}.
20
+ */
21
+ class COGOpenlayersImpl extends RasterLayerOpenlayersImpl {
22
+ static get className(): string {
23
+ return 'COGOpenlayersImpl';
24
+ }
25
+
26
+ private _source: GeoTIFFSource;
27
+
28
+ constructor(map: OpenlayersMap, options: COGLayerImplementationOptions) {
29
+ super(map, options);
30
+ this._source = options.source;
31
+ }
32
+
33
+ getOLLayer(): WebGLTile {
34
+ return new WebGLTile({
35
+ source: this._source,
36
+ opacity: this.opacity,
37
+ minZoom: this.minRenderingLevel,
38
+ maxZoom: this.maxRenderingLevel,
39
+ });
40
+ }
41
+
42
+ protected override _splitPreRender(event: RenderEvent): void {
43
+ const gl = event.context as WebGL2RenderingContext;
44
+ if (gl[vcsCleared] !== event.frameState?.time) {
45
+ gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
46
+ }
47
+ gl.enable(gl.SCISSOR_TEST);
48
+
49
+ const mapSize = this.map.olMap?.getSize();
50
+ if (!mapSize) {
51
+ throw new Error('Map size is not available for scissor test');
52
+ }
53
+
54
+ const bottomLeft = getRenderPixel(event, [0, mapSize[1]]);
55
+ const topRight = getRenderPixel(event, [mapSize[0], 0]);
56
+
57
+ const width = Math.round(
58
+ (topRight[0] - bottomLeft[0]) * this.map.splitPosition,
59
+ );
60
+ const height = topRight[1] - bottomLeft[1];
61
+ if (this.splitDirection === SplitDirection.LEFT) {
62
+ gl.scissor(bottomLeft[0], bottomLeft[1], width, height);
63
+ } else {
64
+ gl.scissor(bottomLeft[0] + width, bottomLeft[1], topRight[0], height);
65
+ }
66
+ }
67
+
68
+ // eslint-disable-next-line class-methods-use-this
69
+ protected override _splitPostReder(event: RenderEvent): void {
70
+ const gl = event.context as WebGL2RenderingContext;
71
+ gl.disable(gl.SCISSOR_TEST);
72
+ }
73
+ }
74
+
75
+ export default COGOpenlayersImpl;
@@ -88,16 +88,14 @@ class LayerOpenlayersImpl extends LayerImplementation<OpenlayersMap> {
88
88
  this.olLayer!.on('prerender', this._splitPreRender.bind(this)),
89
89
  );
90
90
  this._splitDirectionRenderListeners.push(
91
- this.olLayer!.on('postrender', (event) => {
92
- (event.context as CanvasRenderingContext2D).restore();
93
- }),
91
+ this.olLayer!.on('postrender', this._splitPostReder.bind(this)),
94
92
  );
95
93
  this.olLayer!.changed();
96
94
  }
97
95
  }
98
96
  }
99
97
 
100
- private _splitPreRender(event: RenderEvent): void {
98
+ protected _splitPreRender(event: RenderEvent): void {
101
99
  const context = event.context as CanvasRenderingContext2D;
102
100
  const width = context.canvas.width * this.map.splitPosition;
103
101
  context.save();
@@ -116,6 +114,11 @@ class LayerOpenlayersImpl extends LayerImplementation<OpenlayersMap> {
116
114
  context.clip();
117
115
  }
118
116
 
117
+ // eslint-disable-next-line class-methods-use-this
118
+ protected _splitPostReder(event: RenderEvent): void {
119
+ (event.context as CanvasRenderingContext2D).restore();
120
+ }
121
+
119
122
  destroy(): void {
120
123
  if (this.olLayer) {
121
124
  this.map.removeOLLayer(this.olLayer);
@@ -15,7 +15,6 @@ import { layerClassRegistry } from '../classRegistry.js';
15
15
  import FlatGeobufTileProvider from './tileProvider/flatGeobufTileProvider.js';
16
16
  import { mercatorProjection, wgs84Projection } from '../util/projection.js';
17
17
  import { panoramaFeature } from './vectorSymbols.js';
18
- import { PanoramaImageCache } from '../panorama/panoramaImageCache.js';
19
18
  import {
20
19
  createPanoramaImageFromURL,
21
20
  type PanoramaImage,
@@ -63,8 +62,6 @@ export default class PanoramaDatasetLayer extends VectorTileLayer<PanoramaDatase
63
62
 
64
63
  private _panoramaVectorProperties = new VectorProperties({});
65
64
 
66
- private _cache = new PanoramaImageCache();
67
-
68
65
  private _dataExtent?: Extent;
69
66
 
70
67
  readonly baseUrl: string;
@@ -178,15 +175,7 @@ export default class PanoramaDatasetLayer extends VectorTileLayer<PanoramaDatase
178
175
  * @returns
179
176
  */
180
177
  createPanoramaImage(name: string): Promise<PanoramaImage> {
181
- const imageUrl = `${this.baseUrl}/${name}_rgb.tif`;
182
- if (this._cache.containsKey(imageUrl)) {
183
- return this._cache.get(imageUrl);
184
- }
185
- const imagePromise = createPanoramaImageFromURL(imageUrl, this);
186
- this._cache.set(imageUrl, imagePromise);
187
- this._cache.expireCache();
188
-
189
- return imagePromise;
178
+ return createPanoramaImageFromURL(`${this.baseUrl}/${name}_rgb.tif`, this);
190
179
  }
191
180
 
192
181
  override getZoomToExtent(): Extent | null {
@@ -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
  }
@@ -317,8 +320,10 @@ export default class PanoramaMap extends VcsMap {
317
320
  * @param image
318
321
  */
319
322
  setCurrentImage(image?: PanoramaImage): void {
320
- if (this._currentImage !== image) {
323
+ if (!this._currentImage?.equals(image)) {
324
+ const currentImage = this._currentImage;
321
325
  this._currentImage = image;
326
+ currentImage?.destroy();
322
327
  this.currentImageChanged.raiseEvent(image);
323
328
  }
324
329
  }
@@ -104,6 +104,13 @@ export type PanoramaImage = Readonly<
104
104
  coordinate: [number, number],
105
105
  result?: Cartesian3,
106
106
  ): Promise<Cartesian3 | undefined>;
107
+ /**
108
+ * Checks if this panorama image is equal to another panorama image
109
+ * by comparing name and baseUrl of dataset.
110
+ * @param other - the other image to compare with
111
+ * @returns true if the images represent the same panorama data
112
+ */
113
+ equals(other?: PanoramaImage): boolean;
107
114
  destroy(): void;
108
115
  };
109
116
 
@@ -510,6 +517,13 @@ export async function createPanoramaImage(
510
517
  ): Promise<Cartesian3 | undefined> {
511
518
  return positionAtDepth(imageCoordinate, true, result);
512
519
  },
520
+ equals(other?: PanoramaImage): boolean {
521
+ return (
522
+ other != null &&
523
+ nameOrId === other.name &&
524
+ dataset?.baseUrl === other.dataset?.baseUrl
525
+ );
526
+ },
513
527
  destroy(): void {
514
528
  tileProvider.destroy();
515
529
  },
@@ -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();
@@ -323,31 +319,26 @@ function createImageWrapper(
323
319
  };
324
320
  }
325
321
 
326
- let emptyImageOverlay: HTMLDivElement | undefined;
327
- function getEmptyImageBitmap(): HTMLDivElement {
328
- if (!emptyImageOverlay) {
329
- const overlay = document.createElement('div');
330
- overlay.style.position = 'absolute';
331
- overlay.style.display = 'block';
332
- overlay.style.top = '0px';
333
- overlay.style.left = '0px';
334
- overlay.style.bottom = '0px';
335
- overlay.style.right = '0px';
336
- overlay.style.backgroundColor = '#409D76';
337
- overlay.style.padding = '8px';
338
- overlay.style.font = 'bold 64px Monospace, Courier New';
339
- overlay.style.textAlign = 'center';
340
- overlay.style.alignContent = 'center';
341
- overlay.style.color = '#424242';
342
- overlay.innerText = 'No Image';
343
-
344
- emptyImageOverlay = overlay;
345
- }
346
- return emptyImageOverlay;
322
+ function createEmptyImageBitmap(): HTMLDivElement {
323
+ const overlay = document.createElement('div');
324
+ overlay.style.position = 'absolute';
325
+ overlay.style.display = 'block';
326
+ overlay.style.top = '0px';
327
+ overlay.style.left = '0px';
328
+ overlay.style.bottom = '0px';
329
+ overlay.style.right = '0px';
330
+ overlay.style.backgroundColor = '#409D76';
331
+ overlay.style.padding = '8px';
332
+ overlay.style.font = 'bold 64px Monospace, Courier New';
333
+ overlay.style.textAlign = 'center';
334
+ overlay.style.alignContent = 'center';
335
+ overlay.style.color = '#424242';
336
+ overlay.innerText = 'No Image';
337
+ return overlay;
347
338
  }
348
339
 
349
340
  function setupEmptyImageOverlay(container: HTMLElement): () => void {
350
- const overlay = getEmptyImageBitmap();
341
+ const overlay = createEmptyImageBitmap();
351
342
  container.appendChild(overlay);
352
343
 
353
344
  let remover: (() => void) | undefined = () => {
@@ -400,6 +391,7 @@ export function createPanoramaImageView(map: PanoramaMap): PanoramaImageView {
400
391
  primitiveCollection,
401
392
  scene.camera,
402
393
  scene.canvas,
394
+ map,
403
395
  );
404
396
  currentView.suspendTileLoading = suspendTileLoading;
405
397
  } 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