@gisatcz/deckgl-geolib 1.12.0-dev.13 → 1.12.0-dev.14

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/cjs/index.js CHANGED
@@ -5601,61 +5601,90 @@ class CogTiles {
5601
5601
  return __awaiter(this, void 0, void 0, function* () {
5602
5602
  const imageIndex = this.getImageIndexForZoomLevel(zoom);
5603
5603
  const targetImage = yield this.cog.getImage(imageIndex);
5604
- // Ensure the image is tiled
5604
+ // 1. Validation: Ensure the image is tiled
5605
5605
  const tileWidth = targetImage.getTileWidth();
5606
5606
  const tileHeight = targetImage.getTileHeight();
5607
5607
  if (!tileWidth || !tileHeight) {
5608
- throw new Error('The image is not tiled.');
5608
+ throw new Error('GeoTIFF Error: The provided image is not tiled. '
5609
+ + 'Please use "rio cogeo create --web-optimized" to fix this.');
5609
5610
  }
5610
- // Calculate the map offset between the global Web Mercator origin and the COG's origin.
5611
- // (Difference in map units.)
5612
- // if X offset is large and positive (COG is far to the right of global origin)
5613
- // if Y offset is large and positive (COG is far below global origin — expected)
5614
- const offsetXMap = this.cogOrigin[0] - webMercatorOrigin[0];
5615
- const offsetYMap = webMercatorOrigin[1] - this.cogOrigin[1];
5616
- const tileResolution = (EARTH_CIRCUMFERENCE / tileWidth) / Math.pow(2, zoom);
5617
- this.cogResolutionLookup[imageIndex];
5618
- // Convert map offsets into pixel offsets.
5619
- const offsetXPixel = Math.floor(offsetXMap / tileResolution);
5620
- const offsetYPixel = Math.floor(offsetYMap / tileResolution);
5611
+ // --- STEP 1: CALCULATE BOUNDS IN METERS ---
5612
+ // 2. Get COG Metadata (image = COG)
5613
+ const imageResolution = this.cogResolutionLookup[imageIndex];
5621
5614
  const imageHeight = targetImage.getHeight();
5622
5615
  const imageWidth = targetImage.getWidth();
5623
- // approach by comparing bboxes of tile and cog image
5624
- const tilePixelBbox = [
5625
- tileX * tileWidth,
5626
- tileY * tileHeight,
5627
- (tileX + 1) * tileWidth,
5628
- (tileY + 1) * tileHeight,
5629
- ];
5630
- const cogPixelBBox = [
5631
- offsetXPixel,
5632
- offsetYPixel,
5633
- offsetXPixel + imageWidth,
5634
- offsetYPixel + imageHeight,
5635
- ];
5636
- const intersecion = this.getIntersectionBBox(tilePixelBbox, cogPixelBBox, offsetXPixel, offsetYPixel, tileWidth);
5637
- const [validWidth, validHeight, window, missingLeft, missingTop] = intersecion;
5638
- // Read the raster data for the tile window with shifted origin.
5639
- if (missingLeft > 0 || missingTop > 0 || validWidth < tileWidth || validHeight < tileHeight) {
5640
- // Prepare the final tile buffer and fill it with noDataValue.
5616
+ const [imgOriginX, imgOriginY] = this.cogOrigin;
5617
+ // 3. Define Web Mercator Constants
5618
+ // We use the class property tileSize (usually 256) as the ground truth for grid calculations
5619
+ const TILE_SIZE = this.tileSize;
5620
+ const ORIGIN_X = webMercatorOrigin[0];
5621
+ const ORIGIN_Y = webMercatorOrigin[1];
5622
+ // 4. Calculate Tile BBox in World Meters
5623
+ // This defines where the map expects the tile to be physically located
5624
+ const tileGridResolution = (EARTH_CIRCUMFERENCE / TILE_SIZE) / (Math.pow(2, zoom));
5625
+ const tileMinXMeters = ORIGIN_X + (tileX * TILE_SIZE * tileGridResolution);
5626
+ const tileMaxYMeters = ORIGIN_Y - (tileY * TILE_SIZE * tileGridResolution);
5627
+ // Note: We don't strictly need MaxX/MinY meters for the start calculation,
5628
+ // but they are useful if debugging the full meter footprint.
5629
+ // --- STEP 2: CONVERT TO PIXEL COORDINATES ---
5630
+ // 5. Calculate precise floating-point start position relative to the image
5631
+ const windowMinX = (tileMinXMeters - imgOriginX) / imageResolution;
5632
+ const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
5633
+ // 6. Snap to Integer Grid (The "Force 256" Fix)
5634
+ // We round the start position to align with the nearest pixel.
5635
+ // Crucially, we calculate endX/endY by adding tileSize to startX/startY.
5636
+ // This guarantees the window is exactly 256x256, preventing "off-by-one" (257px) errors.
5637
+ const startX = Math.round(windowMinX);
5638
+ const startY = Math.round(windowMinY);
5639
+ const endX = startX + TILE_SIZE;
5640
+ const endY = startY + TILE_SIZE;
5641
+ // --- STEP 3: CALCULATE INTERSECTION ---
5642
+ // 7. Clamp the read window to the actual image dimensions
5643
+ // This defines the "Safe" area we can actually read from the file.
5644
+ const validReadX = Math.max(0, startX);
5645
+ const validReadY = Math.max(0, startY);
5646
+ const validReadMaxX = Math.min(imageWidth, endX);
5647
+ const validReadMaxY = Math.min(imageHeight, endY);
5648
+ const readWidth = validReadMaxX - validReadX;
5649
+ const readHeight = validReadMaxY - validReadY;
5650
+ // CHECK: If no overlap, return empty
5651
+ if (readWidth <= 0 || readHeight <= 0) {
5652
+ return [this.createEmptyTile()];
5653
+ }
5654
+ // 8. Calculate Offsets (Padding)
5655
+ // "missingLeft" is how many blank pixels we need to insert before the image data starts.
5656
+ // Logic: If we wanted to read from -50 (startX), but clamped to 0 (validReadX),
5657
+ // we are missing the first 50 pixels.
5658
+ const missingLeft = validReadX - startX;
5659
+ const missingTop = validReadY - startY;
5660
+ const window = [validReadX, validReadY, validReadMaxX, validReadMaxY];
5661
+ // --- STEP 4: READ AND COMPOSITE ---
5662
+ // Case A: Partial Overlap (Padding or Cropping required)
5663
+ // If the tile is hanging off the edge, we need to manually reconstruct it.
5664
+ if (missingLeft > 0 || missingTop > 0 || readWidth < tileWidth || readHeight < tileHeight) {
5665
+ /// Initialize a temporary buffer for a single band (filled with NoData)
5666
+ // We will reuse this buffer for each band to save memory allocations.
5641
5667
  const tileBuffer = this.createTileBuffer(this.options.format, tileWidth);
5642
5668
  tileBuffer.fill(this.options.noDataValue);
5643
- // 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
5669
+ // if the valid window is smaller than the tile size, it gets the image size width and height, thus validRasterData.width must be used as below
5644
5670
  const validRasterData = yield targetImage.readRasters({ window });
5645
5671
  // FOR MULTI-BAND - the result is one array with sequentially typed bands, firstly all data for the band 0, then for band 1
5646
5672
  // 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.
5647
5673
  const validImageData = Array(validRasterData.length * validRasterData[0].length);
5648
5674
  validImageData.fill(this.options.noDataValue);
5649
5675
  // Place the valid pixel data into the tile buffer.
5650
- for (let band = 0; band < validRasterData.length; band++) {
5651
- for (let row = 0; row < validHeight; row++) {
5652
- for (let col = 0; col < validWidth; col++) {
5676
+ for (let band = 0; band < validRasterData.length; band += 1) {
5677
+ for (let row = 0; row < readHeight; row += 1) {
5678
+ const destRow = missingTop + row;
5679
+ const destRowOffset = destRow * TILE_SIZE;
5680
+ const srcRowOffset = row * validRasterData.width;
5681
+ for (let col = 0; col < readWidth; col += 1) {
5653
5682
  // Compute the destination position in the tile buffer.
5654
5683
  // We shift by the number of missing pixels (if any) at the top/left.
5655
- const destRow = missingTop + row;
5656
5684
  const destCol = missingLeft + col;
5685
+ // Bounds Check: Ensure we don't write outside the 256x256 buffer
5657
5686
  if (destRow < tileWidth && destCol < tileHeight) {
5658
- tileBuffer[destRow * tileWidth + destCol] = validRasterData[band][row * validRasterData.width + col];
5687
+ tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
5659
5688
  }
5660
5689
  else {
5661
5690
  console.log('error in assigning data to tile buffer');
@@ -5668,12 +5697,32 @@ class CogTiles {
5668
5697
  }
5669
5698
  return [validImageData];
5670
5699
  }
5671
- // Read the raster data for the non shifted tile window.
5700
+ // Case B: Perfect Match (Optimization)
5701
+ // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
5702
+ // console.log("Perfect aligned read");
5672
5703
  const tileData = yield targetImage.readRasters({ window, interleave: true });
5673
5704
  // console.log(`data that starts at the left top corner of the tile ${tileX}, ${tileY}`);
5674
5705
  return [tileData];
5675
5706
  });
5676
5707
  }
5708
+ /**
5709
+ * Creates a blank tile buffer filled with the "No Data" value.
5710
+ */
5711
+ createEmptyTile() {
5712
+ // 1. Determine the size
5713
+ // Default to 1 channel (grayscale) if not specified
5714
+ const channels = this.options.numOfChannels || 1;
5715
+ const size = this.tileSize * this.tileSize * channels;
5716
+ // 2. Create the array
5717
+ // Float32 is standard for GeoTIFF data handling in browsers
5718
+ const tileData = new Float32Array(size);
5719
+ // 3. Fill with "No Data" value
5720
+ // If noDataValue is undefined, it defaults to 0
5721
+ if (this.options.noDataValue !== undefined) {
5722
+ tileData.fill(this.options.noDataValue);
5723
+ }
5724
+ return tileData;
5725
+ }
5677
5726
  getTile(x, y, z, bounds, meshMaxError) {
5678
5727
  return __awaiter(this, void 0, void 0, function* () {
5679
5728
  const tileData = yield this.getTileFromImage(x, y, z);
@@ -5766,53 +5815,6 @@ class CogTiles {
5766
5815
  getNumberOfChannels(image) {
5767
5816
  return image.getSamplesPerPixel();
5768
5817
  }
5769
- /**
5770
- * Calculates the intersection between a tile bounding box and a COG bounding box,
5771
- * returning the intersection window in image pixel space (relative to COG offsets),
5772
- * along with how much blank space (nodata) appears on the left and top of the tile.
5773
- *
5774
- * @param {number[]} tileBbox - Tile bounding box: [minX, minY, maxX, maxY]
5775
- * @param {number[]} cogBbox - COG bounding box: [minX, minY, maxX, maxY]
5776
- * @param {number} offsetXPixel - X offset of the COG origin in pixel space
5777
- * @param {number} offsetYPixel - Y offset of the COG origin in pixel space
5778
- * @param {number} tileSize - Size of the tile in pixels (default: 256)
5779
- * @returns {[number, number, number[] | null, number, number]}
5780
- * An array containing:
5781
- * - width of the intersection
5782
- * - height of the intersection
5783
- * - pixel-space window: [startX, startY, endX, endY] or null if no overlap
5784
- * - missingLeft: padding pixels on the left
5785
- * - missingTop: padding pixels on the top
5786
- */
5787
- getIntersectionBBox(tileBbox, cogBbox, offsetXPixel = 0, offsetYPixel = 0, tileSize = 256) {
5788
- const interLeft = Math.max(tileBbox[0], cogBbox[0]);
5789
- const interTop = Math.max(tileBbox[1], cogBbox[1]);
5790
- const interRight = Math.min(tileBbox[2], cogBbox[2]);
5791
- const interBottom = Math.min(tileBbox[3], cogBbox[3]);
5792
- const width = Math.max(0, interRight - interLeft);
5793
- const height = Math.max(0, interBottom - interTop);
5794
- let window = null;
5795
- let missingLeft = 0;
5796
- let missingTop = 0;
5797
- if (width > 0 && height > 0) {
5798
- window = [
5799
- interLeft - offsetXPixel,
5800
- interTop - offsetYPixel,
5801
- interRight - offsetXPixel,
5802
- interBottom - offsetYPixel,
5803
- ];
5804
- // Padding from the tile origin to valid data start
5805
- missingLeft = interLeft - tileBbox[0];
5806
- missingTop = interTop - tileBbox[1];
5807
- }
5808
- return [
5809
- width,
5810
- height,
5811
- window,
5812
- missingLeft,
5813
- missingTop,
5814
- ];
5815
- }
5816
5818
  /**
5817
5819
  * Retrieves the PlanarConfiguration value from a GeoTIFF image.
5818
5820
  *