Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 185 additions & 0 deletions docs/api-reference/geo-layers/get-tile-loading-state.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,185 @@
# getTileLoadingState
Comment thread
akre54 marked this conversation as resolved.
Outdated

Get detailed loading state for tiles in a TileLayer's viewport.

```typescript
import {getTileLoadingState} from '@deck.gl/geo-layers';
import type {TileLoadingState} from '@deck.gl/geo-layers';
```

## Usage

```typescript
import {Deck} from '@deck.gl/core';
import {TileLayer, getTileLoadingState} from '@deck.gl/geo-layers';

const tileLayer = new TileLayer({
data: 'https://tile.example.com/tiles/{z}/{x}/{y}.json',
getTileData: async ({index, id, bbox}) => {
const response = await fetch(getURLFromTemplate(id, index));
if (!response.ok) throw new Error('Tile not found');
return await response.json();
},
renderSubLayers: props => {
return new ScatterplotLayer(props);
}
});

// Check detailed loading state
const state = getTileLoadingState(tileLayer);

console.log(`${state.loaded}/${state.total} tiles loaded`);
console.log(`${state.failed} tiles failed`);
console.log(`${state.percentLoaded}% complete`);

if (state.isComplete && !state.isSuccess) {
console.warn('Some tiles failed to load');
}
```

## Parameters

### `layer` (TileLayer)

A TileLayer instance to inspect.

## Returns

`TileLoadingState` object with the following properties:

- **`total`** (`number`): Total number of tiles in the current viewport
- **`loaded`** (`number`): Number of tiles that loaded successfully (with content)
- **`failed`** (`number`): Number of tiles that failed to load (404, network error, etc.)
- **`pending`** (`number`): Number of tiles still loading
- **`percentLoaded`** (`number`): Percentage of successfully loaded tiles (0-100)
- **`isComplete`** (`boolean`): True if all tile requests are settled (loaded or failed)
- **`isSuccess`** (`boolean`): True if all tiles loaded successfully (no failures)

## Understanding layer.isLoaded vs getTileLoadingState

deck.gl's `layer.isLoaded` property returns `true` once all tile requests are "settled" (completed or failed). This is **intentional** to prevent waiting forever for tiles that will never load (404s, network errors, missing tiles, etc.).

From the source code comments:

> "Error / empty tiles resolve to `content === null`. Once Tile2DHeader marks those requests as loaded, do not wait for generated sublayers because there is nothing to render for that tile and `tile.layers` will remain null."

### When to use each

**Use `layer.isLoaded`** when you want to know:
- Are all tile requests complete? (boolean)
- Is the layer ready to render?
- Should I wait or proceed with what's available?

**Use `getTileLoadingState()`** when you need:
- How many tiles loaded vs failed? (granular counts)
- Progress percentage for a loading indicator
- To distinguish between "loading", "loaded successfully", and "loaded with errors"
- To show error states in the UI

## Examples

### Progress Bar

```typescript
function ProgressBar({tileLayer}) {
const state = getTileLoadingState(tileLayer);

return (
<div className="progress-bar">
<div className="progress" style={{width: `${state.percentLoaded}%`}} />
<span>{state.loaded}/{state.total} tiles loaded</span>
{state.failed > 0 && (
<span className="error">{state.failed} failed</span>
)}
</div>
);
}
```

### Wait for Complete Load

```typescript
// Wait for all tiles to load successfully
async function waitForTiles(layer: TileLayer): Promise<void> {
return new Promise((resolve, reject) => {
const check = () => {
const state = getTileLoadingState(layer);

if (!state.isComplete) {
// Still loading, check again
requestAnimationFrame(check);
return;
}

if (state.isSuccess) {
resolve();
} else {
reject(new Error(`${state.failed} tiles failed to load`));
}
};

check();
});
}
```

### Video Export

```typescript
// For video export, you might want to proceed even if some tiles failed
async function captureFrame(deck: Deck): Promise<void> {
const tileLayers = deck.props.layers.filter(l => l instanceof TileLayer);

// Check if all tile requests are settled (loaded or failed)
const allSettled = tileLayers.every(layer => {
const state = getTileLoadingState(layer);
return state.isComplete;
});

if (!allSettled) {
// Wait for tiles to finish loading or failing
await new Promise(resolve => setTimeout(resolve, 100));
return captureFrame(deck);
}

// Proceed with frame capture (may have missing tiles)
const canvas = deck.canvas as HTMLCanvasElement;
const imageData = canvas.toDataURL('image/png');

// Log any failures
tileLayers.forEach(layer => {
const state = getTileLoadingState(layer);
if (state.failed > 0) {
console.warn(`Layer ${layer.id}: ${state.failed} tiles failed`);
}
});
}
```

### Error Recovery

```typescript
function TileLayerWithRetry({...props}) {
const [retryCount, setRetryCount] = useState(0);
const layerRef = useRef<TileLayer>(null);

useEffect(() => {
if (!layerRef.current) return;

const state = getTileLoadingState(layerRef.current);

if (state.isComplete && !state.isSuccess && retryCount < 3) {
console.log(`${state.failed} tiles failed, retrying...`);
setRetryCount(retryCount + 1);
// Trigger layer update to retry failed tiles
layerRef.current.setNeedsUpdate();
}
}, [retryCount]);

return <TileLayer ref={layerRef} {...props} />;
}
```

## Source

[modules/geo-layers/src/tile-layer/get-tile-loading-state.ts](https://github.com/visgl/deck.gl/tree/master/modules/geo-layers/src/tile-layer/get-tile-loading-state.ts)
2 changes: 2 additions & 0 deletions modules/geo-layers/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ export type {H3HexagonLayerProps} from './h3-layers/h3-hexagon-layer';
export type {GreatCircleLayerProps} from './great-circle-layer/great-circle-layer';
export type {S2LayerProps} from './s2-layer/s2-layer';
export type {TileLayerProps, TileLayerPickingInfo} from './tile-layer/tile-layer';
export type {TileLoadingState} from './tile-layer/get-tile-loading-state';
export {getTileLoadingState} from './tile-layer/get-tile-loading-state';
export type {TripsLayerProps} from './trips-layer/trips-layer';
export type {QuadkeyLayerProps} from './quadkey-layer/quadkey-layer';
export type {TerrainLayerProps} from './terrain-layer/terrain-layer';
Expand Down
101 changes: 101 additions & 0 deletions modules/geo-layers/src/tile-layer/get-tile-loading-state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
// deck.gl
// SPDX-License-Identifier: MIT
// Copyright (c) vis.gl contributors

import type {TileLayer} from './tile-layer';
import type {Tile2DHeader} from '../tileset-2d/tile-2d-header';

export type TileLoadingState = {
Comment thread
akre54 marked this conversation as resolved.
Outdated
/** Total number of tiles in the current viewport */
total: number;
/** Number of tiles that have loaded successfully (with content) */
loaded: number;
/** Number of tiles that failed to load (isLoaded but content is null) */
failed: number;
/** Number of tiles still loading */
pending: number;
/** Percentage of tiles loaded (loaded / total) */
percentLoaded: number;
/** True if all tiles are done loading (loaded or failed) */
isComplete: boolean;
/** True if all tiles loaded successfully (no failures) */
isSuccess: boolean;
};

/**
* Get detailed loading state for tiles in a TileLayer's viewport.
*
* Unlike `layer.isLoaded` which only returns a boolean, this function provides
* granular information about tile loading progress, including:
* - How many tiles loaded successfully vs failed
* - Loading percentage for progress UI
* - Whether any tiles are still pending
*
* @param layer - A TileLayer instance
* @returns Detailed tile loading state
*
* @example
* ```typescript
* const state = getTileLoadingState(tileLayer);
* console.log(`${state.loaded}/${state.total} tiles loaded`);
* console.log(`${state.failed} tiles failed`);
* console.log(`${state.percentLoaded}% complete`);
*
* if (state.isComplete && !state.isSuccess) {
* console.warn('Some tiles failed to load');
* }
* ```
*
* @note
* deck.gl's `layer.isLoaded` returns `true` once all tile requests are "settled"
* (completed or failed). This is intentional to prevent waiting forever for tiles
* that will never load (404s, network errors, etc.). Use this function to distinguish
* between successfully loaded tiles and failed tiles.
*/
export function getTileLoadingState<T>(layer: TileLayer<T>): TileLoadingState {
Comment thread
akre54 marked this conversation as resolved.
Outdated
const tileset = layer.state?.tileset;
const selectedTiles: Tile2DHeader<T>[] = tileset?.selectedTiles || [];

if (selectedTiles.length === 0) {
return {
total: 0,
loaded: 0,
failed: 0,
pending: 0,
percentLoaded: 100,
isComplete: true,
isSuccess: true
};
}

let loaded = 0;
let failed = 0;
let pending = 0;

for (const tile of selectedTiles) {
if (!tile.isLoaded) {
pending++;
} else if (tile.content === null) {
// Tile request completed but returned no content (404, error, etc.)
failed++;
} else {
// Tile loaded successfully with content
loaded++;
}
}

const total = selectedTiles.length;
const percentLoaded = total > 0 ? Math.round((loaded / total) * 100) : 100;
const isComplete = pending === 0;
const isSuccess = isComplete && failed === 0;

return {
total,
loaded,
failed,
pending,
percentLoaded,
isComplete,
isSuccess
};
}
Loading
Loading