@gisatcz/deckgl-geolib 1.12.0-dev.3 → 1.12.0-dev.5

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.
@@ -65,7 +65,7 @@ export default class CogBitmapLayer<ExtraPropsT extends {} = {}> extends Composi
65
65
  getTileData: any;
66
66
  renderSubLayers: any;
67
67
  updateTriggers: {
68
- getTileData: (boolean | GeoImageOptions | ClampToTerrainOptions)[];
68
+ getTileData: (number | boolean | ClampToTerrainOptions)[];
69
69
  };
70
70
  extent: any;
71
71
  tileSize: any;
@@ -83,10 +83,10 @@ declare class CogTiles {
83
83
  getDataTypeFromTags(image: any): string;
84
84
  /**
85
85
  * Extracts the noData value from a GeoTIFF.js image.
86
- * Returns the noData value as a number if available, otherwise undefined.
86
+ * Returns the noData value as a number (including NaN) if available, otherwise undefined.
87
87
  *
88
88
  * @param {GeoTIFFImage} image - The GeoTIFF.js image.
89
- * @returns {number|undefined} The noData value as a number, or undefined if not available.
89
+ * @returns {number|undefined} The noData value, possibly NaN, or undefined if not set or invalid.
90
90
  */
91
91
  getNoDataValue(image: any): number;
92
92
  /**
@@ -96,6 +96,25 @@ declare class CogTiles {
96
96
  * @returns {number} The number of channels in the image.
97
97
  */
98
98
  getNumberOfChannels(image: any): any;
99
+ /**
100
+ * Calculates the intersection between a tile bounding box and a COG bounding box,
101
+ * returning the intersection window in image pixel space (relative to COG offsets),
102
+ * along with how much blank space (nodata) appears on the left and top of the tile.
103
+ *
104
+ * @param {number[]} tileBbox - Tile bounding box: [minX, minY, maxX, maxY]
105
+ * @param {number[]} cogBbox - COG bounding box: [minX, minY, maxX, maxY]
106
+ * @param {number} offsetXPixel - X offset of the COG origin in pixel space
107
+ * @param {number} offsetYPixel - Y offset of the COG origin in pixel space
108
+ * @param {number} tileSize - Size of the tile in pixels (default: 256)
109
+ * @returns {[number, number, number[] | null, number, number]}
110
+ * An array containing:
111
+ * - width of the intersection
112
+ * - height of the intersection
113
+ * - pixel-space window: [startX, startY, endX, endY] or null if no overlap
114
+ * - missingLeft: padding pixels on the left
115
+ * - missingTop: padding pixels on the top
116
+ */
117
+ getIntersectionBBox(tileBbox: any, cogBbox: any, offsetXPixel?: number, offsetYPixel?: number, tileSize?: number): any[];
99
118
  /**
100
119
  * Retrieves the PlanarConfiguration value from a GeoTIFF image.
101
120
  *
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gisatcz/deckgl-geolib",
3
- "version": "1.12.0-dev.3",
3
+ "version": "1.12.0-dev.5",
4
4
  "private": false,
5
5
  "main": "dist/cjs/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -315,7 +315,7 @@ export default class CogBitmapLayer<ExtraPropsT extends {} = {}> extends Composi
315
315
  // opacity,
316
316
  // cogBitmapOptions,
317
317
  clampToTerrain,
318
- cogBitmapOptions,
318
+ cogBitmapOptions.useChannel,
319
319
  ],
320
320
  // renderSubLayers: [cogBitmapOptions],
321
321
  },
@@ -206,44 +206,37 @@ class CogTiles {
206
206
  const offsetXPixel = Math.floor(offsetXMap / tileResolution);
207
207
  const offsetYPixel = Math.floor(offsetYMap / tileResolution);
208
208
 
209
- // Calculate the pixel boundaries for the tile.
210
- const window = [
211
- tileX * tileWidth - offsetXPixel, // startX
212
- tileY * tileHeight - offsetYPixel, // startY
213
- (tileX + 1) * tileWidth - offsetXPixel, // endX (exclusive)
214
- (tileY + 1) * tileHeight - offsetYPixel, // endY (exclusive)
215
- ];
216
-
217
- const [windowStartX, windowStartY, windowEndX, windowEndY] = window;
218
-
219
209
  const imageHeight = targetImage.getHeight();
220
210
  const imageWidth = targetImage.getWidth();
221
211
 
222
- // Determine the effective (valid) window inside the image:
223
- const effectiveStartX = Math.max(0, windowStartX);
224
- const effectiveStartY = Math.max(0, windowStartY);
225
- const effectiveEndX = windowEndX;
226
- const effectiveEndY = windowEndY;
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;
227
229
 
228
- // Calculate how many pixels are missing from the left and top due to negative windowStart.
229
- const missingLeft = Math.max(0, 0 - windowStartX);
230
- const missingTop = Math.max(0, 0 - windowStartY);
231
230
 
232
- // Read only the valid window from the image.
233
- const validWindow = [effectiveStartX, effectiveStartY, effectiveEndX, effectiveEndY];
234
231
 
235
232
  // Read the raster data for the tile window with shifted origin.
236
- if (missingLeft > 0 || missingTop > 0) {
233
+ if (missingLeft > 0 || missingTop > 0 || validWidth < tileWidth || validHeight < tileHeight) {
237
234
  // Prepare the final tile buffer and fill it with noDataValue.
238
235
  const tileBuffer = this.createTileBuffer(this.options.format, tileWidth);
239
236
  tileBuffer.fill(this.options.noDataValue);
240
237
 
241
- // Calculate the width of the valid window.
242
- const validWidth = Math.min(imageWidth, effectiveEndX - effectiveStartX);
243
- const validHeight = Math.min(imageHeight, effectiveEndY - effectiveStartY);
244
-
245
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
246
- const validRasterData = await targetImage.readRasters({ window: validWindow });
239
+ const validRasterData = await targetImage.readRasters({ window });
247
240
 
248
241
  // FOR MULTI-BAND - the result is one array with sequentially typed bands, firstly all data for the band 0, then for band 1
249
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.
@@ -335,29 +328,43 @@ class CogTiles {
335
328
 
336
329
  /**
337
330
  * Extracts the noData value from a GeoTIFF.js image.
338
- * Returns the noData value as a number if available, otherwise undefined.
331
+ * Returns the noData value as a number (including NaN) if available, otherwise undefined.
339
332
  *
340
333
  * @param {GeoTIFFImage} image - The GeoTIFF.js image.
341
- * @returns {number|undefined} The noData value as a number, or undefined if not available.
334
+ * @returns {number|undefined} The noData value, possibly NaN, or undefined if not set or invalid.
342
335
  */
343
336
  getNoDataValue(image) {
344
- // Attempt to retrieve the noData value via the GDAL method.
345
337
  const noDataRaw = image.getGDALNoData();
346
338
 
347
339
  if (noDataRaw === undefined || noDataRaw === null) {
348
- console.log('noDataValue is undefined or null,raster might be displayed incorrectly.');
349
- // No noData value is defined
340
+ console.warn('No noData value defined raster might be rendered incorrectly.');
350
341
  return undefined;
351
342
  }
352
343
 
353
- // In geotiff.js, the noData value is typically returned as a string.
354
- // Clean up the string by removing any null characters or extra whitespace.
355
- const cleanedValue = String(noDataRaw).replace(/\0/g, '').trim();
344
+ const cleaned = String(noDataRaw).replace(/\0/g, '').trim();
356
345
 
357
- const parsedValue = Number(cleanedValue);
358
- return Number.isNaN(parsedValue) ? undefined : parsedValue;
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;
359
365
  }
360
366
 
367
+
361
368
  /**
362
369
  * Retrieves the number of channels (samples per pixel) in a GeoTIFF image.
363
370
  *
@@ -368,6 +375,62 @@ class CogTiles {
368
375
  return image.getSamplesPerPixel();
369
376
  }
370
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
+
371
434
  /**
372
435
  * Retrieves the PlanarConfiguration value from a GeoTIFF image.
373
436
  *
@@ -423,6 +423,8 @@ export default class GeoImage {
423
423
  }
424
424
 
425
425
  getColorValue(dataArray:[], options:GeoImageOptions, arrayLength:number, numOfChannels = 1) {
426
+ // const rgb = chroma.random().rgb(); // [R, G, B]
427
+ // const randomColor = [...rgb, 120];
426
428
  const colorScale = chroma.scale(options.colorScale).domain(options.colorScaleValueRange);
427
429
  // channel index is equal to channel number - 1
428
430
  let pixel:number = options.useChannelIndex === null ? 0 : options.useChannelIndex;
@@ -443,6 +445,7 @@ export default class GeoImage {
443
445
 
444
446
  for (let i = 0; i < arrayLength; i += 4) {
445
447
  let pixelColor = options.nullColor;
448
+ // let pixelColor = randomColor;
446
449
  // FIXME
447
450
  // eslint-disable-next-line max-len
448
451
  if ((!Number.isNaN(dataArray[pixel])) && (options.noDataValue === undefined || dataArray[pixel] !== options.noDataValue)) {