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