@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
|
@@ -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', (
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|