@gisatcz/deckgl-geolib 2.5.0-dev.3 → 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
@@ -7,6 +7,45 @@ import { getMeshBoundingBox } from '@loaders.gl/schema';
7
7
  import { concatenateTypedArrays } from '@loaders.gl/loader-utils';
8
8
  import { SimpleMeshLayer } from '@deck.gl/mesh-layers';
9
9
 
10
+ let isListenerAttached = false;
11
+ /**
12
+ * Suppresses unhandled AbortErrors from deck.gl tile cancellation.
13
+ *
14
+ * NOTE: The library's main entry point installs this handler automatically
15
+ * when the package is imported via its primary build (for example
16
+ * `import '@gisatcz/deckgl-geolib'`). This default prevents console spam during
17
+ * normal tile cancellation (pan/zoom) for the vast majority of consumers.
18
+ *
19
+ * If you need to control installation manually (for example when importing
20
+ * internals or for custom lifecycle control), import and call the exported
21
+ * function yourself:
22
+ *
23
+ * import { suppressGlobalAbortErrors } from '@gisatcz/deckgl-geolib';
24
+ * suppressGlobalAbortErrors();
25
+ *
26
+ * Warning: This suppresses ALL unhandled AbortErrors (including from your own
27
+ * code). If you need finer control, implement a custom `unhandledrejection`
28
+ * handler instead.
29
+ *
30
+ * The listener is attached only once and only in browser environments,
31
+ * making this function idempotent and safe to call multiple times.
32
+ */
33
+ function suppressGlobalAbortErrors() {
34
+ // Ensure we are in a browser environment and haven't already attached the listener
35
+ if (typeof window !== 'undefined' && !isListenerAttached) {
36
+ window.addEventListener('unhandledrejection', (event) => {
37
+ // Suppress standard AbortErrors from tile cancellation and fetch aborts.
38
+ // These are expected during viewport changes and represent normal control flow,
39
+ // not application errors.
40
+ if (event.reason && event.reason.name === 'AbortError') {
41
+ // Prevent the browser from logging it to the console
42
+ event.preventDefault();
43
+ }
44
+ });
45
+ isListenerAttached = true;
46
+ }
47
+ }
48
+
10
49
  /* eslint-disable no-restricted-globals, no-restricted-syntax */
11
50
  /* global SharedArrayBuffer */
12
51
 
@@ -2957,7 +2996,7 @@ class CustomAggregateError extends Error {
2957
2996
  this.name = 'AggregateError';
2958
2997
  }
2959
2998
  }
2960
- const AggregateError = CustomAggregateError;
2999
+ const AggregateError$1 = CustomAggregateError;
2961
3000
 
2962
3001
  class Block {
2963
3002
  /**
@@ -3088,7 +3127,7 @@ class BlockedSource extends BaseSource {
3088
3127
  const blocks = allBlockIds.map((id) => this.blockCache.get(id) || this.evictedBlocks.get(id));
3089
3128
  const failedBlocks = blocks.filter((i) => !i);
3090
3129
  if (failedBlocks.length) {
3091
- throw new AggregateError(failedBlocks, 'Request failed');
3130
+ throw new AggregateError$1(failedBlocks, 'Request failed');
3092
3131
  }
3093
3132
  // create a final Map, with all required blocks for this request to satisfy
3094
3133
  const requiredBlocks = new Map(zip(allBlockIds, blocks));
@@ -5547,14 +5586,14 @@ function addSkirt(attributes, triangles, skirtHeight, outsideIndices) {
5547
5586
  * @returns {number[][]} - outside edges data
5548
5587
  */
5549
5588
  function getOutsideEdgesFromTriangles(triangles) {
5550
- // Use integer keys instead of strings: min * 70000 + max is collision-free
5551
- // for any grid 257×257 (66,049 vertices < 70,000)
5589
+ // Use BigInt keys to avoid collisions for large meshes.
5590
+ // Pack min and max into a single BigInt key: (min << 32) | max
5552
5591
  const edgeMap = new Map();
5553
5592
  const processEdge = (a, b) => {
5554
5593
  const min = Math.min(a, b);
5555
5594
  const max = Math.max(a, b);
5556
- // Integer key: no string allocation per edge
5557
- const key = min * 70000 + max;
5595
+ // Pack indices into a single BigInt key to avoid string allocation and collisions
5596
+ const key = (BigInt(min) << 32n) | BigInt(max);
5558
5597
  if (edgeMap.has(key)) {
5559
5598
  edgeMap.delete(key); // Interior edge, remove
5560
5599
  }
@@ -6679,11 +6718,37 @@ class CogTiles {
6679
6718
  bounds = [0, 0, 0, 0];
6680
6719
  geo = new GeoImage();
6681
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
+ }
6682
6745
  // Cache GeoTIFFImage Promises by index to prevent redundant HTTP requests from geotiff 3.0.4+ eager loading
6683
6746
  // Stores Promises (not resolved values) so concurrent requests share the same getImage() call
6684
6747
  imageCache = new Map();
6685
6748
  // Store initialization promise to prevent concurrent duplicate initializations
6686
6749
  initializePromise;
6750
+ // Track the last successfully initialized URL to detect URL changes
6751
+ lastInitializedUrl;
6687
6752
  constructor(options) {
6688
6753
  this.options = { ...CogTilesGeoImageOptionsDefaults, ...options };
6689
6754
  }
@@ -6691,6 +6756,10 @@ class CogTiles {
6691
6756
  // Return existing initialization promise if already in progress (prevents concurrent duplicates)
6692
6757
  if (this.initializePromise)
6693
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
+ }
6694
6763
  if (this.cog)
6695
6764
  return;
6696
6765
  this.initializePromise = (async () => {
@@ -6720,6 +6789,8 @@ class CogTiles {
6720
6789
  }
6721
6790
  this.zoomRange = this.calculateZoomRange(this.tileSize, image.getResolution()[0], await this.cog.getImageCount());
6722
6791
  this.bounds = this.calculateBoundsAsLatLon(image.getBoundingBox());
6792
+ // Mark initialization complete for this URL (used to detect URL changes)
6793
+ this.lastInitializedUrl = url;
6723
6794
  }
6724
6795
  catch (error) {
6725
6796
  // Reset initialization promise on error so retry can be attempted
@@ -6825,120 +6896,161 @@ class CogTiles {
6825
6896
  return this.cogZoomLookup.length - 1;
6826
6897
  // For zoom levels within the available range, find the exact or closest matching index.
6827
6898
  const exactMatchIndex = this.cogZoomLookup.indexOf(zoom);
6828
- if (exactMatchIndex === -1) {
6829
- // TO DO improve the condition if the match index is not found
6830
- /* eslint-disable no-console */
6831
- console.log('getImageIndexForZoomLevel: error in retrieving image by zoom index');
6832
- }
6833
- return exactMatchIndex;
6834
- }
6835
- async getTileFromImage(tileX, tileY, zoom, fetchSize) {
6836
- const imageIndex = this.getImageIndexForZoomLevel(zoom);
6837
- // Cache Promises to share in-flight requests across concurrent tiles at the same overview
6838
- let imagePromise = this.imageCache.get(imageIndex);
6839
- if (!imagePromise) {
6840
- imagePromise = this.cog.getImage(imageIndex);
6841
- this.imageCache.set(imageIndex, imagePromise);
6842
- }
6843
- const targetImage = await imagePromise;
6844
- // --- STEP 1: CALCULATE BOUNDS IN METERS ---
6845
- // 2. Get COG Metadata (image = COG)
6846
- const imageResolution = this.cogResolutionLookup[imageIndex];
6847
- const imageHeight = targetImage.getHeight();
6848
- const imageWidth = targetImage.getWidth();
6849
- const [imgOriginX, imgOriginY] = this.cogOrigin;
6850
- // 3. Define Web Mercator Constants
6851
- // We use the class property tileSize (usually 256) as the ground truth for grid calculations
6852
- const TILE_SIZE = this.tileSize;
6853
- const ORIGIN_X = webMercatorOrigin[0];
6854
- const ORIGIN_Y = webMercatorOrigin[1];
6855
- // 4. Calculate Tile BBox in World Meters
6856
- // This defines where the map expects the tile to be physically located
6857
- const tileGridResolution = (EARTH_CIRCUMFERENCE / TILE_SIZE) / (2 ** zoom);
6858
- const tileMinXMeters = ORIGIN_X + (tileX * TILE_SIZE * tileGridResolution);
6859
- const tileMaxYMeters = ORIGIN_Y - (tileY * TILE_SIZE * tileGridResolution);
6860
- // Note: We don't strictly need MaxX/MinY meters for the start calculation,
6861
- // but they are useful if debugging the full meter footprint.
6862
- // --- STEP 2: CONVERT TO PIXEL COORDINATES ---
6863
- // 5. Calculate precise floating-point start position relative to the image
6864
- const windowMinX = (tileMinXMeters - imgOriginX) / imageResolution;
6865
- const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
6866
- // 6. Snap to Integer Grid (The "Force 256" Fix)
6867
- // We round the start position to align with the nearest pixel.
6868
- const FETCH_SIZE = fetchSize || TILE_SIZE; // Default to 256 if not provided
6869
- const startX = Math.round(windowMinX);
6870
- const startY = Math.round(windowMinY);
6871
- const endX = startX + FETCH_SIZE;
6872
- const endY = startY + FETCH_SIZE;
6873
- // --- STEP 3: CALCULATE INTERSECTION ---
6874
- // 7. Clamp the read window to the actual image dimensions
6875
- // This defines the "Safe" area we can actually read from the file.
6876
- const validReadX = Math.max(0, startX);
6877
- const validReadY = Math.max(0, startY);
6878
- const validReadMaxX = Math.min(imageWidth, endX);
6879
- const validReadMaxY = Math.min(imageHeight, endY);
6880
- const readWidth = validReadMaxX - validReadX;
6881
- const readHeight = validReadMaxY - validReadY;
6882
- // CHECK: If no overlap, return empty
6883
- if (readWidth <= 0 || readHeight <= 0) {
6884
- return [this.createEmptyTile(FETCH_SIZE)];
6885
- }
6886
- // 8. Calculate Offsets (Padding)
6887
- // "missingLeft" is how many blank pixels we need to insert before the image data starts.
6888
- // Logic: If we wanted to read from -50 (startX), but clamped to 0 (validReadX),
6889
- // we are missing the first 50 pixels.
6890
- const missingLeft = validReadX - startX;
6891
- const missingTop = validReadY - startY;
6892
- const window = [validReadX, validReadY, validReadMaxX, validReadMaxY];
6893
- // --- STEP 4: READ AND COMPOSITE ---
6894
- // Case A: Partial Overlap (Padding or Cropping required)
6895
- // If the tile is hanging off the edge, we need to manually reconstruct it.
6896
- // We strictly compare against FETCH_SIZE because that is our target buffer dimension.
6897
- if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
6898
- const numChannels = this.options.numOfChannels || 1;
6899
- // Initialize with a TypedArray of the full target size and correct data type
6900
- const validImageData = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE, numChannels);
6901
- if (this.options.noDataValue !== undefined) {
6902
- validImageData.fill(this.options.noDataValue);
6903
- }
6904
- // 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
6905
- const validRasterData = await targetImage.readRasters({ window });
6906
- // Place the valid pixel data into the tile buffer.
6907
- for (let band = 0; band < validRasterData.length; band += 1) {
6908
- // We must reset the buffer for each band, otherwise data from previous band persists in padding areas
6909
- const tileBuffer = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE);
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
+ }
6911
+ }
6912
+ return closestIndex;
6913
+ }
6914
+ async getTileFromImage(tileX, tileY, zoom, fetchSize, signal) {
6915
+ // Create a fresh local AbortController for this specific fetch.
6916
+ // We do NOT pass `signal` directly to readRasters because deck.gl may reuse tile
6917
+ // objects whose signal is already aborted (same tile re-requested after viewport change).
6918
+ // An already-aborted signal passed to geotiff.js immediately cancels the fetch,
6919
+ // leaving the tile permanently empty. Instead, we only forward cancellation when
6920
+ // the signal fires WHILE the request is actually in flight.
6921
+ const controller = new AbortController();
6922
+ if (signal && !signal.aborted) {
6923
+ signal.addEventListener('abort', () => controller.abort(), { once: true });
6924
+ }
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
+ }
6932
+ try {
6933
+ const imageIndex = this.getImageIndexForZoomLevel(zoom);
6934
+ // Cache Promises to share in-flight requests across concurrent tiles at the same overview
6935
+ let imagePromise = this.imageCache.get(imageIndex);
6936
+ if (!imagePromise) {
6937
+ imagePromise = this.cog.getImage(imageIndex);
6938
+ this.imageCache.set(imageIndex, imagePromise);
6939
+ }
6940
+ const targetImage = await imagePromise;
6941
+ // --- STEP 1: CALCULATE BOUNDS IN METERS ---
6942
+ // 2. Get COG Metadata (image = COG)
6943
+ const imageResolution = this.cogResolutionLookup[imageIndex];
6944
+ const imageHeight = targetImage.getHeight();
6945
+ const imageWidth = targetImage.getWidth();
6946
+ const [imgOriginX, imgOriginY] = this.cogOrigin;
6947
+ // 3. Define Web Mercator Constants
6948
+ // We use the class property tileSize (usually 256) as the ground truth for grid calculations
6949
+ const TILE_SIZE = this.tileSize;
6950
+ const ORIGIN_X = webMercatorOrigin[0];
6951
+ const ORIGIN_Y = webMercatorOrigin[1];
6952
+ // 4. Calculate Tile BBox in World Meters
6953
+ // This defines where the map expects the tile to be physically located
6954
+ const tileGridResolution = (EARTH_CIRCUMFERENCE / TILE_SIZE) / (2 ** zoom);
6955
+ const tileMinXMeters = ORIGIN_X + (tileX * TILE_SIZE * tileGridResolution);
6956
+ const tileMaxYMeters = ORIGIN_Y - (tileY * TILE_SIZE * tileGridResolution);
6957
+ // Note: We don't strictly need MaxX/MinY meters for the start calculation,
6958
+ // but they are useful if debugging the full meter footprint.
6959
+ // --- STEP 2: CONVERT TO PIXEL COORDINATES ---
6960
+ // 5. Calculate precise floating-point start position relative to the image
6961
+ const windowMinX = (tileMinXMeters - imgOriginX) / imageResolution;
6962
+ const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
6963
+ // 6. Snap to Integer Grid (The "Force 256" Fix)
6964
+ // We round the start position to align with the nearest pixel.
6965
+ const FETCH_SIZE = fetchSize || TILE_SIZE; // Default to 256 if not provided
6966
+ const startX = Math.round(windowMinX);
6967
+ const startY = Math.round(windowMinY);
6968
+ const endX = startX + FETCH_SIZE;
6969
+ const endY = startY + FETCH_SIZE;
6970
+ // --- STEP 3: CALCULATE INTERSECTION ---
6971
+ // 7. Clamp the read window to the actual image dimensions
6972
+ // This defines the "Safe" area we can actually read from the file.
6973
+ const validReadX = Math.max(0, startX);
6974
+ const validReadY = Math.max(0, startY);
6975
+ const validReadMaxX = Math.min(imageWidth, endX);
6976
+ const validReadMaxY = Math.min(imageHeight, endY);
6977
+ const readWidth = validReadMaxX - validReadX;
6978
+ const readHeight = validReadMaxY - validReadY;
6979
+ // CHECK: If no overlap, return empty
6980
+ if (readWidth <= 0 || readHeight <= 0) {
6981
+ return [this.createEmptyTile(FETCH_SIZE)];
6982
+ }
6983
+ // 8. Calculate Offsets (Padding)
6984
+ // "missingLeft" is how many blank pixels we need to insert before the image data starts.
6985
+ // Logic: If we wanted to read from -50 (startX), but clamped to 0 (validReadX),
6986
+ // we are missing the first 50 pixels.
6987
+ const missingLeft = validReadX - startX;
6988
+ const missingTop = validReadY - startY;
6989
+ const window = [validReadX, validReadY, validReadMaxX, validReadMaxY];
6990
+ // --- STEP 4: READ AND COMPOSITE ---
6991
+ // Case A: Partial Overlap (Padding or Cropping required)
6992
+ // If the tile is hanging off the edge, we need to manually reconstruct it.
6993
+ // We strictly compare against FETCH_SIZE because that is our target buffer dimension.
6994
+ if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
6995
+ const numChannels = this.options.numOfChannels || 1;
6996
+ // Initialize with a TypedArray of the full target size and correct data type
6997
+ const validImageData = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE, numChannels);
6910
6998
  if (this.options.noDataValue !== undefined) {
6911
- tileBuffer.fill(this.options.noDataValue);
6999
+ validImageData.fill(this.options.noDataValue);
6912
7000
  }
6913
- for (let row = 0; row < readHeight; row += 1) {
6914
- const destRow = missingTop + row;
6915
- const destRowOffset = destRow * FETCH_SIZE;
6916
- const srcRowOffset = row * validRasterData.width;
6917
- for (let col = 0; col < readWidth; col += 1) {
6918
- // Compute the destination position in the tile buffer.
6919
- const destCol = missingLeft + col;
6920
- // Bounds Check: Ensure we don't write outside the allocated buffer
6921
- if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
6922
- tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
6923
- }
6924
- else {
6925
- /* eslint-disable no-console */
6926
- console.log(`error in assigning data to tile buffer: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
7001
+ // 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
7002
+ const validRasterData = await targetImage.readRasters({ window, signal: localSignal });
7003
+ // Place the valid pixel data into the tile buffer.
7004
+ for (let band = 0; band < validRasterData.length; band += 1) {
7005
+ // We must reset the buffer for each band, otherwise data from previous band persists in padding areas
7006
+ const tileBuffer = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE);
7007
+ if (this.options.noDataValue !== undefined) {
7008
+ tileBuffer.fill(this.options.noDataValue);
7009
+ }
7010
+ for (let row = 0; row < readHeight; row += 1) {
7011
+ const destRow = missingTop + row;
7012
+ const destRowOffset = destRow * FETCH_SIZE;
7013
+ const srcRowOffset = row * validRasterData.width;
7014
+ for (let col = 0; col < readWidth; col += 1) {
7015
+ // Compute the destination position in the tile buffer.
7016
+ const destCol = missingLeft + col;
7017
+ // Bounds Check: Ensure we don't write outside the allocated buffer
7018
+ if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
7019
+ tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
7020
+ }
7021
+ else {
7022
+ console.error(`[CogTiles] tile buffer bounds exceeded: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
7023
+ }
6927
7024
  }
6928
7025
  }
7026
+ for (let i = 0; i < tileBuffer.length; i += 1) {
7027
+ validImageData[i * numChannels + band] = tileBuffer[i];
7028
+ }
6929
7029
  }
6930
- for (let i = 0; i < tileBuffer.length; i += 1) {
6931
- validImageData[i * numChannels + band] = tileBuffer[i];
6932
- }
7030
+ // Mark raster as cached after successful fetch
7031
+ this.setCachedRaster(cacheKey, validImageData); // for partial overlap
7032
+ return [validImageData];
6933
7033
  }
6934
- return [validImageData];
7034
+ // Case B: Perfect Match (Optimization)
7035
+ // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
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
7039
+ return [tileData];
7040
+ }
7041
+ catch (error) {
7042
+ // If the signal was aborted (or geotiff.js threw AggregateError wrapping an abort),
7043
+ // re-throw as a standard AbortError so deck.gl handles tile cancellation gracefully
7044
+ // and suppressGlobalAbortErrors() can suppress the unhandled rejection noise.
7045
+ const isAbortRelated = localSignal.aborted
7046
+ || (error instanceof AggregateError && error.errors?.some((e) => e?.name === 'AbortError' || e?.message?.includes('aborted') || e?.message?.includes('abort')))
7047
+ || (error instanceof DOMException && error.name === 'AbortError')
7048
+ || (error instanceof Error && error.message === 'Request was aborted');
7049
+ if (isAbortRelated) {
7050
+ throw new DOMException('Tile request aborted', 'AbortError');
7051
+ }
7052
+ throw error;
6935
7053
  }
6936
- // Case B: Perfect Match (Optimization)
6937
- // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
6938
- // console.log("Perfect aligned read");
6939
- const tileData = await targetImage.readRasters({ window, interleave: true });
6940
- // console.log(`data that starts at the left top corner of the tile ${tileX}, ${tileY}`);
6941
- return [tileData];
6942
7054
  }
6943
7055
  /**
6944
7056
  * Creates a blank tile buffer filled with the "No Data" value.
@@ -6954,7 +7066,7 @@ class CogTiles {
6954
7066
  }
6955
7067
  return tileData;
6956
7068
  }
6957
- async getTile(x, y, z, bounds, meshMaxError) {
7069
+ async getTile(x, y, z, bounds, meshMaxError, signal) {
6958
7070
  let requiredSize = this.tileSize; // Default 256 for image/bitmap
6959
7071
  if (this.options.type === 'terrain') {
6960
7072
  const isKernel = this.options.useSlope || this.options.useHillshade || this.options.useSwissRelief;
@@ -6964,7 +7076,7 @@ class CogTiles {
6964
7076
  // Bitmap layer with relief glaze mode needs kernel padding for slope/hillshade computation
6965
7077
  requiredSize = this.tileSize + 2; // 258 for kernel
6966
7078
  }
6967
- const tileData = await this.getTileFromImage(x, y, z, requiredSize);
7079
+ const tileData = await this.getTileFromImage(x, y, z, requiredSize, signal);
6968
7080
  // Compute true ground cell size in meters from tile indices.
6969
7081
  // Tile y in slippy-map convention → center latitude → Web Mercator distortion correction.
6970
7082
  const latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 0.5) / Math.pow(2, z))));
@@ -6984,13 +7096,17 @@ class CogTiles {
6984
7096
  tileWidth = this.tileSize;
6985
7097
  tileHeight = this.tileSize;
6986
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
+ }
6987
7103
  return this.geo.getMap({
6988
7104
  rasters,
6989
7105
  width: tileWidth,
6990
7106
  height: tileHeight,
6991
- bounds,
7107
+ bounds: bounds ?? [0, 0, 0, 0],
6992
7108
  cellSizeMeters,
6993
- }, this.options, meshMaxError);
7109
+ }, this.options, meshMaxError ?? 4.0);
6994
7110
  }
6995
7111
  /**
6996
7112
  * Determines the data type (e.g., "Int32", "Float64") of a GeoTIFF image
@@ -7200,20 +7316,11 @@ class CogBitmapLayer extends CompositeLayer {
7200
7316
  }
7201
7317
  }
7202
7318
  async getTiledBitmapData(tile) {
7203
- try {
7204
- // TODO - pass signal to getTile
7205
- // abort request if signal is aborted
7206
- const tileData = await this.state.bitmapCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z);
7207
- if (tileData && !this.props.pickable) {
7208
- tileData.raw = null;
7209
- }
7210
- return tileData;
7211
- }
7212
- catch (error) {
7213
- // Log the error and rethrow so TileLayer can surface the failure via onTileError
7214
- log.warn(`Failed to load bitmap tile at ${tile.index.z}/${tile.index.x}/${tile.index.y}:`, error)();
7215
- throw error;
7319
+ const resolvedTileData = await this.state.bitmapCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, undefined, undefined, tile.signal);
7320
+ if (resolvedTileData && !this.props.pickable) {
7321
+ resolvedTileData.raw = null;
7216
7322
  }
7323
+ return resolvedTileData;
7217
7324
  }
7218
7325
  renderSubLayers(props) {
7219
7326
  const SubLayerClass = this.getSubLayerClass('image', BitmapLayer);
@@ -7347,7 +7454,6 @@ function urlTemplateToUpdateTrigger(template) {
7347
7454
  }
7348
7455
  // TODO remove elevationDecoder
7349
7456
  // TODO use meshMaxError
7350
- // TODO - pass signal to getTile
7351
7457
  /** Render mesh surfaces from height map images. */
7352
7458
  class CogTerrainLayer extends CompositeLayer {
7353
7459
  static defaultProps = defaultProps;
@@ -7446,13 +7552,11 @@ class CogTerrainLayer extends CompositeLayer {
7446
7552
  topRight = [bbox.right, bbox.top];
7447
7553
  }
7448
7554
  const bounds = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
7449
- // TODO - pass signal to getTile
7450
- // abort request if signal is aborted
7451
- const terrain = await this.state.terrainCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, bounds, this.props.meshMaxError);
7452
- if (terrain && !this.props.pickable) {
7453
- terrain.raw = null;
7555
+ const resolvedTerrain = await this.state.terrainCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, bounds, this.props.meshMaxError, tile.signal);
7556
+ if (resolvedTerrain && !this.props.pickable) {
7557
+ resolvedTerrain.raw = null;
7454
7558
  }
7455
- return Promise.all([terrain, null]);
7559
+ return Promise.all([resolvedTerrain, null]);
7456
7560
  }
7457
7561
  renderSubLayers(props) {
7458
7562
  const SubLayerClass = this.getSubLayerClass('mesh', SimpleMeshLayer);
@@ -7561,6 +7665,10 @@ class CogTerrainLayer extends CompositeLayer {
7561
7665
  }
7562
7666
  }
7563
7667
 
7668
+ // src/index.ts
7669
+ // Initialize global error suppression for deck.gl AbortErrors
7670
+ suppressGlobalAbortErrors();
7671
+
7564
7672
  class RawDecoder extends BaseDecoder {
7565
7673
  /** @param {ArrayBuffer} buffer */
7566
7674
  decodeBlock(buffer) {
@@ -7569,8 +7677,8 @@ class RawDecoder extends BaseDecoder {
7569
7677
  }
7570
7678
 
7571
7679
  var raw = /*#__PURE__*/Object.freeze({
7572
- __proto__: null,
7573
- default: RawDecoder
7680
+ __proto__: null,
7681
+ default: RawDecoder
7574
7682
  });
7575
7683
 
7576
7684
  const MIN_BITS = 9;
@@ -7728,8 +7836,8 @@ class LZWDecoder extends BaseDecoder {
7728
7836
  }
7729
7837
 
7730
7838
  var lzw = /*#__PURE__*/Object.freeze({
7731
- __proto__: null,
7732
- default: LZWDecoder
7839
+ __proto__: null,
7840
+ default: LZWDecoder
7733
7841
  });
7734
7842
 
7735
7843
  /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
@@ -8792,8 +8900,8 @@ class JpegDecoder extends BaseDecoder {
8792
8900
  }
8793
8901
 
8794
8902
  var jpeg = /*#__PURE__*/Object.freeze({
8795
- __proto__: null,
8796
- default: JpegDecoder
8903
+ __proto__: null,
8904
+ default: JpegDecoder
8797
8905
  });
8798
8906
 
8799
8907
  /*============================================================================*/
@@ -12039,8 +12147,8 @@ class DeflateDecoder extends BaseDecoder {
12039
12147
  }
12040
12148
 
12041
12149
  var deflate = /*#__PURE__*/Object.freeze({
12042
- __proto__: null,
12043
- default: DeflateDecoder
12150
+ __proto__: null,
12151
+ default: DeflateDecoder
12044
12152
  });
12045
12153
 
12046
12154
  class PackbitsDecoder extends BaseDecoder {
@@ -12070,8 +12178,8 @@ class PackbitsDecoder extends BaseDecoder {
12070
12178
  }
12071
12179
 
12072
12180
  var packbits = /*#__PURE__*/Object.freeze({
12073
- __proto__: null,
12074
- default: PackbitsDecoder
12181
+ __proto__: null,
12182
+ default: PackbitsDecoder
12075
12183
  });
12076
12184
 
12077
12185
  function getDefaultExportFromCjs (x) {
@@ -14525,9 +14633,9 @@ class LercDecoder extends BaseDecoder {
14525
14633
  }
14526
14634
 
14527
14635
  var lerc = /*#__PURE__*/Object.freeze({
14528
- __proto__: null,
14529
- default: LercDecoder,
14530
- zstd: zstd$2
14636
+ __proto__: null,
14637
+ default: LercDecoder,
14638
+ zstd: zstd$2
14531
14639
  });
14532
14640
 
14533
14641
  /**
@@ -14701,9 +14809,9 @@ class ZstdDecoder extends BaseDecoder {
14701
14809
  }
14702
14810
 
14703
14811
  var zstd$1 = /*#__PURE__*/Object.freeze({
14704
- __proto__: null,
14705
- default: ZstdDecoder,
14706
- zstd: zstd
14812
+ __proto__: null,
14813
+ default: ZstdDecoder,
14814
+ zstd: zstd
14707
14815
  });
14708
14816
 
14709
14817
  /**
@@ -14766,9 +14874,9 @@ class WebImageDecoder extends BaseDecoder {
14766
14874
  }
14767
14875
 
14768
14876
  var webimage = /*#__PURE__*/Object.freeze({
14769
- __proto__: null,
14770
- default: WebImageDecoder
14877
+ __proto__: null,
14878
+ default: WebImageDecoder
14771
14879
  });
14772
14880
 
14773
- export { CogBitmapLayer, CogTerrainLayer, CogTiles, GeoImage };
14881
+ export { CogBitmapLayer, CogTerrainLayer, CogTiles, GeoImage, suppressGlobalAbortErrors };
14774
14882
  //# sourceMappingURL=index.js.map