Skip to content

Commit 2b5d3cc

Browse files
Add coverage LOD strategy for tile layers
1 parent 1075042 commit 2b5d3cc

11 files changed

Lines changed: 1047 additions & 26 deletions

File tree

docs/api-reference/geo-layers/tile-layer.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -317,6 +317,19 @@ Apps may define a custom `refinementStrategy` by supplying its own callback func
317317

318318
When called, the function receives an array of [Tile](#tile) instances representing every tile that is currently in the cache. It is an opportunity to manipulate `tile.isVisible` before sub layers are rendered. `isVisible` is initially set to the value of `isSelected` (equivalent to `refinementStrategy: 'never'`).
319319

320+
#### `lodStrategy` (string, optional) {#lodstrategy}
321+
322+
Controls whether the layer prefetches lower-resolution tiles to provide instant coverage during viewport transitions (panning, zooming, or camera flights).
323+
324+
* `'none'`: Only request tiles at the current zoom level. No additional lower-resolution coverage tiles are fetched.
325+
* `'coverage'`: In addition to the current zoom tiles, prefetch ancestor tiles at progressively lower zoom levels. These low-resolution tiles load quickly and act as fallback coverage while higher-resolution tiles are still loading, reducing blank areas during navigation.
326+
327+
Use `'coverage'` when your application involves frequent camera movement (e.g. animated transitions, user exploration) and visual continuity matters more than minimizing network requests. Use `'none'` when bandwidth is constrained, tiles are expensive to fetch, or the viewport is mostly static.
328+
329+
Note: The `'coverage'` strategy is designed for geospatial views (e.g. `MapView`) where tiles follow a power-of-2 hierarchy. It is not recommended for non-geospatial views (e.g. `FirstPersonView`, `OrthographicView`) where viewport movement patterns differ and ancestor tiles may not provide meaningful coverage.
330+
331+
- Default: `'none'`
332+
320333
#### `maxRequests` (number, optional) {#maxrequests}
321334

322335
The maximum number of concurrent `getTileData` calls.

modules/geo-layers/src/terrain-layer/terrain-layer.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,8 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
355355
onTileError,
356356
maxCacheSize,
357357
maxCacheByteSize,
358-
refinementStrategy
358+
refinementStrategy,
359+
lodStrategy
359360
} = this.props;
360361

361362
if (this.state.isTiled) {
@@ -387,7 +388,8 @@ export default class TerrainLayer<ExtraPropsT extends {} = {}> extends Composite
387388
onTileError,
388389
maxCacheSize,
389390
maxCacheByteSize,
390-
refinementStrategy
391+
refinementStrategy,
392+
lodStrategy
391393
}
392394
);
393395
}

modules/geo-layers/src/tile-layer/tile-layer.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ import {
2222
Tileset2D,
2323
Tile2DHeader,
2424
RefinementStrategy,
25+
LODStrategy,
2526
STRATEGY_DEFAULT,
2627
Tileset2DProps
2728
} from '../tileset-2d/index';
@@ -47,6 +48,7 @@ const defaultProps: DefaultProps<TileLayerProps> = {
4748
maxCacheSize: null,
4849
maxCacheByteSize: null,
4950
refinementStrategy: STRATEGY_DEFAULT,
51+
lodStrategy: 'none',
5052
zRange: null,
5153
maxRequests: 6,
5254
debounceTime: 0,
@@ -126,6 +128,13 @@ type _TileLayerProps<DataT> = {
126128
*/
127129
refinementStrategy?: RefinementStrategy;
128130

131+
/**
132+
* How the tile layer prefetches lower resolution coverage for smooth transitions.
133+
*
134+
* @default 'none'
135+
*/
136+
lodStrategy?: LODStrategy;
137+
129138
/** Range of minimum and maximum heights in the tile. */
130139
zRange?: ZRange | null;
131140

@@ -261,6 +270,7 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
261270
maxCacheSize,
262271
maxCacheByteSize,
263272
refinementStrategy,
273+
lodStrategy,
264274
extent,
265275
maxZoom,
266276
minZoom,
@@ -278,6 +288,7 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
278288
minZoom,
279289
tileSize,
280290
refinementStrategy,
291+
lodStrategy,
281292
extent,
282293
maxRequests,
283294
debounceTime,

modules/geo-layers/src/tileset-2d/index.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ export type {
1313
TileBoundingBox
1414
} from './types';
1515

16-
export type {Tileset2DProps, RefinementStrategy} from './tileset-2d';
17-
export {Tileset2D, STRATEGY_DEFAULT} from './tileset-2d';
16+
export type {Tileset2DProps, RefinementStrategy, LODStrategy} from './tileset-2d';
17+
export {Tileset2D, STRATEGY_DEFAULT, LOD_STRATEGY_COVERAGE, LOD_STRATEGY_NONE} from './tileset-2d';
1818

1919
export {Tile2DHeader} from './tile-2d-header';
2020

modules/geo-layers/src/tileset-2d/tile-2d-header.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import type {Layer} from '@deck.gl/core';
1010
export type TileLoadDataProps<DataT = any> = {
1111
requestScheduler: RequestScheduler;
1212
getData: (props: TileLoadProps) => Promise<DataT>;
13+
getPriority: (tile: Tile2DHeader<DataT>) => number;
1314
onLoad: (tile: Tile2DHeader<DataT>) => void;
1415
onError: (error: any, tile: Tile2DHeader<DataT>) => void;
1516
};
@@ -18,6 +19,7 @@ export class Tile2DHeader<DataT = any> {
1819
index: TileIndex;
1920
isVisible: boolean;
2021
isSelected: boolean;
22+
isPrefetch: boolean;
2123
parent: Tile2DHeader | null;
2224
children: Tile2DHeader[] | null;
2325
content: DataT | null;
@@ -41,6 +43,7 @@ export class Tile2DHeader<DataT = any> {
4143
this.index = index;
4244
this.isVisible = false;
4345
this.isSelected = false;
46+
this.isPrefetch = false;
4447
this.parent = null;
4548
this.children = [];
4649

@@ -106,6 +109,7 @@ export class Tile2DHeader<DataT = any> {
106109
/* eslint-disable max-statements */
107110
private async _loadData({
108111
getData,
112+
getPriority,
109113
requestScheduler,
110114
onLoad,
111115
onError
@@ -116,10 +120,8 @@ export class Tile2DHeader<DataT = any> {
116120
this._abortController = new AbortController();
117121
const {signal} = this._abortController;
118122

119-
// @ts-expect-error (2345) Argument of type '(tile: any) => 1 | -1' is not assignable ...
120-
const requestToken = await requestScheduler.scheduleRequest(this, tile => {
121-
return tile.isSelected ? 1 : -1;
122-
});
123+
// @ts-expect-error (2345) loaders.gl's RequestScheduler callback type is too narrow.
124+
const requestToken = await requestScheduler.scheduleRequest(this, getPriority);
123125

124126
if (!requestToken) {
125127
this._isCancelled = true;

0 commit comments

Comments
 (0)