@gisatcz/deckgl-geolib 2.4.0-dev.5 → 2.4.1-dev.1

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
@@ -4866,6 +4866,7 @@ async function fromArrayBuffer(arrayBuffer, signal) {
4866
4866
  const DefaultGeoImageOptions = {
4867
4867
  // --- Shared / Data ---
4868
4868
  type: 'image',
4869
+ blockSize: 65536,
4869
4870
  format: undefined,
4870
4871
  useChannel: null,
4871
4872
  useChannelIndex: null,
@@ -6255,36 +6256,57 @@ class CogTiles {
6255
6256
  bounds = [0, 0, 0, 0];
6256
6257
  geo = new GeoImage();
6257
6258
  options;
6259
+ // Cache GeoTIFFImage Promises by index to prevent redundant HTTP requests from geotiff 3.0.4+ eager loading
6260
+ // Stores Promises (not resolved values) so concurrent requests share the same getImage() call
6261
+ imageCache = new Map();
6262
+ // Store initialization promise to prevent concurrent duplicate initializations
6263
+ initializePromise;
6258
6264
  constructor(options) {
6259
6265
  this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
6260
6266
  }
6261
6267
  async initializeCog(url) {
6268
+ // Return existing initialization promise if already in progress (prevents concurrent duplicates)
6269
+ if (this.initializePromise)
6270
+ return this.initializePromise;
6262
6271
  if (this.cog)
6263
- return; // Prevent re-initialization on the same instance
6264
- try {
6265
- this.cog = await fromUrl(url);
6266
- const image = await this.cog.getImage();
6267
- const fileDirectory = image.fileDirectory;
6268
- this.cogOrigin = image.getOrigin();
6269
- this.options.noDataValue ??= await this.getNoDataValue(image);
6270
- this.options.format ??= await this.getDataTypeFromTags(fileDirectory);
6271
- this.options.numOfChannels = fileDirectory.getValue('SamplesPerPixel');
6272
- this.options.planarConfig = fileDirectory.getValue('PlanarConfiguration');
6273
- [this.cogZoomLookup, this.cogResolutionLookup] = await this.buildCogZoomResolutionLookup(this.cog);
6274
- this.tileSize = image.getTileWidth();
6275
- // 1. Validation: Ensure the image is tiled
6276
- if (!this.tileSize || !image.getTileHeight()) {
6277
- throw new Error('GeoTIFF Error: The provided image is not tiled. '
6278
- + 'Please use "rio cogeo create --web-optimized" to fix this.');
6279
- }
6280
- this.zoomRange = this.calculateZoomRange(this.tileSize, image.getResolution()[0], await this.cog.getImageCount());
6281
- this.bounds = this.calculateBoundsAsLatLon(image.getBoundingBox());
6282
- }
6283
- catch (error) {
6284
- /* eslint-disable no-console */
6285
- console.error(`[CogTiles] Failed to initialize COG from ${url}:`, error);
6286
- throw error;
6287
- }
6272
+ return;
6273
+ this.initializePromise = (async () => {
6274
+ try {
6275
+ // fromUrl's type declaration only exposes RemoteSourceOptions, but the implementation
6276
+ // also accepts BlockedSourceOptions (forwarded to makeFetchSource internally).
6277
+ // Explicitly enabling BlockedSource restores the block-level LRU cache that was
6278
+ // accidentally active in geotiff 3.0.3 (due to a null vs undefined bug there).
6279
+ // blockSize defaults to 65536 (64KB) and can be tuned via GeoImageOptions.
6280
+ const blockSize = this.options.blockSize ?? 65536;
6281
+ this.cog = await fromUrl(url, { blockSize });
6282
+ const imagePromise = this.cog.getImage();
6283
+ this.imageCache.set(0, imagePromise); // Cache base image (index 0) to avoid re-fetching during getTileFromImage
6284
+ const image = await imagePromise;
6285
+ const fileDirectory = image.fileDirectory;
6286
+ this.cogOrigin = image.getOrigin();
6287
+ this.options.noDataValue ??= await this.getNoDataValue(image);
6288
+ this.options.format ??= await this.getDataTypeFromTags(fileDirectory);
6289
+ this.options.numOfChannels = fileDirectory.getValue('SamplesPerPixel');
6290
+ this.options.planarConfig = fileDirectory.getValue('PlanarConfiguration');
6291
+ [this.cogZoomLookup, this.cogResolutionLookup] = await this.buildCogZoomResolutionLookup(this.cog);
6292
+ this.tileSize = image.getTileWidth();
6293
+ // 1. Validation: Ensure the image is tiled
6294
+ if (!this.tileSize || !image.getTileHeight()) {
6295
+ throw new Error('GeoTIFF Error: The provided image is not tiled. '
6296
+ + 'Please use "rio cogeo create --web-optimized" to fix this.');
6297
+ }
6298
+ this.zoomRange = this.calculateZoomRange(this.tileSize, image.getResolution()[0], await this.cog.getImageCount());
6299
+ this.bounds = this.calculateBoundsAsLatLon(image.getBoundingBox());
6300
+ }
6301
+ catch (error) {
6302
+ // Reset initialization promise on error so retry can be attempted
6303
+ this.initializePromise = undefined;
6304
+ /* eslint-disable no-console */
6305
+ console.error(`[CogTiles] Failed to initialize COG from ${url}:`, error);
6306
+ throw error;
6307
+ }
6308
+ })();
6309
+ return this.initializePromise;
6288
6310
  }
6289
6311
  getZoomRange() {
6290
6312
  return this.zoomRange;
@@ -6389,7 +6411,13 @@ class CogTiles {
6389
6411
  }
6390
6412
  async getTileFromImage(tileX, tileY, zoom, fetchSize) {
6391
6413
  const imageIndex = this.getImageIndexForZoomLevel(zoom);
6392
- const targetImage = await this.cog.getImage(imageIndex);
6414
+ // Cache Promises to share in-flight requests across concurrent tiles at the same overview
6415
+ let imagePromise = this.imageCache.get(imageIndex);
6416
+ if (!imagePromise) {
6417
+ imagePromise = this.cog.getImage(imageIndex);
6418
+ this.imageCache.set(imageIndex, imagePromise);
6419
+ }
6420
+ const targetImage = await imagePromise;
6393
6421
  // --- STEP 1: CALCULATE BOUNDS IN METERS ---
6394
6422
  // 2. Get COG Metadata (image = COG)
6395
6423
  const imageResolution = this.cogResolutionLookup[imageIndex];
@@ -6880,11 +6908,21 @@ class CogTerrainLayer extends CompositeLayer {
6880
6908
  terrainUrl = '';
6881
6909
  async initializeState(context) {
6882
6910
  super.initializeState(context);
6911
+ const terrainCogTiles = this.props.cogTiles || new CogTiles(this.props.terrainOptions);
6883
6912
  this.setState({
6884
- terrainCogTiles: this.props.cogTiles || new CogTiles(this.props.terrainOptions),
6913
+ terrainCogTiles,
6885
6914
  initialized: false,
6886
6915
  });
6887
- await this.init(this.terrainUrl);
6916
+ // Only initialize if not already done (e.g., provided cogTiles instance may be pre-initialized)
6917
+ if (!terrainCogTiles.cog) {
6918
+ await this.init();
6919
+ }
6920
+ else {
6921
+ // CogTiles already initialized; just extract zoom range and mark ready
6922
+ const zoomRange = terrainCogTiles.getZoomRange();
6923
+ const [minZoom, maxZoom] = zoomRange;
6924
+ this.setState({ initialized: true, minZoom, maxZoom });
6925
+ }
6888
6926
  }
6889
6927
  async init() {
6890
6928
  await this.state.terrainCogTiles.initializeCog(this.props.elevationData);