@gisatcz/deckgl-geolib 1.12.0-dev.5 → 2.1.0-dev.0

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 (48) hide show
  1. package/README.md +69 -64
  2. package/dist/cjs/index.js +5252 -4865
  3. package/dist/cjs/index.js.map +1 -1
  4. package/dist/cjs/index.min.js +3 -3
  5. package/dist/cjs/index.min.js.map +1 -1
  6. package/dist/{esm/types/cogtiles/cogtiles.d.ts → cjs/types/core/CogTiles.d.ts} +17 -47
  7. package/dist/cjs/types/{geoimage/geoimage.d.ts → core/GeoImage.d.ts} +7 -6
  8. package/dist/cjs/types/core/index.d.ts +3 -0
  9. package/dist/cjs/types/index.d.ts +3 -11
  10. package/dist/{esm/types/cogbitmaplayer → cjs/types/layers}/CogBitmapLayer.d.ts +6 -5
  11. package/dist/cjs/types/{cogterrainlayer → layers}/CogTerrainLayer.d.ts +15 -8
  12. package/dist/cjs/types/layers/index.d.ts +2 -0
  13. package/dist/esm/index.js +5248 -4864
  14. package/dist/esm/index.js.map +1 -1
  15. package/dist/esm/index.min.js +3 -3
  16. package/dist/esm/index.min.js.map +1 -1
  17. package/dist/{cjs/types/cogtiles/cogtiles.d.ts → esm/types/core/CogTiles.d.ts} +17 -47
  18. package/dist/esm/types/{geoimage/geoimage.d.ts → core/GeoImage.d.ts} +7 -6
  19. package/dist/esm/types/core/index.d.ts +3 -0
  20. package/dist/esm/types/index.d.ts +3 -11
  21. package/dist/{cjs/types/cogbitmaplayer → esm/types/layers}/CogBitmapLayer.d.ts +6 -5
  22. package/dist/esm/types/{cogterrainlayer → layers}/CogTerrainLayer.d.ts +15 -8
  23. package/dist/esm/types/layers/index.d.ts +2 -0
  24. package/package.json +67 -26
  25. package/.eslintignore +0 -2
  26. package/.eslintrc.cjs +0 -3
  27. package/CHANGELOG.md +0 -166
  28. package/rollup.config.mjs +0 -77
  29. package/src/cogbitmaplayer/CogBitmapLayer.ts +0 -337
  30. package/src/cogbitmaplayer/README.md +0 -113
  31. package/src/cogterrainlayer/CogTerrainLayer.ts +0 -445
  32. package/src/cogterrainlayer/README.md +0 -118
  33. package/src/cogtiles/README.md +0 -72
  34. package/src/cogtiles/cogtiles.ts +0 -483
  35. package/src/cogtiles/lzw.js +0 -256
  36. package/src/geoimage/README.md +0 -129
  37. package/src/geoimage/delatin/index.ts +0 -495
  38. package/src/geoimage/geoimage.ts +0 -602
  39. package/src/geoimage/helpers/skirt.ts +0 -171
  40. package/src/index.ts +0 -11
  41. package/src/utilities/tileurls.ts +0 -21
  42. package/tsconfig.json +0 -6
  43. /package/dist/cjs/types/{geoimage → core}/delatin/index.d.ts +0 -0
  44. /package/dist/cjs/types/{geoimage → core}/helpers/skirt.d.ts +0 -0
  45. /package/dist/cjs/types/{utilities → utils}/tileurls.d.ts +0 -0
  46. /package/dist/esm/types/{geoimage → core}/delatin/index.d.ts +0 -0
  47. /package/dist/esm/types/{geoimage → core}/helpers/skirt.d.ts +0 -0
  48. /package/dist/esm/types/{utilities → utils}/tileurls.d.ts +0 -0
@@ -1,483 +0,0 @@
1
- /* eslint 'max-len': [1, { code: 100, comments: 999, ignoreStrings: true, ignoreUrls: true }] */
2
- // COG loading
3
- import { fromUrl, GeoTIFF, GeoTIFFImage } from 'geotiff';
4
-
5
- // Image compression support
6
- import { worldToLngLat } from '@math.gl/web-mercator';
7
-
8
- // Bitmap styling
9
- import GeoImage, { GeoImageOptions } from '../geoimage/geoimage.ts';
10
-
11
- export type Bounds = [minX: number, minY: number, maxX: number, maxY: number];
12
-
13
- const EARTH_CIRCUMFERENCE = 2 * Math.PI * 6378137;
14
- const EARTH_HALF_CIRCUMFERENCE = EARTH_CIRCUMFERENCE / 2;
15
- const webMercatorOrigin = [-20037508.342789244, 20037508.342789244];
16
- const webMercatorRes0 = 156543.03125;
17
-
18
- const CogTilesGeoImageOptionsDefaults = {
19
- blurredTexture: true,
20
- };
21
-
22
- class CogTiles {
23
- cog: GeoTIFF;
24
-
25
- cogZoomLookup = [0];
26
-
27
- cogResolutionLookup = [0];
28
-
29
- cogOrigin = [0, 0];
30
-
31
- zoomRange = [0, 0];
32
-
33
- tileSize: number;
34
-
35
- bounds: Bounds;
36
-
37
- loaded: boolean = false;
38
-
39
- geo: GeoImage = new GeoImage();
40
-
41
- options: GeoImageOptions;
42
-
43
- constructor(options: GeoImageOptions) {
44
- this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
45
- }
46
-
47
- async initializeCog(url: string) {
48
- this.cog = await fromUrl(url);
49
- const image = await this.cog.getImage(); // by default, the first image is read.
50
- this.cogOrigin = image.getOrigin();
51
- this.options.noDataValue ??= this.getNoDataValue(image);
52
- this.options.format ??= this.getDataTypeFromTags(image);
53
- this.options.numOfChannels = this.getNumberOfChannels(image);
54
- this.options.planarConfig = this.getPlanarConfiguration(image);
55
- [this.cogZoomLookup, this.cogResolutionLookup] = await this.buildCogZoomResolutionLookup(this.cog);
56
- this.tileSize = image.getTileWidth();
57
- this.zoomRange = this.calculateZoomRange(image, await this.cog.getImageCount());
58
- this.bounds = this.calculateBoundsAsLatLon(image);
59
- }
60
-
61
- getZoomRange() {
62
- return this.zoomRange;
63
- }
64
-
65
- calculateZoomRange(img: GeoTIFFImage, imgCount: number) {
66
- const maxZoom = this.getZoomLevelFromResolution(img.getTileWidth(), img.getResolution()[0]);
67
- const minZoom = maxZoom - (imgCount - 1);
68
-
69
- return [minZoom, maxZoom];
70
- }
71
-
72
- calculateBoundsAsLatLon(image: GeoTIFFImage) {
73
- const bbox = image.getBoundingBox();
74
-
75
- const minX = Math.min(bbox[0], bbox[2]);
76
- const maxX = Math.max(bbox[0], bbox[2]);
77
- const minY = Math.min(bbox[1], bbox[3]);
78
- const maxY = Math.max(bbox[1], bbox[3]);
79
-
80
- const minXYDeg = this.getLatLon([minX, minY]);
81
- const maxXYDeg = this.getLatLon([maxX, maxY]);
82
-
83
- return [minXYDeg[0], minXYDeg[1], maxXYDeg[0], maxXYDeg[1]] as [number, number, number, number];
84
- }
85
-
86
- getZoomLevelFromResolution(tileSize: number, resolution: number) {
87
- return Math.round(Math.log2(EARTH_CIRCUMFERENCE / (resolution * tileSize)));
88
- }
89
-
90
- getBoundsAsLatLon() {
91
- return this.bounds;
92
- }
93
-
94
- getLatLon(input: number[]) {
95
- const ax = EARTH_HALF_CIRCUMFERENCE + input[0];
96
- const ay = -(EARTH_HALF_CIRCUMFERENCE + (input[1] - EARTH_CIRCUMFERENCE));
97
-
98
- const cartesianPosition = [
99
- ax * (512 / EARTH_CIRCUMFERENCE),
100
- ay * (512 / EARTH_CIRCUMFERENCE),
101
- ];
102
- const cartographicPosition = worldToLngLat(cartesianPosition);
103
- const cartographicPositionAdjusted = [cartographicPosition[0], -cartographicPosition[1]];
104
-
105
- return cartographicPositionAdjusted;
106
- }
107
-
108
- /**
109
- * Builds lookup tables for zoom levels and estimated resolutions from a Cloud Optimized GeoTIFF (COG) object.
110
- *
111
- * It is assumed that inn web mapping, COG data is visualized in the Web Mercator coordinate system.
112
- * At zoom level 0, the Web Mercator resolution is defined by the constant `webMercatorRes0`
113
- * (e.g., 156543.03125 m/pixel). At each subsequent zoom level, this resolution is halved.
114
- *
115
- * This function calculates, for each image (overview) in the COG, its estimated resolution and
116
- * corresponding zoom level based on the base image's resolution and width.
117
- *
118
- * @param {object} cog - A Cloud Optimized GeoTIFF object loaded via geotiff.js.
119
- * @returns {Promise<[number[], number[]]>} A promise resolving to a tuple of two arrays:
120
- * - The first array (`zoomLookup`) maps each image index to its computed zoom level.
121
- * - The second array (`resolutionLookup`) maps each image index to its estimated resolution (m/pixel).
122
- */
123
- async buildCogZoomResolutionLookup(cog) {
124
- // Retrieve the total number of images (overviews) in the COG.
125
- const imageCount = await cog.getImageCount();
126
-
127
- // Use the first image as the base reference.
128
- const baseImage = await cog.getImage(0);
129
- const baseResolution = baseImage.getResolution()[0]; // Resolution (m/pixel) of the base image.
130
- const baseWidth = baseImage.getWidth();
131
-
132
- // Initialize arrays to store the zoom level and resolution for each image.
133
- const zoomLookup = [];
134
- const resolutionLookup = [];
135
-
136
- // Iterate over each image (overview) in the COG.
137
- for (let idx = 0; idx < imageCount; idx++) {
138
- const image = await cog.getImage(idx);
139
- const width = image.getWidth();
140
-
141
- // Calculate the scale factor relative to the base image.
142
- const scaleFactor = baseWidth / width;
143
- const estimatedResolution = baseResolution * scaleFactor;
144
-
145
- // Calculate the zoom level using the Web Mercator resolution standard:
146
- // webMercatorRes0 is the resolution at zoom level 0; each zoom level halves the resolution.
147
- const zoomLevel = Math.round(Math.log2(webMercatorRes0 / estimatedResolution));
148
- // console.log(`buildCogZoomResolutionLookup: Image index ${idx}: Estimated Resolution = ${estimatedResolution} m/pixel, Zoom Level = ${zoomLevel}`);
149
-
150
- zoomLookup[idx] = zoomLevel;
151
- resolutionLookup[idx] = estimatedResolution;
152
- }
153
-
154
- return [zoomLookup, resolutionLookup];
155
- }
156
-
157
- /**
158
- * Determines the appropriate image index from the Cloud Optimized GeoTIFF (COG)
159
- * that best matches a given zoom level.
160
- *
161
- * This function utilizes precomputed lookup tables (`cogZoomLookup`) that map
162
- * each image index in the COG to its corresponding zoom level. It ensures that
163
- * the selected image index provides the closest resolution to the desired zoom level.
164
- *
165
- * @param {number} zoom - The target zoom level for which the image index is sought.
166
- * @returns {number} The index of the image in the COG that best matches the specified zoom level.
167
- */
168
- getImageIndexForZoomLevel(zoom) {
169
- // Retrieve the minimum and maximum zoom levels from the lookup table.
170
- const minZoom = this.cogZoomLookup[this.cogZoomLookup.length - 1];
171
- const maxZoom = this.cogZoomLookup[0];
172
- if (zoom > maxZoom) return 0;
173
- if (zoom < minZoom) return this.cogZoomLookup.length - 1;
174
-
175
- // For zoom levels within the available range, find the exact or closest matching index.
176
- const exactMatchIndex = this.cogZoomLookup.indexOf(zoom);
177
- if (exactMatchIndex === -1) {
178
- // TO DO improve the condition if the match index is not found
179
- console.log('getImageIndexForZoomLevel: error in retrieving image by zoom index');
180
- }
181
- return exactMatchIndex;
182
- }
183
-
184
- async getTileFromImage(tileX, tileY, zoom) {
185
- const imageIndex = this.getImageIndexForZoomLevel(zoom);
186
- const targetImage = await this.cog.getImage(imageIndex);
187
-
188
- // Ensure the image is tiled
189
- const tileWidth = targetImage.getTileWidth();
190
- const tileHeight = targetImage.getTileHeight();
191
- if (!tileWidth || !tileHeight) {
192
- throw new Error('The image is not tiled.');
193
- }
194
-
195
- // Calculate the map offset between the global Web Mercator origin and the COG's origin.
196
- // (Difference in map units.)
197
- // if X offset is large and positive (COG is far to the right of global origin)
198
- // if Y offset is large and positive (COG is far below global origin — expected)
199
- const offsetXMap = this.cogOrigin[0] - webMercatorOrigin[0];
200
- const offsetYMap = webMercatorOrigin[1] - this.cogOrigin[1];
201
-
202
- const tileResolution = (EARTH_CIRCUMFERENCE / tileWidth) / 2 ** zoom;
203
- const cogResolution = this.cogResolutionLookup[imageIndex];
204
-
205
- // Convert map offsets into pixel offsets.
206
- const offsetXPixel = Math.floor(offsetXMap / tileResolution);
207
- const offsetYPixel = Math.floor(offsetYMap / tileResolution);
208
-
209
- const imageHeight = targetImage.getHeight();
210
- const imageWidth = targetImage.getWidth();
211
-
212
- // approach by comparing bboxes of tile and cog image
213
- const tilePixelBbox = [
214
- tileX * tileWidth,
215
- tileY * tileHeight,
216
- (tileX + 1) * tileWidth,
217
- (tileY + 1) * tileHeight,
218
- ];
219
-
220
- const cogPixelBBox = [
221
- offsetXPixel,
222
- offsetYPixel,
223
- offsetXPixel + imageWidth,
224
- offsetYPixel + imageHeight,
225
- ];
226
-
227
- const intersecion = this.getIntersectionBBox(tilePixelBbox, cogPixelBBox, offsetXPixel, offsetYPixel, tileWidth);
228
- const [validWidth, validHeight, window, missingLeft, missingTop] = intersecion;
229
-
230
-
231
-
232
- // Read the raster data for the tile window with shifted origin.
233
- if (missingLeft > 0 || missingTop > 0 || validWidth < tileWidth || validHeight < tileHeight) {
234
- // Prepare the final tile buffer and fill it with noDataValue.
235
- const tileBuffer = this.createTileBuffer(this.options.format, tileWidth);
236
- tileBuffer.fill(this.options.noDataValue);
237
-
238
- // if the valid window is smaller than tile size, it gets the image size width and height, thus validRasterData.width must be used as below
239
- const validRasterData = await targetImage.readRasters({ window });
240
-
241
- // FOR MULTI-BAND - the result is one array with sequentially typed bands, firstly all data for the band 0, then for band 1
242
- // I think this is less practical then the commented solution above, but I do it so it works with the code in geoimage.ts in deck.gl-geoimage in function getColorValue.
243
- const validImageData = Array(validRasterData.length * validRasterData[0].length);
244
- validImageData.fill(this.options.noDataValue);
245
-
246
- // Place the valid pixel data into the tile buffer.
247
- for (let band = 0; band < validRasterData.length; band++) {
248
- for (let row = 0; row < validHeight; row++) {
249
- for (let col = 0; col < validWidth; col++) {
250
- // Compute the destination position in the tile buffer.
251
- // We shift by the number of missing pixels (if any) at the top/left.
252
- const destRow = missingTop + row;
253
- const destCol = missingLeft + col;
254
- if (destRow < tileWidth && destCol < tileHeight) {
255
- tileBuffer[destRow * tileWidth + destCol] = validRasterData[band][row * validRasterData.width + col];
256
- } else {
257
- console.log('error in assigning data to tile buffer');
258
- }
259
- }
260
- }
261
- tileBuffer.forEach((rasterValue, index) => {
262
- validImageData[index * this.options.numOfChannels + band] = rasterValue;
263
- });
264
- }
265
- return [validImageData];
266
- }
267
-
268
- // Read the raster data for the non shifted tile window.
269
- const tileData = await targetImage.readRasters({ window, interleave: true });
270
- // console.log(`data that starts at the left top corner of the tile ${tileX}, ${tileY}`);
271
- return [tileData];
272
- }
273
-
274
- async getTile(x: number, y: number, z: number, bounds:Bounds, meshMaxError: number) {
275
- const tileData = await this.getTileFromImage(x, y, z);
276
-
277
- return this.geo.getMap({
278
- rasters: [tileData[0]],
279
- width: this.tileSize,
280
- height: this.tileSize,
281
- bounds,
282
- }, this.options, meshMaxError);
283
- }
284
-
285
- /**
286
- * Determines the data type (e.g., "Int32", "Float64") of a GeoTIFF image
287
- * by reading its TIFF tags.
288
- *
289
- * @param {GeoTIFFImage} image - A GeoTIFF.js image.
290
- * @returns {Promise<string>} - A string representing the data type.
291
- */
292
- getDataTypeFromTags(image) {
293
- // Retrieve the file directory containing TIFF tags.
294
- const fileDirectory = image.getFileDirectory();
295
-
296
- // In GeoTIFF, BitsPerSample (tag 258) and SampleFormat (tag 339) provide the type info.
297
- // They can be either a single number or an array if there are multiple samples.
298
- const sampleFormat = fileDirectory.SampleFormat; // Tag 339
299
- const bitsPerSample = fileDirectory.BitsPerSample; // Tag 258
300
-
301
- // If multiple bands exist, we assume all bands share the same type.
302
- const format = (sampleFormat && typeof sampleFormat.length === 'number' && sampleFormat.length > 0)
303
- ? sampleFormat[0]
304
- : sampleFormat;
305
-
306
- const bits = (bitsPerSample && typeof bitsPerSample.length === 'number' && bitsPerSample.length > 0)
307
- ? bitsPerSample[0]
308
- : bitsPerSample;
309
-
310
- // Map the sample format to its corresponding type string.
311
- // The common definitions are:
312
- // 1: Unsigned integer
313
- // 2: Signed integer
314
- // 3: Floating point
315
- let typePrefix;
316
- if (format === 1) {
317
- typePrefix = 'UInt';
318
- } else if (format === 2) {
319
- typePrefix = 'Int';
320
- } else if (format === 3) {
321
- typePrefix = 'Float';
322
- } else {
323
- typePrefix = 'Unknown';
324
- }
325
- // console.log(`data type ${typePrefix}${bits}`);
326
- return `${typePrefix}${bits}`;
327
- }
328
-
329
- /**
330
- * Extracts the noData value from a GeoTIFF.js image.
331
- * Returns the noData value as a number (including NaN) if available, otherwise undefined.
332
- *
333
- * @param {GeoTIFFImage} image - The GeoTIFF.js image.
334
- * @returns {number|undefined} The noData value, possibly NaN, or undefined if not set or invalid.
335
- */
336
- getNoDataValue(image) {
337
- const noDataRaw = image.getGDALNoData();
338
-
339
- if (noDataRaw === undefined || noDataRaw === null) {
340
- console.warn('No noData value defined — raster might be rendered incorrectly.');
341
- return undefined;
342
- }
343
-
344
- const cleaned = String(noDataRaw).replace(/\0/g, '').trim();
345
-
346
- if (cleaned === '') {
347
- console.warn('noData value is an empty string after cleanup.');
348
- return undefined;
349
- }
350
-
351
- const parsed = Number(cleaned);
352
-
353
- // Allow NaN if explicitly declared
354
- if (cleaned.toLowerCase() === 'nan') {
355
- return NaN;
356
- }
357
-
358
- // If not declared as "nan" and still parsed to NaN, it's an error
359
- if (Number.isNaN(parsed)) {
360
- console.warn(`Failed to parse numeric noData value: '${cleaned}'`);
361
- return undefined;
362
- }
363
-
364
- return parsed;
365
- }
366
-
367
-
368
- /**
369
- * Retrieves the number of channels (samples per pixel) in a GeoTIFF image.
370
- *
371
- * @param {GeoTIFFImage} image - A GeoTIFFImage object from which to extract the number of channels.
372
- * @returns {number} The number of channels in the image.
373
- */
374
- getNumberOfChannels(image) {
375
- return image.getSamplesPerPixel();
376
- }
377
-
378
-
379
- /**
380
- * Calculates the intersection between a tile bounding box and a COG bounding box,
381
- * returning the intersection window in image pixel space (relative to COG offsets),
382
- * along with how much blank space (nodata) appears on the left and top of the tile.
383
- *
384
- * @param {number[]} tileBbox - Tile bounding box: [minX, minY, maxX, maxY]
385
- * @param {number[]} cogBbox - COG bounding box: [minX, minY, maxX, maxY]
386
- * @param {number} offsetXPixel - X offset of the COG origin in pixel space
387
- * @param {number} offsetYPixel - Y offset of the COG origin in pixel space
388
- * @param {number} tileSize - Size of the tile in pixels (default: 256)
389
- * @returns {[number, number, number[] | null, number, number]}
390
- * An array containing:
391
- * - width of the intersection
392
- * - height of the intersection
393
- * - pixel-space window: [startX, startY, endX, endY] or null if no overlap
394
- * - missingLeft: padding pixels on the left
395
- * - missingTop: padding pixels on the top
396
- */
397
- getIntersectionBBox(tileBbox, cogBbox, offsetXPixel = 0, offsetYPixel = 0, tileSize = 256) {
398
- const interLeft = Math.max(tileBbox[0], cogBbox[0]);
399
- const interTop = Math.max(tileBbox[1], cogBbox[1]);
400
- const interRight = Math.min(tileBbox[2], cogBbox[2]);
401
- const interBottom = Math.min(tileBbox[3], cogBbox[3]);
402
-
403
- const width = Math.max(0, interRight - interLeft);
404
- const height = Math.max(0, interBottom - interTop);
405
-
406
- let window = null;
407
- let missingLeft = 0;
408
- let missingTop = 0;
409
-
410
- if (width > 0 && height > 0) {
411
- window = [
412
- interLeft - offsetXPixel,
413
- interTop - offsetYPixel,
414
- interRight - offsetXPixel,
415
- interBottom - offsetYPixel,
416
- ];
417
-
418
- // Padding from the tile origin to valid data start
419
- missingLeft = interLeft - tileBbox[0];
420
- missingTop = interTop - tileBbox[1];
421
- }
422
-
423
- return [
424
- width,
425
- height,
426
- window,
427
- missingLeft,
428
- missingTop,
429
- ];
430
- }
431
-
432
-
433
-
434
- /**
435
- * Retrieves the PlanarConfiguration value from a GeoTIFF image.
436
- *
437
- * @param {GeoTIFFImage} image - The GeoTIFF image object.
438
- * @returns {number} The PlanarConfiguration value (1 for Chunky format, 2 for Planar format).
439
- */
440
- getPlanarConfiguration(image) {
441
- // Access the PlanarConfiguration tag directly
442
- const planarConfiguration = image.fileDirectory.PlanarConfiguration;
443
-
444
- // If the tag is not present, default to 1 (Chunky format)
445
- if (planarConfiguration !== 1 && planarConfiguration !== 2) {
446
- throw new Error('Invalid planar configuration.');
447
- }
448
- return planarConfiguration;
449
- }
450
-
451
- /**
452
- * Creates a tile buffer of the specified size using a typed array corresponding to the provided data type.
453
- *
454
- * @param {string} dataType - A string specifying the data type (e.g., "Int32", "Float64", "UInt16", etc.).
455
- * @param {number} tileSize - The width/height of the square tile.
456
- * @returns {TypedArray} A typed array buffer of length tileSize * tileSize.
457
- */
458
- createTileBuffer(dataType, tileSize) {
459
- const length = tileSize * tileSize;
460
- switch (dataType) {
461
- case 'UInt8':
462
- return new Uint8Array(length);
463
- case 'Int8':
464
- return new Int8Array(length);
465
- case 'UInt16':
466
- return new Uint16Array(length);
467
- case 'Int16':
468
- return new Int16Array(length);
469
- case 'UInt32':
470
- return new Uint32Array(length);
471
- case 'Int32':
472
- return new Int32Array(length);
473
- case 'Float32':
474
- return new Float32Array(length);
475
- case 'Float64':
476
- return new Float64Array(length);
477
- default:
478
- throw new Error(`Unsupported data type: ${dataType}`);
479
- }
480
- }
481
- }
482
-
483
- export default CogTiles;