@gisatcz/deckgl-geolib 2.5.0-dev.2 → 2.5.0-dev.4

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));
@@ -4872,6 +4911,7 @@ const DefaultGeoImageOptions = {
4872
4911
  useChannelIndex: null,
4873
4912
  noDataValue: undefined,
4874
4913
  multiplier: 1.0,
4914
+ verticalExaggeration: 1.0,
4875
4915
  numOfChannels: undefined,
4876
4916
  planarConfig: undefined,
4877
4917
  // --- Mesh generation (terrain only) ---
@@ -5546,25 +5586,31 @@ function addSkirt(attributes, triangles, skirtHeight, outsideIndices) {
5546
5586
  * @returns {number[][]} - outside edges data
5547
5587
  */
5548
5588
  function getOutsideEdgesFromTriangles(triangles) {
5549
- const edges = [];
5550
- for (let i = 0; i < triangles.length; i += 3) {
5551
- edges.push([triangles[i], triangles[i + 1]]);
5552
- edges.push([triangles[i + 1], triangles[i + 2]]);
5553
- edges.push([triangles[i + 2], triangles[i]]);
5554
- }
5555
- edges.sort((a, b) => Math.min(...a) - Math.min(...b) || Math.max(...a) - Math.max(...b));
5556
- const outsideEdges = [];
5557
- let index = 0;
5558
- while (index < edges.length) {
5559
- if (edges[index][0] === edges[index + 1]?.[1] && edges[index][1] === edges[index + 1]?.[0]) {
5560
- index += 2;
5589
+ // Use BigInt keys to avoid collisions for large meshes.
5590
+ // Pack min and max into a single BigInt key: (min << 32) | max
5591
+ const edgeMap = new Map();
5592
+ const processEdge = (a, b) => {
5593
+ const min = Math.min(a, b);
5594
+ const max = Math.max(a, b);
5595
+ // Pack indices into a single BigInt key to avoid string allocation and collisions
5596
+ const key = (BigInt(min) << 32n) | BigInt(max);
5597
+ if (edgeMap.has(key)) {
5598
+ edgeMap.delete(key); // Interior edge, remove
5561
5599
  }
5562
5600
  else {
5563
- outsideEdges.push(edges[index]);
5564
- index++;
5601
+ edgeMap.set(key, [a, b]);
5565
5602
  }
5603
+ };
5604
+ for (let i = 0; i < triangles.length; i += 3) {
5605
+ const v0 = triangles[i];
5606
+ const v1 = triangles[i + 1];
5607
+ const v2 = triangles[i + 2];
5608
+ // Process each edge inline — no temporary array allocation per triangle
5609
+ processEdge(v0, v1);
5610
+ processEdge(v1, v2);
5611
+ processEdge(v2, v0);
5566
5612
  }
5567
- return outsideEdges;
5613
+ return Array.from(edgeMap.values());
5568
5614
  }
5569
5615
  /**
5570
5616
  * Get geometry edges that located on a border of the mesh
@@ -6282,7 +6328,7 @@ class TerrainGenerator {
6282
6328
  const meshWidth = isKernel ? 257 : width;
6283
6329
  const meshHeight = isKernel ? 257 : height;
6284
6330
  // 2. Tesselate (Generate Mesh)
6285
- const { terrainSkirtHeight } = options;
6331
+ const { terrainSkirtHeight, verticalExaggeration = 1.0 } = options;
6286
6332
  let mesh;
6287
6333
  switch (options.tesselator) {
6288
6334
  case 'martini':
@@ -6298,13 +6344,17 @@ class TerrainGenerator {
6298
6344
  }
6299
6345
  const { vertices } = mesh;
6300
6346
  let { triangles } = mesh;
6301
- let attributes = this.getMeshAttributes(vertices, meshTerrain, meshWidth, meshHeight, input.bounds);
6347
+ let attributes = this.getMeshAttributes(vertices, meshTerrain, meshWidth, meshHeight, input.bounds, verticalExaggeration);
6302
6348
  // Compute bounding box before adding skirt so that z values are not skewed
6303
6349
  const boundingBox = getMeshBoundingBox(attributes);
6304
6350
  if (terrainSkirtHeight) {
6305
- const { attributes: newAttributes, triangles: newTriangles } = addSkirt(attributes, triangles, terrainSkirtHeight);
6306
- attributes = newAttributes;
6307
- triangles = newTriangles;
6351
+ const scaledSkirtHeight = terrainSkirtHeight * verticalExaggeration;
6352
+ // Skip skirt generation if scaled height is zero (e.g., verticalExaggeration = 0)
6353
+ if (scaledSkirtHeight > 0) {
6354
+ const { attributes: newAttributes, triangles: newTriangles } = addSkirt(attributes, triangles, scaledSkirtHeight);
6355
+ attributes = newAttributes;
6356
+ triangles = newTriangles;
6357
+ }
6308
6358
  }
6309
6359
  const map = {
6310
6360
  // Data return by this loader implementation
@@ -6500,10 +6550,10 @@ class TerrainGenerator {
6500
6550
  const vertices = coords;
6501
6551
  return { vertices, triangles };
6502
6552
  }
6503
- static getMeshAttributes(vertices, terrain, width, height, bounds) {
6553
+ static getMeshAttributes(vertices, terrain, width, height, bounds, verticalExaggeration = 1.0) {
6504
6554
  const gridSize = width === 257 ? 257 : width + 1;
6505
6555
  const numOfVerticies = vertices.length / 2;
6506
- // vec3. x, y in pixels, z in meters
6556
+ // vec3. x, y in pixels, z in meters (scaled by verticalExaggeration)
6507
6557
  const positions = new Float32Array(numOfVerticies * 3);
6508
6558
  // vec2. 1 to 1 relationship with position. represents the uv on the texture image. 0,0 to 1,1.
6509
6559
  const texCoords = new Float32Array(numOfVerticies * 2);
@@ -6520,7 +6570,7 @@ class TerrainGenerator {
6520
6570
  const pixelIdx = y * gridSize + x;
6521
6571
  positions[3 * i] = x * xScale + minX;
6522
6572
  positions[3 * i + 1] = -y * yScale + maxY;
6523
- positions[3 * i + 2] = terrain[pixelIdx];
6573
+ positions[3 * i + 2] = terrain[pixelIdx] * verticalExaggeration;
6524
6574
  texCoords[2 * i] = x / effectiveWidth;
6525
6575
  texCoords[2 * i + 1] = y / effectiveHeight;
6526
6576
  }
@@ -6821,113 +6871,137 @@ class CogTiles {
6821
6871
  }
6822
6872
  return exactMatchIndex;
6823
6873
  }
6824
- async getTileFromImage(tileX, tileY, zoom, fetchSize) {
6825
- const imageIndex = this.getImageIndexForZoomLevel(zoom);
6826
- // Cache Promises to share in-flight requests across concurrent tiles at the same overview
6827
- let imagePromise = this.imageCache.get(imageIndex);
6828
- if (!imagePromise) {
6829
- imagePromise = this.cog.getImage(imageIndex);
6830
- this.imageCache.set(imageIndex, imagePromise);
6831
- }
6832
- const targetImage = await imagePromise;
6833
- // --- STEP 1: CALCULATE BOUNDS IN METERS ---
6834
- // 2. Get COG Metadata (image = COG)
6835
- const imageResolution = this.cogResolutionLookup[imageIndex];
6836
- const imageHeight = targetImage.getHeight();
6837
- const imageWidth = targetImage.getWidth();
6838
- const [imgOriginX, imgOriginY] = this.cogOrigin;
6839
- // 3. Define Web Mercator Constants
6840
- // We use the class property tileSize (usually 256) as the ground truth for grid calculations
6841
- const TILE_SIZE = this.tileSize;
6842
- const ORIGIN_X = webMercatorOrigin[0];
6843
- const ORIGIN_Y = webMercatorOrigin[1];
6844
- // 4. Calculate Tile BBox in World Meters
6845
- // This defines where the map expects the tile to be physically located
6846
- const tileGridResolution = (EARTH_CIRCUMFERENCE / TILE_SIZE) / (2 ** zoom);
6847
- const tileMinXMeters = ORIGIN_X + (tileX * TILE_SIZE * tileGridResolution);
6848
- const tileMaxYMeters = ORIGIN_Y - (tileY * TILE_SIZE * tileGridResolution);
6849
- // Note: We don't strictly need MaxX/MinY meters for the start calculation,
6850
- // but they are useful if debugging the full meter footprint.
6851
- // --- STEP 2: CONVERT TO PIXEL COORDINATES ---
6852
- // 5. Calculate precise floating-point start position relative to the image
6853
- const windowMinX = (tileMinXMeters - imgOriginX) / imageResolution;
6854
- const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
6855
- // 6. Snap to Integer Grid (The "Force 256" Fix)
6856
- // We round the start position to align with the nearest pixel.
6857
- const FETCH_SIZE = fetchSize || TILE_SIZE; // Default to 256 if not provided
6858
- const startX = Math.round(windowMinX);
6859
- const startY = Math.round(windowMinY);
6860
- const endX = startX + FETCH_SIZE;
6861
- const endY = startY + FETCH_SIZE;
6862
- // --- STEP 3: CALCULATE INTERSECTION ---
6863
- // 7. Clamp the read window to the actual image dimensions
6864
- // This defines the "Safe" area we can actually read from the file.
6865
- const validReadX = Math.max(0, startX);
6866
- const validReadY = Math.max(0, startY);
6867
- const validReadMaxX = Math.min(imageWidth, endX);
6868
- const validReadMaxY = Math.min(imageHeight, endY);
6869
- const readWidth = validReadMaxX - validReadX;
6870
- const readHeight = validReadMaxY - validReadY;
6871
- // CHECK: If no overlap, return empty
6872
- if (readWidth <= 0 || readHeight <= 0) {
6873
- return [this.createEmptyTile(FETCH_SIZE)];
6874
- }
6875
- // 8. Calculate Offsets (Padding)
6876
- // "missingLeft" is how many blank pixels we need to insert before the image data starts.
6877
- // Logic: If we wanted to read from -50 (startX), but clamped to 0 (validReadX),
6878
- // we are missing the first 50 pixels.
6879
- const missingLeft = validReadX - startX;
6880
- const missingTop = validReadY - startY;
6881
- const window = [validReadX, validReadY, validReadMaxX, validReadMaxY];
6882
- // --- STEP 4: READ AND COMPOSITE ---
6883
- // Case A: Partial Overlap (Padding or Cropping required)
6884
- // If the tile is hanging off the edge, we need to manually reconstruct it.
6885
- // We strictly compare against FETCH_SIZE because that is our target buffer dimension.
6886
- if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
6887
- const numChannels = this.options.numOfChannels || 1;
6888
- // Initialize with a TypedArray of the full target size and correct data type
6889
- const validImageData = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE, numChannels);
6890
- if (this.options.noDataValue !== undefined) {
6891
- validImageData.fill(this.options.noDataValue);
6892
- }
6893
- // 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
6894
- const validRasterData = await targetImage.readRasters({ window });
6895
- // Place the valid pixel data into the tile buffer.
6896
- for (let band = 0; band < validRasterData.length; band += 1) {
6897
- // We must reset the buffer for each band, otherwise data from previous band persists in padding areas
6898
- const tileBuffer = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE);
6874
+ async getTileFromImage(tileX, tileY, zoom, fetchSize, signal) {
6875
+ // Create a fresh local AbortController for this specific fetch.
6876
+ // We do NOT pass `signal` directly to readRasters because deck.gl may reuse tile
6877
+ // objects whose signal is already aborted (same tile re-requested after viewport change).
6878
+ // An already-aborted signal passed to geotiff.js immediately cancels the fetch,
6879
+ // leaving the tile permanently empty. Instead, we only forward cancellation when
6880
+ // the signal fires WHILE the request is actually in flight.
6881
+ const controller = new AbortController();
6882
+ if (signal && !signal.aborted) {
6883
+ signal.addEventListener('abort', () => controller.abort(), { once: true });
6884
+ }
6885
+ const localSignal = controller.signal;
6886
+ try {
6887
+ const imageIndex = this.getImageIndexForZoomLevel(zoom);
6888
+ // Cache Promises to share in-flight requests across concurrent tiles at the same overview
6889
+ let imagePromise = this.imageCache.get(imageIndex);
6890
+ if (!imagePromise) {
6891
+ imagePromise = this.cog.getImage(imageIndex);
6892
+ this.imageCache.set(imageIndex, imagePromise);
6893
+ }
6894
+ const targetImage = await imagePromise;
6895
+ // --- STEP 1: CALCULATE BOUNDS IN METERS ---
6896
+ // 2. Get COG Metadata (image = COG)
6897
+ const imageResolution = this.cogResolutionLookup[imageIndex];
6898
+ const imageHeight = targetImage.getHeight();
6899
+ const imageWidth = targetImage.getWidth();
6900
+ const [imgOriginX, imgOriginY] = this.cogOrigin;
6901
+ // 3. Define Web Mercator Constants
6902
+ // We use the class property tileSize (usually 256) as the ground truth for grid calculations
6903
+ const TILE_SIZE = this.tileSize;
6904
+ const ORIGIN_X = webMercatorOrigin[0];
6905
+ const ORIGIN_Y = webMercatorOrigin[1];
6906
+ // 4. Calculate Tile BBox in World Meters
6907
+ // This defines where the map expects the tile to be physically located
6908
+ const tileGridResolution = (EARTH_CIRCUMFERENCE / TILE_SIZE) / (2 ** zoom);
6909
+ const tileMinXMeters = ORIGIN_X + (tileX * TILE_SIZE * tileGridResolution);
6910
+ const tileMaxYMeters = ORIGIN_Y - (tileY * TILE_SIZE * tileGridResolution);
6911
+ // Note: We don't strictly need MaxX/MinY meters for the start calculation,
6912
+ // but they are useful if debugging the full meter footprint.
6913
+ // --- STEP 2: CONVERT TO PIXEL COORDINATES ---
6914
+ // 5. Calculate precise floating-point start position relative to the image
6915
+ const windowMinX = (tileMinXMeters - imgOriginX) / imageResolution;
6916
+ const windowMinY = (imgOriginY - tileMaxYMeters) / imageResolution;
6917
+ // 6. Snap to Integer Grid (The "Force 256" Fix)
6918
+ // We round the start position to align with the nearest pixel.
6919
+ const FETCH_SIZE = fetchSize || TILE_SIZE; // Default to 256 if not provided
6920
+ const startX = Math.round(windowMinX);
6921
+ const startY = Math.round(windowMinY);
6922
+ const endX = startX + FETCH_SIZE;
6923
+ const endY = startY + FETCH_SIZE;
6924
+ // --- STEP 3: CALCULATE INTERSECTION ---
6925
+ // 7. Clamp the read window to the actual image dimensions
6926
+ // This defines the "Safe" area we can actually read from the file.
6927
+ const validReadX = Math.max(0, startX);
6928
+ const validReadY = Math.max(0, startY);
6929
+ const validReadMaxX = Math.min(imageWidth, endX);
6930
+ const validReadMaxY = Math.min(imageHeight, endY);
6931
+ const readWidth = validReadMaxX - validReadX;
6932
+ const readHeight = validReadMaxY - validReadY;
6933
+ // CHECK: If no overlap, return empty
6934
+ if (readWidth <= 0 || readHeight <= 0) {
6935
+ return [this.createEmptyTile(FETCH_SIZE)];
6936
+ }
6937
+ // 8. Calculate Offsets (Padding)
6938
+ // "missingLeft" is how many blank pixels we need to insert before the image data starts.
6939
+ // Logic: If we wanted to read from -50 (startX), but clamped to 0 (validReadX),
6940
+ // we are missing the first 50 pixels.
6941
+ const missingLeft = validReadX - startX;
6942
+ const missingTop = validReadY - startY;
6943
+ const window = [validReadX, validReadY, validReadMaxX, validReadMaxY];
6944
+ // --- STEP 4: READ AND COMPOSITE ---
6945
+ // Case A: Partial Overlap (Padding or Cropping required)
6946
+ // If the tile is hanging off the edge, we need to manually reconstruct it.
6947
+ // We strictly compare against FETCH_SIZE because that is our target buffer dimension.
6948
+ if (missingLeft > 0 || missingTop > 0 || readWidth < FETCH_SIZE || readHeight < FETCH_SIZE) {
6949
+ const numChannels = this.options.numOfChannels || 1;
6950
+ // Initialize with a TypedArray of the full target size and correct data type
6951
+ const validImageData = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE, numChannels);
6899
6952
  if (this.options.noDataValue !== undefined) {
6900
- tileBuffer.fill(this.options.noDataValue);
6953
+ validImageData.fill(this.options.noDataValue);
6901
6954
  }
6902
- for (let row = 0; row < readHeight; row += 1) {
6903
- const destRow = missingTop + row;
6904
- const destRowOffset = destRow * FETCH_SIZE;
6905
- const srcRowOffset = row * validRasterData.width;
6906
- for (let col = 0; col < readWidth; col += 1) {
6907
- // Compute the destination position in the tile buffer.
6908
- const destCol = missingLeft + col;
6909
- // Bounds Check: Ensure we don't write outside the allocated buffer
6910
- if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
6911
- tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
6912
- }
6913
- else {
6914
- /* eslint-disable no-console */
6915
- console.log(`error in assigning data to tile buffer: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
6955
+ // 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
6956
+ const validRasterData = await targetImage.readRasters({ window, signal: localSignal });
6957
+ // Place the valid pixel data into the tile buffer.
6958
+ for (let band = 0; band < validRasterData.length; band += 1) {
6959
+ // We must reset the buffer for each band, otherwise data from previous band persists in padding areas
6960
+ const tileBuffer = this.createTileBuffer(this.options.format || 'Float32', FETCH_SIZE);
6961
+ if (this.options.noDataValue !== undefined) {
6962
+ tileBuffer.fill(this.options.noDataValue);
6963
+ }
6964
+ for (let row = 0; row < readHeight; row += 1) {
6965
+ const destRow = missingTop + row;
6966
+ const destRowOffset = destRow * FETCH_SIZE;
6967
+ const srcRowOffset = row * validRasterData.width;
6968
+ for (let col = 0; col < readWidth; col += 1) {
6969
+ // Compute the destination position in the tile buffer.
6970
+ const destCol = missingLeft + col;
6971
+ // Bounds Check: Ensure we don't write outside the allocated buffer
6972
+ if (destRow < FETCH_SIZE && destCol < FETCH_SIZE) {
6973
+ tileBuffer[destRowOffset + destCol] = validRasterData[band][srcRowOffset + col];
6974
+ }
6975
+ else {
6976
+ /* eslint-disable no-console */
6977
+ console.log(`error in assigning data to tile buffer: destRow ${destRow}, destCol ${destCol}, FETCH_SIZE ${FETCH_SIZE}`);
6978
+ }
6916
6979
  }
6917
6980
  }
6981
+ for (let i = 0; i < tileBuffer.length; i += 1) {
6982
+ validImageData[i * numChannels + band] = tileBuffer[i];
6983
+ }
6918
6984
  }
6919
- for (let i = 0; i < tileBuffer.length; i += 1) {
6920
- validImageData[i * numChannels + band] = tileBuffer[i];
6921
- }
6985
+ return [validImageData];
6986
+ }
6987
+ // Case B: Perfect Match (Optimization)
6988
+ // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
6989
+ const tileData = await targetImage.readRasters({ window, interleave: true, signal: localSignal });
6990
+ return [tileData];
6991
+ }
6992
+ catch (error) {
6993
+ // If the signal was aborted (or geotiff.js threw AggregateError wrapping an abort),
6994
+ // re-throw as a standard AbortError so deck.gl handles tile cancellation gracefully
6995
+ // and suppressGlobalAbortErrors() can suppress the unhandled rejection noise.
6996
+ const isAbortRelated = localSignal.aborted
6997
+ || (error instanceof AggregateError && error.errors?.some((e) => e?.name === 'AbortError' || e?.message?.includes('aborted') || e?.message?.includes('abort')))
6998
+ || (error instanceof DOMException && error.name === 'AbortError')
6999
+ || (error instanceof Error && error.message === 'Request was aborted');
7000
+ if (isAbortRelated) {
7001
+ throw new DOMException('Tile request aborted', 'AbortError');
6922
7002
  }
6923
- return [validImageData];
7003
+ throw error;
6924
7004
  }
6925
- // Case B: Perfect Match (Optimization)
6926
- // If the read window is exactly 256x256 and aligned, we can read directly interleaved.
6927
- // console.log("Perfect aligned read");
6928
- const tileData = await targetImage.readRasters({ window, interleave: true });
6929
- // console.log(`data that starts at the left top corner of the tile ${tileX}, ${tileY}`);
6930
- return [tileData];
6931
7005
  }
6932
7006
  /**
6933
7007
  * Creates a blank tile buffer filled with the "No Data" value.
@@ -6943,7 +7017,7 @@ class CogTiles {
6943
7017
  }
6944
7018
  return tileData;
6945
7019
  }
6946
- async getTile(x, y, z, bounds, meshMaxError) {
7020
+ async getTile(x, y, z, bounds, meshMaxError, signal) {
6947
7021
  let requiredSize = this.tileSize; // Default 256 for image/bitmap
6948
7022
  if (this.options.type === 'terrain') {
6949
7023
  const isKernel = this.options.useSlope || this.options.useHillshade || this.options.useSwissRelief;
@@ -6953,7 +7027,7 @@ class CogTiles {
6953
7027
  // Bitmap layer with relief glaze mode needs kernel padding for slope/hillshade computation
6954
7028
  requiredSize = this.tileSize + 2; // 258 for kernel
6955
7029
  }
6956
- const tileData = await this.getTileFromImage(x, y, z, requiredSize);
7030
+ const tileData = await this.getTileFromImage(x, y, z, requiredSize, signal);
6957
7031
  // Compute true ground cell size in meters from tile indices.
6958
7032
  // Tile y in slippy-map convention → center latitude → Web Mercator distortion correction.
6959
7033
  const latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * (y + 0.5) / Math.pow(2, z))));
@@ -6977,9 +7051,9 @@ class CogTiles {
6977
7051
  rasters,
6978
7052
  width: tileWidth,
6979
7053
  height: tileHeight,
6980
- bounds,
7054
+ bounds: bounds ?? [0, 0, 0, 0],
6981
7055
  cellSizeMeters,
6982
- }, this.options, meshMaxError);
7056
+ }, this.options, meshMaxError ?? 4.0);
6983
7057
  }
6984
7058
  /**
6985
7059
  * Determines the data type (e.g., "Int32", "Float64") of a GeoTIFF image
@@ -7189,20 +7263,11 @@ class CogBitmapLayer extends CompositeLayer {
7189
7263
  }
7190
7264
  }
7191
7265
  async getTiledBitmapData(tile) {
7192
- try {
7193
- // TODO - pass signal to getTile
7194
- // abort request if signal is aborted
7195
- const tileData = await this.state.bitmapCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z);
7196
- if (tileData && !this.props.pickable) {
7197
- tileData.raw = null;
7198
- }
7199
- return tileData;
7200
- }
7201
- catch (error) {
7202
- // Log the error and rethrow so TileLayer can surface the failure via onTileError
7203
- log.warn(`Failed to load bitmap tile at ${tile.index.z}/${tile.index.x}/${tile.index.y}:`, error)();
7204
- throw error;
7266
+ const resolvedTileData = await this.state.bitmapCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, undefined, undefined, tile.signal);
7267
+ if (resolvedTileData && !this.props.pickable) {
7268
+ resolvedTileData.raw = null;
7205
7269
  }
7270
+ return resolvedTileData;
7206
7271
  }
7207
7272
  renderSubLayers(props) {
7208
7273
  const SubLayerClass = this.getSubLayerClass('image', BitmapLayer);
@@ -7336,7 +7401,6 @@ function urlTemplateToUpdateTrigger(template) {
7336
7401
  }
7337
7402
  // TODO remove elevationDecoder
7338
7403
  // TODO use meshMaxError
7339
- // TODO - pass signal to getTile
7340
7404
  /** Render mesh surfaces from height map images. */
7341
7405
  class CogTerrainLayer extends CompositeLayer {
7342
7406
  static defaultProps = defaultProps;
@@ -7435,13 +7499,11 @@ class CogTerrainLayer extends CompositeLayer {
7435
7499
  topRight = [bbox.right, bbox.top];
7436
7500
  }
7437
7501
  const bounds = [bottomLeft[0], bottomLeft[1], topRight[0], topRight[1]];
7438
- // TODO - pass signal to getTile
7439
- // abort request if signal is aborted
7440
- const terrain = await this.state.terrainCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, bounds, this.props.meshMaxError);
7441
- if (terrain && !this.props.pickable) {
7442
- terrain.raw = null;
7502
+ const resolvedTerrain = await this.state.terrainCogTiles.getTile(tile.index.x, tile.index.y, tile.index.z, bounds, this.props.meshMaxError, tile.signal);
7503
+ if (resolvedTerrain && !this.props.pickable) {
7504
+ resolvedTerrain.raw = null;
7443
7505
  }
7444
- return Promise.all([terrain, null]);
7506
+ return Promise.all([resolvedTerrain, null]);
7445
7507
  }
7446
7508
  renderSubLayers(props) {
7447
7509
  const SubLayerClass = this.getSubLayerClass('mesh', SimpleMeshLayer);
@@ -7550,6 +7612,10 @@ class CogTerrainLayer extends CompositeLayer {
7550
7612
  }
7551
7613
  }
7552
7614
 
7615
+ // src/index.ts
7616
+ // Initialize global error suppression for deck.gl AbortErrors
7617
+ suppressGlobalAbortErrors();
7618
+
7553
7619
  class RawDecoder extends BaseDecoder {
7554
7620
  /** @param {ArrayBuffer} buffer */
7555
7621
  decodeBlock(buffer) {
@@ -7558,8 +7624,8 @@ class RawDecoder extends BaseDecoder {
7558
7624
  }
7559
7625
 
7560
7626
  var raw = /*#__PURE__*/Object.freeze({
7561
- __proto__: null,
7562
- default: RawDecoder
7627
+ __proto__: null,
7628
+ default: RawDecoder
7563
7629
  });
7564
7630
 
7565
7631
  const MIN_BITS = 9;
@@ -7717,8 +7783,8 @@ class LZWDecoder extends BaseDecoder {
7717
7783
  }
7718
7784
 
7719
7785
  var lzw = /*#__PURE__*/Object.freeze({
7720
- __proto__: null,
7721
- default: LZWDecoder
7786
+ __proto__: null,
7787
+ default: LZWDecoder
7722
7788
  });
7723
7789
 
7724
7790
  /* -*- tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- /
@@ -8781,8 +8847,8 @@ class JpegDecoder extends BaseDecoder {
8781
8847
  }
8782
8848
 
8783
8849
  var jpeg = /*#__PURE__*/Object.freeze({
8784
- __proto__: null,
8785
- default: JpegDecoder
8850
+ __proto__: null,
8851
+ default: JpegDecoder
8786
8852
  });
8787
8853
 
8788
8854
  /*============================================================================*/
@@ -12028,8 +12094,8 @@ class DeflateDecoder extends BaseDecoder {
12028
12094
  }
12029
12095
 
12030
12096
  var deflate = /*#__PURE__*/Object.freeze({
12031
- __proto__: null,
12032
- default: DeflateDecoder
12097
+ __proto__: null,
12098
+ default: DeflateDecoder
12033
12099
  });
12034
12100
 
12035
12101
  class PackbitsDecoder extends BaseDecoder {
@@ -12059,8 +12125,8 @@ class PackbitsDecoder extends BaseDecoder {
12059
12125
  }
12060
12126
 
12061
12127
  var packbits = /*#__PURE__*/Object.freeze({
12062
- __proto__: null,
12063
- default: PackbitsDecoder
12128
+ __proto__: null,
12129
+ default: PackbitsDecoder
12064
12130
  });
12065
12131
 
12066
12132
  function getDefaultExportFromCjs (x) {
@@ -14514,9 +14580,9 @@ class LercDecoder extends BaseDecoder {
14514
14580
  }
14515
14581
 
14516
14582
  var lerc = /*#__PURE__*/Object.freeze({
14517
- __proto__: null,
14518
- default: LercDecoder,
14519
- zstd: zstd$2
14583
+ __proto__: null,
14584
+ default: LercDecoder,
14585
+ zstd: zstd$2
14520
14586
  });
14521
14587
 
14522
14588
  /**
@@ -14690,9 +14756,9 @@ class ZstdDecoder extends BaseDecoder {
14690
14756
  }
14691
14757
 
14692
14758
  var zstd$1 = /*#__PURE__*/Object.freeze({
14693
- __proto__: null,
14694
- default: ZstdDecoder,
14695
- zstd: zstd
14759
+ __proto__: null,
14760
+ default: ZstdDecoder,
14761
+ zstd: zstd
14696
14762
  });
14697
14763
 
14698
14764
  /**
@@ -14755,9 +14821,9 @@ class WebImageDecoder extends BaseDecoder {
14755
14821
  }
14756
14822
 
14757
14823
  var webimage = /*#__PURE__*/Object.freeze({
14758
- __proto__: null,
14759
- default: WebImageDecoder
14824
+ __proto__: null,
14825
+ default: WebImageDecoder
14760
14826
  });
14761
14827
 
14762
- export { CogBitmapLayer, CogTerrainLayer, CogTiles, GeoImage };
14828
+ export { CogBitmapLayer, CogTerrainLayer, CogTiles, GeoImage, suppressGlobalAbortErrors };
14763
14829
  //# sourceMappingURL=index.js.map