@gisatcz/deckgl-geolib 2.1.1 → 2.1.2-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.
package/dist/cjs/index.js CHANGED
@@ -4,7 +4,6 @@ var core = require('@deck.gl/core');
4
4
  var geoLayers = require('@deck.gl/geo-layers');
5
5
  var layers = require('@deck.gl/layers');
6
6
  var extensions = require('@deck.gl/extensions');
7
- var webMercator = require('@math.gl/web-mercator');
8
7
  var chroma = require('chroma-js');
9
8
  var schema = require('@loaders.gl/schema');
10
9
  var loaderUtils = require('@loaders.gl/loader-utils');
@@ -5326,17 +5325,25 @@ class GeoImage {
5326
5325
  channel = rasters[optionsLocal.useChannelIndex];
5327
5326
  }
5328
5327
  }
5329
- const terrain = new Float32Array((width + 1) * (height + 1));
5328
+ const terrain = new Float32Array((width === 257 ? width : width + 1) * (height === 257 ? height : height + 1));
5330
5329
  const numOfChannels = channel.length / (width * height);
5331
5330
  let pixel = options.useChannelIndex === null ? 0 : options.useChannelIndex;
5332
- for (let i = 0, y = 0; y < height; y++) {
5333
- for (let x = 0; x < width; x++, i++) {
5334
- const elevationValue = (options.noDataValue && channel[pixel] === options.noDataValue) ? options.terrainMinValue : channel[pixel] * options.multiplier;
5335
- terrain[i + y] = elevationValue;
5331
+ const isStitched = width === 257;
5332
+ for (let y = 0; y < height; y++) {
5333
+ for (let x = 0; x < width; x++) {
5334
+ let elevationValue = (options.noDataValue && channel[pixel] === options.noDataValue) ? options.terrainMinValue : channel[pixel] * options.multiplier;
5335
+ // Validate that the elevation value is within the valid range for Float32.
5336
+ // Extreme values (like -1.79e308) can become -Infinity when cast, causing WebGL errors.
5337
+ if (Number.isNaN(elevationValue) || elevationValue < -3.4e38 || elevationValue > 3.4e38) {
5338
+ elevationValue = options.terrainMinValue;
5339
+ }
5340
+ // If stitched (257), fill linearly. If 256, fill with stride for padding.
5341
+ const index = isStitched ? (y * width + x) : (y * (width + 1) + x);
5342
+ terrain[index] = elevationValue;
5336
5343
  pixel += numOfChannels;
5337
5344
  }
5338
5345
  }
5339
- if (options.tesselator === 'martini') {
5346
+ if (!isStitched) {
5340
5347
  // backfill bottom border
5341
5348
  for (let i = (width + 1) * width, x = 0; x < width; x++, i++) {
5342
5349
  terrain[i] = terrain[i - width - 1];
@@ -5655,22 +5662,26 @@ class GeoImage {
5655
5662
  * @returns {{vertices: Uint16Array, triangles: Uint32Array}} vertices and triangles data
5656
5663
  */
5657
5664
  function getMartiniTileMesh(meshMaxError, width, terrain) {
5658
- const gridSize = width + 1;
5665
+ const gridSize = width === 257 ? 257 : width + 1;
5659
5666
  const martini = new Martini(gridSize);
5660
5667
  const tile = martini.createTile(terrain);
5661
5668
  const { vertices, triangles } = tile.getMesh(meshMaxError);
5662
5669
  return { vertices, triangles };
5663
5670
  }
5664
5671
  function getMeshAttributes(vertices, terrain, width, height, bounds) {
5665
- const gridSize = width + 1;
5672
+ const gridSize = width === 257 ? 257 : width + 1;
5666
5673
  const numOfVerticies = vertices.length / 2;
5667
5674
  // vec3. x, y in pixels, z in meters
5668
5675
  const positions = new Float32Array(numOfVerticies * 3);
5669
5676
  // vec2. 1 to 1 relationship with position. represents the uv on the texture image. 0,0 to 1,1.
5670
5677
  const texCoords = new Float32Array(numOfVerticies * 2);
5671
5678
  const [minX, minY, maxX, maxY] = bounds || [0, 0, width, height];
5672
- const xScale = (maxX - minX) / width;
5673
- const yScale = (maxY - minY) / height;
5679
+ // If stitched (257), the spatial extent covers 0..256 pixels, so we divide by 256.
5680
+ // If standard (256), the spatial extent covers 0..256 pixels (with backfill), so we divide by 256.
5681
+ const effectiveWidth = width === 257 ? width - 1 : width;
5682
+ const effectiveHeight = height === 257 ? height - 1 : height;
5683
+ const xScale = (maxX - minX) / effectiveWidth;
5684
+ const yScale = (maxY - minY) / effectiveHeight;
5674
5685
  for (let i = 0; i < numOfVerticies; i++) {
5675
5686
  const x = vertices[i * 2];
5676
5687
  const y = vertices[i * 2 + 1];
@@ -5678,8 +5689,8 @@ function getMeshAttributes(vertices, terrain, width, height, bounds) {
5678
5689
  positions[3 * i + 0] = x * xScale + minX;
5679
5690
  positions[3 * i + 1] = -y * yScale + maxY;
5680
5691
  positions[3 * i + 2] = terrain[pixelIdx];
5681
- texCoords[2 * i + 0] = x / width;
5682
- texCoords[2 * i + 1] = y / height;
5692
+ texCoords[2 * i + 0] = x / effectiveWidth;
5693
+ texCoords[2 * i + 1] = y / effectiveHeight;
5683
5694
  }
5684
5695
  return {
5685
5696
  POSITION: { value: positions, size: 3 },
@@ -5697,7 +5708,9 @@ function getMeshAttributes(vertices, terrain, width, height, bounds) {
5697
5708
  * @returns {{vertices: number[], triangles: number[]}} vertices and triangles data
5698
5709
  */
5699
5710
  function getDelatinTileMesh(meshMaxError, width, height, terrain) {
5700
- const tin = new Delatin(terrain, width + 1, height + 1);
5711
+ const widthPlus = width === 257 ? 257 : width + 1;
5712
+ const heightPlus = height === 257 ? 257 : height + 1;
5713
+ const tin = new Delatin(terrain, widthPlus, heightPlus);
5701
5714
  tin.run(meshMaxError);
5702
5715
  // @ts-expect-error: Delatin instance properties 'coords' and 'triangles' are not explicitly typed in the library port
5703
5716
  const { coords, triangles } = tin;
@@ -5765,16 +5778,15 @@ class CogTiles {
5765
5778
  return this.bounds;
5766
5779
  }
5767
5780
  getLatLon(input) {
5768
- const ax = EARTH_HALF_CIRCUMFERENCE + input[0];
5769
- const ay = -(EARTH_HALF_CIRCUMFERENCE + (input[1] - EARTH_CIRCUMFERENCE));
5770
- const cartesianPosition = [
5771
- ax * (512 / EARTH_CIRCUMFERENCE),
5772
- ay * (512 / EARTH_CIRCUMFERENCE),
5773
- ];
5774
- const cartographicPosition = webMercator.worldToLngLat(cartesianPosition);
5775
- const cartographicPositionAdjusted = [cartographicPosition[0], -cartographicPosition[1]];
5776
- return cartographicPositionAdjusted;
5777
- }
5781
+ const x = input[0];
5782
+ const y = input[1];
5783
+ const lon = (x / EARTH_HALF_CIRCUMFERENCE) * 180;
5784
+ let lat = (y / EARTH_HALF_CIRCUMFERENCE) * 180;
5785
+ lat = (180 / Math.PI) * (2 * Math.atan(Math.exp((lat * Math.PI) / 180)) - Math.PI / 2);
5786
+ return [lon, lat];
5787
+ }
5788
+ // return cartographicPositionAdjusted;
5789
+ // }
5778
5790
  /**
5779
5791
  * Builds lookup tables for zoom levels and estimated resolutions from a Cloud Optimized GeoTIFF (COG) object.
5780
5792
  *
@@ -5843,7 +5855,7 @@ class CogTiles {
5843
5855
  }
5844
5856
  return exactMatchIndex;
5845
5857
  }
5846
- async getTileFromImage(tileX, tileY, zoom) {
5858
+ async getTileFromImage(tileX, tileY, zoom, fetchSize) {
5847
5859
  const imageIndex = this.getImageIndexForZoomLevel(zoom);
5848
5860
  const targetImage = await this.cog.getImage(imageIndex);
5849
5861
  // 1. Validation: Ensure the image is tiled
@@ -5877,12 +5889,11 @@ class CogTiles {
5877
5889
  const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
5878
5890
  // 6. Snap to Integer Grid (The "Force 256" Fix)
5879
5891
  // We round the start position to align with the nearest pixel.
5880
- // Crucially, we calculate endX/endY by adding tileSize to startX/startY.
5881
- // This guarantees the window is exactly 256x256, preventing "off-by-one" (257px) errors.
5892
+ const FETCH_SIZE = fetchSize || TILE_SIZE; // Default to 256 if not provided
5882
5893
  const startX = Math.round(windowMinX);
5883
5894
  const startY = Math.round(windowMinY);
5884
- const endX = startX + TILE_SIZE;
5885
- const endY = startY + TILE_SIZE;
5895
+ const endX = startX + FETCH_SIZE;
5896
+ const endY = startY + FETCH_SIZE;
5886
5897
  // --- STEP 3: CALCULATE INTERSECTION ---
5887
5898
  // 7. Clamp the read window to the actual image dimensions
5888
5899
  // This defines the "Safe" area we can actually read from the file.
@@ -5894,7 +5905,7 @@ class CogTiles {
5894
5905
  const readHeight = validReadMaxY - validReadY;
5895
5906
  // CHECK: If no overlap, return empty
5896
5907
  if (readWidth <= 0 || readHeight <= 0) {
5897
- return [this.createEmptyTile()];
5908
+ return [this.createEmptyTile(FETCH_SIZE)];
5898
5909
  }
5899
5910
  // 8. Calculate Offsets (Padding)
5900
5911
  // "missingLeft" is how many blank pixels we need to insert before the image data starts.
@@ -5906,10 +5917,11 @@ class CogTiles {
5906
5917
  // --- STEP 4: READ AND COMPOSITE ---
5907
5918
  // Case A: Partial Overlap (Padding or Cropping required)
5908
5919
  // If the tile is hanging off the edge, we need to manually reconstruct it.
5909
- if (missingLeft > 0 || missingTop > 0 || readWidth < tileWidth || readHeight < tileHeight) {
5920
+ // We strictly compare against FETCH_SIZE because that is our target buffer dimension.
5921
+ if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
5910
5922
  /// Initialize a temporary buffer for a single band (filled with NoData)
5911
5923
  // We will reuse this buffer for each band to save memory allocations.
5912
- const tileBuffer = this.createTileBuffer(this.options.format, tileWidth);
5924
+ const tileBuffer = this.createTileBuffer(this.options.format, FETCH_SIZE);
5913
5925
  tileBuffer.fill(this.options.noDataValue);
5914
5926
  // 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
5915
5927
  const validRasterData = await targetImage.readRasters({ window });
@@ -5919,21 +5931,26 @@ class CogTiles {
5919
5931
  validImageData.fill(this.options.noDataValue);
5920
5932
  // Place the valid pixel data into the tile buffer.
5921
5933
  for (let band = 0; band < validRasterData.length; band += 1) {
5934
+ // We must reset the buffer for each band, otherwise data from previous band persists in padding areas
5935
+ const tileBuffer = this.createTileBuffer(this.options.format, FETCH_SIZE);
5936
+ if (this.options.noDataValue !== undefined) {
5937
+ tileBuffer.fill(this.options.noDataValue);
5938
+ }
5922
5939
  for (let row = 0; row < readHeight; row += 1) {
5923
5940
  const destRow = missingTop + row;
5924
- const destRowOffset = destRow * TILE_SIZE;
5941
+ const destRowOffset = destRow * FETCH_SIZE;
5925
5942
  const srcRowOffset = row * validRasterData.width;
5926
5943
  for (let col = 0; col < readWidth; col += 1) {
5927
5944
  // Compute the destination position in the tile buffer.
5928
5945
  // We shift by the number of missing pixels (if any) at the top/left.
5929
5946
  const destCol = missingLeft + col;
5930
- // Bounds Check: Ensure we don't write outside the 256x256 buffer
5931
- if (destRow < tileWidth && destCol < tileHeight) {
5947
+ // Bounds Check: Ensure we don't write outside the allocated buffer
5948
+ if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
5932
5949
  tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
5933
5950
  }
5934
5951
  else {
5935
5952
  /* eslint-disable no-console */
5936
- console.log('error in assigning data to tile buffer');
5953
+ console.log(`error in assigning data to tile buffer: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
5937
5954
  }
5938
5955
  }
5939
5956
  }
@@ -5952,28 +5969,28 @@ class CogTiles {
5952
5969
  }
5953
5970
  /**
5954
5971
  * Creates a blank tile buffer filled with the "No Data" value.
5972
+ * @param size The width/height of the square tile (e.g., 256 or 257)
5955
5973
  */
5956
- createEmptyTile() {
5957
- // 1. Determine the size
5958
- // Default to 1 channel (grayscale) if not specified
5974
+ createEmptyTile(size) {
5975
+ const s = size || this.tileSize; // Defaults to 256
5959
5976
  const channels = this.options.numOfChannels || 1;
5960
- const size = this.tileSize * this.tileSize * channels;
5961
- // 2. Create the array
5962
- // Float32 is standard for GeoTIFF data handling in browsers
5963
- const tileData = new Float32Array(size);
5964
- // 3. Fill with "No Data" value
5965
- // If noDataValue is undefined, it defaults to 0
5977
+ const totalSize = s * s * channels;
5978
+ const tileData = new Float32Array(totalSize);
5966
5979
  if (this.options.noDataValue !== undefined) {
5967
5980
  tileData.fill(this.options.noDataValue);
5968
5981
  }
5969
5982
  return tileData;
5970
5983
  }
5971
5984
  async getTile(x, y, z, bounds, meshMaxError) {
5972
- const tileData = await this.getTileFromImage(x, y, z);
5985
+ let requiredSize = this.tileSize; // Default 256 for image/bitmap
5986
+ if (this.options.type === 'terrain') {
5987
+ requiredSize = this.tileSize + 1; // 257 for stitching
5988
+ }
5989
+ const tileData = await this.getTileFromImage(x, y, z, requiredSize);
5973
5990
  return this.geo.getMap({
5974
5991
  rasters: [tileData[0]],
5975
- width: this.tileSize,
5976
- height: this.tileSize,
5992
+ width: requiredSize,
5993
+ height: requiredSize,
5977
5994
  bounds,
5978
5995
  }, this.options, meshMaxError);
5979
5996
  }
@@ -6078,7 +6095,8 @@ class CogTiles {
6078
6095
  case 'Float64':
6079
6096
  return new Float64Array(length);
6080
6097
  default:
6081
- throw new Error(`Unsupported data type: ${dataType}`);
6098
+ console.warn(`Unsupported data type: ${dataType}, defaulting to Float32`);
6099
+ return new Float32Array(length);
6082
6100
  }
6083
6101
  }
6084
6102
  }