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