@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
@@ -0,0 +1,389 @@
1
+ import type { Projection } from 'ol/proj.js';
2
+ import type GeoTIFFSource from 'ol/source/GeoTIFF.js';
3
+ import {
4
+ createEmpty as createEmptyExtent,
5
+ extend as extendExtent,
6
+ getWidth as getExtentWidth,
7
+ getHeight as getExtentHeight,
8
+ getTopLeft as getTopLeftExtent,
9
+ } from 'ol/extent.js';
10
+ import TileState from 'ol/TileState.js';
11
+ import {
12
+ Cartesian2,
13
+ Event as CesiumEvent,
14
+ GeographicTilingScheme,
15
+ type ImageryTypes,
16
+ Math as CesiumMath,
17
+ Rectangle,
18
+ type TilingScheme,
19
+ WebMercatorTilingScheme,
20
+ } from '@vcmap-cesium/engine';
21
+ import EventType from 'ol/events/EventType.js';
22
+ import type TileGrid from 'ol/tilegrid/TileGrid.js';
23
+ import {
24
+ mercatorExtentToRectangle,
25
+ rectangleToMercatorExtent,
26
+ } from '../../util/math.js';
27
+
28
+ export function createEmptyCanvas(
29
+ width: number,
30
+ height: number,
31
+ ): HTMLCanvasElement {
32
+ const canvas = document.createElement('canvas');
33
+ canvas.width = width;
34
+ canvas.height = height;
35
+ return canvas;
36
+ }
37
+
38
+ function areGridsAligned(
39
+ tileGrid: TileGrid,
40
+ tilingScheme: TilingScheme,
41
+ ): boolean {
42
+ const olRectangle = mercatorExtentToRectangle(
43
+ tileGrid.getTileCoordExtent([0, 0, 0]),
44
+ );
45
+ const cesiumRectangle = tilingScheme.tileXYToRectangle(0, 0, 0);
46
+
47
+ return Rectangle.equalsEpsilon(
48
+ cesiumRectangle,
49
+ olRectangle,
50
+ CesiumMath.EPSILON8,
51
+ );
52
+ }
53
+
54
+ function getTilingSchemeFromSource(source: GeoTIFFSource): TilingScheme {
55
+ const tileGrid = source.getTileGrid()!;
56
+ const projection = source.getProjection()!;
57
+ const extent = tileGrid.getExtent();
58
+ const level0TileRange = tileGrid.getTileRangeForExtentAndZ(extent, 0);
59
+
60
+ let tilingScheme: TilingScheme | undefined;
61
+
62
+ if (projection.getCode() === 'EPSG:4326') {
63
+ tilingScheme = new GeographicTilingScheme({
64
+ numberOfLevelZeroTilesX: level0TileRange.getWidth(),
65
+ numberOfLevelZeroTilesY: level0TileRange.getHeight(),
66
+ rectangle: mercatorExtentToRectangle(extent),
67
+ });
68
+ } else if (projection.getCode() === 'EPSG:3857') {
69
+ tilingScheme = new WebMercatorTilingScheme({
70
+ numberOfLevelZeroTilesX: level0TileRange.getWidth(),
71
+ numberOfLevelZeroTilesY: level0TileRange.getHeight(),
72
+ rectangleSouthwestInMeters: new Cartesian2(extent[0], extent[1]),
73
+ rectangleNortheastInMeters: new Cartesian2(extent[2], extent[3]),
74
+ });
75
+ }
76
+
77
+ if (!tilingScheme) {
78
+ throw new Error(`Unexpected code projection: ${projection.getCode()}`);
79
+ }
80
+ return tilingScheme;
81
+ }
82
+
83
+ export default class COGImageryProvider {
84
+ private _emptyCanvas: HTMLCanvasElement;
85
+
86
+ // eslint-disable-next-line @typescript-eslint/naming-convention
87
+ _reload: undefined | (() => void) = undefined;
88
+
89
+ private _projection: Projection;
90
+
91
+ private _tilingScheme: TilingScheme;
92
+
93
+ private _tileGrid: TileGrid;
94
+
95
+ private _boundTileLoader: (
96
+ x: number,
97
+ y: number,
98
+ level: number,
99
+ ) => Promise<ImageryTypes>;
100
+
101
+ constructor(private _source: GeoTIFFSource) {
102
+ this._emptyCanvas = createEmptyCanvas(this.tileWidth, this.tileHeight);
103
+ this._projection = this._source.getProjection()!;
104
+ this._tileGrid = this._source.getTileGrid()!;
105
+ this._tilingScheme = getTilingSchemeFromSource(this._source);
106
+ if (areGridsAligned(this._tileGrid, this._tilingScheme)) {
107
+ this._boundTileLoader = this._loadAlignedTile.bind(this);
108
+ } else {
109
+ this._boundTileLoader = this._loadUnalignedTile.bind(this);
110
+ }
111
+ }
112
+
113
+ // eslint-disable-next-line class-methods-use-this,@typescript-eslint/naming-convention
114
+ get _ready(): boolean {
115
+ return true;
116
+ }
117
+
118
+ // eslint-disable-next-line class-methods-use-this
119
+ get ready(): boolean {
120
+ return true;
121
+ }
122
+
123
+ get rectangle(): Rectangle {
124
+ return this._tilingScheme.rectangle;
125
+ }
126
+
127
+ get tilingScheme(): TilingScheme {
128
+ return this._tilingScheme;
129
+ }
130
+
131
+ readonly errorEvent: CesiumEvent = new CesiumEvent();
132
+
133
+ // eslint-disable-next-line class-methods-use-this
134
+ get credit(): undefined {
135
+ return undefined;
136
+ }
137
+
138
+ // eslint-disable-next-line class-methods-use-this
139
+ get proxy(): undefined {
140
+ return undefined;
141
+ }
142
+
143
+ get tileWidth(): number {
144
+ const tileGrid = this._source.getTileGrid();
145
+ if (tileGrid) {
146
+ const tileSizeAtZoom0 = tileGrid.getTileSize(0);
147
+ if (Array.isArray(tileSizeAtZoom0)) {
148
+ return Math.round(tileSizeAtZoom0[0]);
149
+ } else {
150
+ return Math.round(tileSizeAtZoom0); // same width and height
151
+ }
152
+ }
153
+ return 256;
154
+ }
155
+
156
+ get tileHeight(): number {
157
+ const tileGrid = this._source.getTileGrid();
158
+ if (tileGrid) {
159
+ const tileSizeAtZoom0 = tileGrid.getTileSize(0);
160
+ if (Array.isArray(tileSizeAtZoom0)) {
161
+ return Math.round(tileSizeAtZoom0[1]);
162
+ } else {
163
+ return Math.round(tileSizeAtZoom0); // same width and height
164
+ }
165
+ }
166
+ return 256;
167
+ }
168
+
169
+ get maximumLevel(): number {
170
+ const tileGrid = this._source.getTileGrid();
171
+ if (tileGrid) {
172
+ return tileGrid.getMaxZoom();
173
+ } else {
174
+ return 18; // some arbitrary value
175
+ }
176
+ }
177
+
178
+ // eslint-disable-next-line class-methods-use-this
179
+ get minimumLevel(): number {
180
+ // WARNING: Do not use the minimum level (at least until the extent is
181
+ // properly set). Cesium assumes the minimumLevel to contain only
182
+ // a few tiles and tries to load them all at once -- this can
183
+ // freeze and/or crash the browser !
184
+ return 0;
185
+ //var tg = this._source.getTileGrid();
186
+ //return tg ? tg.getMinZoom() : 0;
187
+ }
188
+
189
+ // eslint-disable-next-line class-methods-use-this
190
+ get tileDiscardPolicy(): undefined {
191
+ return undefined;
192
+ }
193
+
194
+ // eslint-disable-next-line class-methods-use-this
195
+ get hasAlphaChannel(): boolean {
196
+ return true;
197
+ }
198
+
199
+ private async _loadOLTile(
200
+ x: number,
201
+ y: number,
202
+ level: number,
203
+ ): Promise<Uint8Array | undefined> {
204
+ const tile = this._source.getTile(level, x, y, 1, this._projection);
205
+ if (tile) {
206
+ return new Promise<Uint8Array | undefined>((resolve) => {
207
+ const listener = (): void => {
208
+ const data = tile.getData() as Uint8Array | null;
209
+ if (data) {
210
+ tile.removeEventListener(EventType.CHANGE, listener);
211
+ resolve(data);
212
+ } else if (
213
+ tile.getState() === TileState.EMPTY ||
214
+ tile.getState() === TileState.ERROR
215
+ ) {
216
+ tile.removeEventListener(EventType.CHANGE, listener);
217
+ resolve(undefined);
218
+ }
219
+ };
220
+ if (tile.getState() === TileState.LOADED) {
221
+ listener();
222
+ } else if (
223
+ tile.getState() === TileState.EMPTY ||
224
+ tile.getState() === TileState.ERROR
225
+ ) {
226
+ resolve(undefined);
227
+ } else {
228
+ tile.addEventListener(EventType.CHANGE, listener);
229
+ if (tile.getState() === TileState.IDLE) {
230
+ tile.load();
231
+ }
232
+ }
233
+ });
234
+ }
235
+ return Promise.resolve(undefined);
236
+ }
237
+
238
+ private _drawData(
239
+ ctx: CanvasRenderingContext2D,
240
+ data: Uint8Array,
241
+ offsetX = 0,
242
+ offsetY = 0,
243
+ ): void {
244
+ const imageData = ctx.createImageData(this.tileWidth, this.tileHeight);
245
+ let usedData = data;
246
+ // this is a grey image
247
+ if (data.length === imageData.data.length / 2) {
248
+ usedData = new Uint8Array(imageData.data.length);
249
+ for (let i = 0; i < data.length; i++) {
250
+ const value = data[i];
251
+ if (i % 2 === 0) {
252
+ const pixelOffset = (i / 2) * 4;
253
+ usedData[pixelOffset] = value;
254
+ usedData[pixelOffset + 1] = value;
255
+ usedData[pixelOffset + 2] = value;
256
+ } else {
257
+ const pixelOffset = ((i - 1) / 2) * 4;
258
+ usedData[pixelOffset + 3] = value;
259
+ }
260
+ }
261
+ }
262
+
263
+ imageData.data.set(usedData);
264
+ ctx.putImageData(imageData, offsetX, offsetY);
265
+ }
266
+
267
+ private async _loadAlignedTile(
268
+ x: number,
269
+ y: number,
270
+ level: number,
271
+ ): Promise<ImageryTypes> {
272
+ const tileData = await this._loadOLTile(x, y, level);
273
+ if (tileData) {
274
+ const canvas = createEmptyCanvas(this.tileWidth, this.tileHeight);
275
+ const ctx = canvas.getContext('2d');
276
+ if (ctx) {
277
+ this._drawData(ctx, tileData);
278
+ }
279
+ return canvas;
280
+ }
281
+
282
+ return this._emptyCanvas;
283
+ }
284
+
285
+ private async _loadUnalignedTile(
286
+ x: number,
287
+ y: number,
288
+ level: number,
289
+ ): Promise<ImageryTypes> {
290
+ const rectangle = this._tilingScheme.tileXYToRectangle(x, y, level);
291
+ const extent = rectangleToMercatorExtent(rectangle);
292
+ const resolution = Math.max(
293
+ getExtentWidth(extent) / this.tileWidth,
294
+ getExtentHeight(extent) / this.tileHeight,
295
+ );
296
+ const levelResolution = this._tileGrid.getZForResolution(resolution);
297
+ const tileRange = this._tileGrid.getTileRangeForExtentAndZ(
298
+ extent,
299
+ levelResolution,
300
+ );
301
+ const canvas = createEmptyCanvas(
302
+ this.tileWidth * tileRange.getWidth(),
303
+ this.tileHeight * tileRange.getHeight(),
304
+ );
305
+ const ctx = canvas.getContext('2d');
306
+ if (!ctx) {
307
+ return this._emptyCanvas;
308
+ }
309
+
310
+ const promises: Promise<void>[] = [];
311
+ const tileRangeExtent = createEmptyExtent();
312
+ for (
313
+ let partialX = tileRange.minX;
314
+ partialX <= tileRange.maxX;
315
+ partialX++
316
+ ) {
317
+ for (
318
+ let partialY = tileRange.minY;
319
+ partialY <= tileRange.maxY;
320
+ partialY++
321
+ ) {
322
+ extendExtent(
323
+ tileRangeExtent,
324
+ this._tileGrid.getTileCoordExtent([
325
+ levelResolution,
326
+ partialX,
327
+ partialY,
328
+ ]),
329
+ );
330
+ promises.push(
331
+ this._loadOLTile(partialX, partialY, levelResolution).then(
332
+ (tileData) => {
333
+ if (tileData) {
334
+ this._drawData(
335
+ ctx,
336
+ tileData,
337
+ (partialX - tileRange.minX) * this.tileWidth,
338
+ (partialY - tileRange.minY) * this.tileHeight,
339
+ );
340
+ }
341
+ },
342
+ ),
343
+ );
344
+ }
345
+ }
346
+ await Promise.all(promises);
347
+
348
+ const unitsPerPixelX =
349
+ getExtentWidth(tileRangeExtent) / (this.tileWidth * tileRange.getWidth());
350
+ const unitsPerPixelY =
351
+ getExtentHeight(tileRangeExtent) /
352
+ (this.tileHeight * tileRange.getHeight());
353
+
354
+ const tileRangeTopLeft = getTopLeftExtent(tileRangeExtent);
355
+ const extentTopLeft = getTopLeftExtent(extent);
356
+
357
+ const windowX =
358
+ Math.abs(tileRangeTopLeft[0] - extentTopLeft[0]) / unitsPerPixelX;
359
+ const windowY =
360
+ Math.abs(tileRangeTopLeft[1] - extentTopLeft[1]) / unitsPerPixelY;
361
+ const windowWidth = getExtentWidth(extent) / unitsPerPixelX;
362
+ const windowHeight = getExtentHeight(extent) / unitsPerPixelY;
363
+
364
+ const windowCanvas = createEmptyCanvas(this.tileWidth, this.tileHeight);
365
+ const windowCtx = windowCanvas.getContext('2d');
366
+
367
+ if (windowCtx) {
368
+ windowCtx.drawImage(
369
+ canvas,
370
+ windowX,
371
+ windowY,
372
+ windowWidth,
373
+ windowHeight,
374
+ 0,
375
+ 0,
376
+ this.tileWidth,
377
+ this.tileHeight,
378
+ );
379
+
380
+ return windowCanvas;
381
+ }
382
+
383
+ return this._emptyCanvas;
384
+ }
385
+
386
+ requestImage(x: number, y: number, level: number): Promise<ImageryTypes> {
387
+ return this._boundTileLoader(x, y, level);
388
+ }
389
+ }
@@ -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 {