@gisatcz/deckgl-geolib 2.5.0-dev.4 → 2.5.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.
package/dist/esm/index.js CHANGED
@@ -6718,11 +6718,37 @@ class CogTiles {
6718
6718
  bounds = [0, 0, 0, 0];
6719
6719
  geo = new GeoImage();
6720
6720
  options;
6721
+ // Cache fetched rasters in an LRU-style Map keyed by `${z}/${x}/${y}/${fetchSize}`,
6722
+ // with each value holding the cached raster for that tile request.
6723
+ rasterCache = new Map();
6724
+ maxCacheSize = 256;
6725
+ // LRU cache helpers
6726
+ getCachedRaster(key) {
6727
+ const value = this.rasterCache.get(key);
6728
+ if (value !== undefined) {
6729
+ // Refresh order: delete and re-set
6730
+ this.rasterCache.delete(key);
6731
+ this.rasterCache.set(key, value);
6732
+ }
6733
+ return value;
6734
+ }
6735
+ setCachedRaster(key, value) {
6736
+ this.rasterCache.set(key, value);
6737
+ if (this.rasterCache.size > this.maxCacheSize) {
6738
+ // Evict oldest
6739
+ const oldestKey = this.rasterCache.keys().next().value;
6740
+ if (typeof oldestKey === 'string') {
6741
+ this.rasterCache.delete(oldestKey);
6742
+ }
6743
+ }
6744
+ }
6721
6745
  // Cache GeoTIFFImage Promises by index to prevent redundant HTTP requests from geotiff 3.0.4+ eager loading
6722
6746
  // Stores Promises (not resolved values) so concurrent requests share the same getImage() call
6723
6747
  imageCache = new Map();
6724
6748
  // Store initialization promise to prevent concurrent duplicate initializations
6725
6749
  initializePromise;
6750
+ // Track the last successfully initialized URL to detect URL changes
6751
+ lastInitializedUrl;
6726
6752
  constructor(options) {
6727
6753
  this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
6728
6754
  }
@@ -6730,6 +6756,10 @@ class CogTiles {
6730
6756
  // Return existing initialization promise if already in progress (prevents concurrent duplicates)
6731
6757
  if (this.initializePromise)
6732
6758
  return this.initializePromise;
6759
+ // Clear cache only if URL changed (preserves cache on idempotent re-init)
6760
+ if (this.lastInitializedUrl !== url) {
6761
+ this.rasterCache.clear();
6762
+ }
6733
6763
  if (this.cog)
6734
6764
  return;
6735
6765
  this.initializePromise = (async () => {
@@ -6759,6 +6789,8 @@ class CogTiles {
6759
6789
  }
6760
6790
  this.zoomRange = this.calculateZoomRange(this.tileSize, image.getResolution()[0], await this.cog.getImageCount());
6761
6791
  this.bounds = this.calculateBoundsAsLatLon(image.getBoundingBox());
6792
+ // Mark initialization complete for this URL (used to detect URL changes)
6793
+ this.lastInitializedUrl = url;
6762
6794
  }
6763
6795
  catch (error) {
6764
6796
  // Reset initialization promise on error so retry can be attempted
@@ -6864,12 +6896,20 @@ class CogTiles {
6864
6896
  return this.cogZoomLookup.length - 1;
6865
6897
  // For zoom levels within the available range, find the exact or closest matching index.
6866
6898
  const exactMatchIndex = this.cogZoomLookup.indexOf(zoom);
6867
- if (exactMatchIndex === -1) {
6868
- // TO DO improve the condition if the match index is not found
6869
- /* eslint-disable no-console */
6870
- console.log('getImageIndexForZoomLevel: error in retrieving image by zoom index');
6899
+ if (exactMatchIndex !== -1) {
6900
+ return exactMatchIndex;
6901
+ }
6902
+ // No exact match: find the closest zoom level
6903
+ let closestIndex = 0;
6904
+ let minDistance = Math.abs(this.cogZoomLookup[0] - zoom);
6905
+ for (let i = 1; i < this.cogZoomLookup.length; i += 1) {
6906
+ const distance = Math.abs(this.cogZoomLookup[i] - zoom);
6907
+ if (distance < minDistance) {
6908
+ minDistance = distance;
6909
+ closestIndex = i;
6910
+ }
6871
6911
  }
6872
- return exactMatchIndex;
6912
+ return closestIndex;
6873
6913
  }
6874
6914
  async getTileFromImage(tileX, tileY, zoom, fetchSize, signal) {
6875
6915
  // Create a fresh local AbortController for this specific fetch.
@@ -6883,6 +6923,12 @@ class CogTiles {
6883
6923
  signal.addEventListener('abort', () => controller.abort(), { once: true });
6884
6924
  }
6885
6925
  const localSignal = controller.signal;
6926
+ // Check if raster is already cached
6927
+ const cacheKey = `${zoom}/${tileX}/${tileY}/${fetchSize ?? this.tileSize}`;
6928
+ const cachedRaster = this.getCachedRaster(cacheKey);
6929
+ if (cachedRaster) {
6930
+ return [cachedRaster];
6931
+ }
6886
6932
  try {
6887
6933
  const imageIndex = this.getImageIndexForZoomLevel(zoom);
6888
6934
  // Cache Promises to share in-flight requests across concurrent tiles at the same overview
@@ -6973,8 +7019,7 @@ class CogTiles {
6973
7019
  tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
6974
7020
  }
6975
7021
  else {
6976
- /* eslint-disable no-console */
6977
- console.log(`error in assigning data to tile buffer: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
7022
+ console.error(`[CogTiles] tile buffer bounds exceeded: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
6978
7023
  }
6979
7024
  }
6980
7025
  }
@@ -6982,11 +7027,15 @@ class CogTiles {
6982
7027
  validImageData[i * numChannels + band] = tileBuffer[i];
6983
7028
  }
6984
7029
  }
7030
+ // Mark raster as cached after successful fetch
7031
+ this.setCachedRaster(cacheKey, validImageData); // for partial overlap
6985
7032
  return [validImageData];
6986
7033
  }
6987
7034
  // Case B: Perfect Match (Optimization)
6988
7035
  // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
6989
7036
  const tileData = await targetImage.readRasters({ window, interleave: true, signal: localSignal });
7037
+ // Mark raster as cached after successful fetch
7038
+ this.setCachedRaster(cacheKey, tileData); // for perfect match
6990
7039
  return [tileData];
6991
7040
  }
6992
7041
  catch (error) {
@@ -7047,6 +7096,10 @@ class CogTiles {
7047
7096
  tileWidth = this.tileSize;
7048
7097
  tileHeight = this.tileSize;
7049
7098
  }
7099
+ // Guard against abort race condition: if signal aborted after cache hit but before expensive geo.getMap()
7100
+ if (signal?.aborted) {
7101
+ return null;
7102
+ }
7050
7103
  return this.geo.getMap({
7051
7104
  rasters,
7052
7105
  width: tileWidth,