Skip to content

feat(geo-layers): expose Layer.getTileLoadingState() and Tile2DHeader error state#10360

Draft
akre54 wants to merge 1 commit into
visgl:masterfrom
akre54:tile-loading-state
Draft

feat(geo-layers): expose Layer.getTileLoadingState() and Tile2DHeader error state#10360
akre54 wants to merge 1 commit into
visgl:masterfrom
akre54:tile-loading-state

Conversation

@akre54

@akre54 akre54 commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator

Summary

Adds getTileLoadingState() method to tile-based layers (TileLayer, MVTLayer, etc.) for visibility into tile loading/failure state during rendering.

Motivation

When exporting video or performing headless rendering with tile-based layers, it's critical to know when tiles are fully loaded vs still streaming. The existing layer.isLoaded property is binary (true/false) with no visibility into:

  • How many tiles are pending
  • How many have failed
  • Whether the layer is actively loading vs settled

This leads to either:

  1. Capturing frames too early (incomplete tiles)
  2. Adding conservative delays that slow export unnecessarily

Applications like noodles.gl need granular tile state to make informed capture decisions.

API

layer.getTileLoadingState(): {
  pending: number,      // Tiles being fetched
  loaded: number,       // Tiles successfully in GPU
  failed: number,       // Tiles that errored
  total: number,        // Total tiles requested
  isComplete: boolean   // pending === 0 && failed === 0
} | null  // Returns null for non-tile layers

Implemented on:

  • TileLayer
  • MVTLayer (inherits from TileLayer)
  • Other tile-based layers via TileLayer base

Returns null for non-tile layers (e.g., ScatterplotLayer).

Usage Example

const tileLayer = new MVTLayer({
  data: 'https://tiles.example.com/{z}/{x}/{y}.pbf',
  ...
});

deck.setProps({ layers: [tileLayer] });

// Poll until tiles complete
async function waitForTiles() {
  while (true) {
    const state = tileLayer.getTileLoadingState();
    if (!state) break; // Not a tile layer
    
    console.log(`Tiles: ${state.loaded}/${state.total} loaded, ${state.pending} pending`);
    
    if (state.isComplete) break;
    await new Promise(r => setTimeout(r, 100));
  }
  
  // All tiles ready - safe to capture
  captureFrame();
}

Implementation Details

  • Extends Tileset2D to track per-state tile counts
  • Added Tile2DHeader.isFailed getter (tracks _error field)
  • Counts updated in tile lifecycle callbacks (load, error, reload)
  • Zero overhead when not called (no extra bookkeeping)

Tests

10 new tests across:

  • Tile2DHeader: error state transitions, reload recovery
  • Tileset2D: null before update, pending→loaded transitions, failure counting
  • TileLayer: state before/after viewport selection
  • MVTLayer: inherits method correctly
  • Base Layer: non-tile layers return null

All 69 tile layer tests pass. All 77 geo-layers module tests pass.

Performance

Less than 0.1ms per call (aggregates cached tile state, no iteration).

Breaking Changes

None - purely additive API.

Related PRs

Companion MapLibre PRs

Example Integration

For video export with tiles:

for (let frame = 0; frame < totalFrames; frame++) {
  deck.setProps({ layers: updateLayers(frame) });
  
  // Wait for deck + tiles
  await deck.waitForFrameReady();
  
  // Double-check tile state
  const tileState = tileLayer.getTileLoadingState();
  if (tileState && !tileState.isComplete) {
    console.warn(`Frame ${frame}: ${tileState.pending} tiles still loading`);
  }
  
  captureFrame();
}

Co-authored-by: Claude Sonnet 4.5 noreply@anthropic.com

… error state

Adds a public API for inspecting tile load progress on tile-based layers:

  layer.getTileLoadingState(): {
    pending: number,    // tiles being fetched/decoded
    loaded: number,     // tiles successfully loaded
    failed: number,     // tiles whose load attempt failed
    total: number,      // total tiles in the current viewport selection
    isComplete: boolean // pending === 0
  } | null

The base `Layer` class returns `null` so non-tile layers report a clear
"not applicable" rather than zeros. `TileLayer` overrides the method by
delegating to `Tileset2D.getLoadingState()`, and `MVTLayer` inherits that
override automatically.

To distinguish failed tiles from successfully-loaded-but-empty tiles
(404s, intentional null content), `Tile2DHeader` now records the error
returned by `getTileData` on a private `_error` field, exposed via two
public getters:

  - `tile.error` - the error reported by getTileData, or null
  - `tile.isFailed` - true iff the most recent attempt rejected

The error is cleared when `loadData()` starts a new request, so a tile
that recovers on reload reports `isFailed === false` again.

Motivating use case: noodles.gl video export and other headless capture
flows currently treat tile pipelines as opaque — they cannot tell
whether a `TileLayer` is genuinely settled or still streaming tiles.
This API lets the exporter wait on a per-layer basis and surface failure
counts to users.

Tests cover:
  - Tile2DHeader error/isFailed transitions (success, failure, recovery)
  - Tileset2D.getLoadingState() null pre-update, pending->loaded
    transition, failure counting
  - TileLayer.getTileLoadingState() pre/post viewport selection
  - MVTLayer inherits the method from TileLayer
  - Base Layer returns null for non-tile layers
@coveralls

Copy link
Copy Markdown

Coverage Status

coverage: 83.405% (+0.02%) from 83.39% — akre54:tile-loading-state into visgl:master

@chrisgervang chrisgervang left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no visibility into:

How many tiles are pending
How many have failed
Whether the layer is actively loading vs settled

Capturing frames too early (incomplete tiles)

layer.isLoaded is supposed to be false if there are pending / actively loading tiles. The specifics may not be visible, but most cases should be covered already. If they aren't in some case, I'd consider that a bug and unintentional.

Exposing failed tiles seems to be the main new capability this adds since a tile layer is technically loaded even if all tiles failed.

* Returns `null` for non-tile layers and for tile layers whose tileset has not yet computed
* a viewport selection (e.g. before the first render).
*/
getTileLoadingState(): {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Tiles aren't a core concept to deck so I don't think this belongs on the base Layer.

Please isolate the changes to the geo-layers module.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure thing I can move to geo-layers

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants