Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
24 changes: 22 additions & 2 deletions modules/geo-layers/src/tile-layer/tile-layer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,9 @@ const defaultProps: DefaultProps<TileLayerProps> = {
debounceTime: 0,
zoomOffset: 0,
visibleMinZoom: null,
visibleMaxZoom: null
visibleMaxZoom: null,
prefetchZoomDelta: 0,
prefetchTileRadius: 0
};

/** All props supported by the TileLayer */
Expand Down Expand Up @@ -167,6 +169,20 @@ type _TileLayerProps<DataT> = {
* @default null
*/
visibleMaxZoom?: number | null;

/**
* Number of lower zoom levels to preload around selected tiles.
*
* @default 0
*/
prefetchZoomDelta?: number;

/**
* Number of selected-zoom tile rings to include when prefetching lower zoom tiles.
*
* @default 0
*/
prefetchTileRadius?: number;
};

export type TileLayerPickingInfo<
Expand Down Expand Up @@ -268,7 +284,9 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
debounceTime,
zoomOffset,
visibleMinZoom,
visibleMaxZoom
visibleMaxZoom,
prefetchZoomDelta,
prefetchTileRadius
} = this.props;

return {
Expand All @@ -284,6 +302,8 @@ export default class TileLayer<DataT = any, ExtraPropsT extends {} = {}> extends
zoomOffset,
visibleMinZoom,
visibleMaxZoom,
prefetchZoomDelta,
prefetchTileRadius,

getTileData: this.getTileData.bind(this),
onTileLoad: this._onTileLoad.bind(this),
Expand Down
2 changes: 2 additions & 0 deletions modules/geo-layers/src/tileset-2d/tile-2d-header.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export class Tile2DHeader<DataT = any> {
index: TileIndex;
isVisible: boolean;
isSelected: boolean;
isPrefetch: boolean;
parent: Tile2DHeader | null;
children: Tile2DHeader[] | null;
content: DataT | null;
Expand All @@ -42,6 +43,7 @@ export class Tile2DHeader<DataT = any> {
this.index = index;
this.isVisible = false;
this.isSelected = false;
this.isPrefetch = false;
this.parent = null;
this.children = [];

Expand Down
69 changes: 69 additions & 0 deletions modules/geo-layers/src/tileset-2d/tileset-2d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,10 @@ export type Tileset2DProps<DataT = any> = {
visibleMinZoom?: number | null;
/** The maximum zoom level at which tiles are visible. @default null */
visibleMaxZoom?: number | null;
/** Number of lower zoom levels to preload around selected tiles. @default 0 */
prefetchZoomDelta?: number;
/** Number of selected-zoom tile rings to include when prefetching lower zoom tiles. @default 0 */
prefetchTileRadius?: number;
/** Called when a tile successfully loads. */
onTileLoad?: (tile: Tile2DHeader<DataT>) => void;
/** Called when a tile is cleared from cache. */
Expand Down Expand Up @@ -114,6 +118,8 @@ export const DEFAULT_TILESET2D_PROPS: Omit<Required<Tileset2DProps>, 'getTileDat
zoomOffset: 0,
visibleMinZoom: null,
visibleMaxZoom: null,
prefetchZoomDelta: 0,
prefetchTileRadius: 0,

// onTileLoad: (tile: Tile2DHeader) => void, // onTileUnload: (tile: Tile2DHeader) => void, // onTileError: (error: any, tile: Tile2DHeader) => void, /** Called when all tiles in the current viewport are loaded. */
// onViewportLoad: ((tiles: Tile2DHeader<DataT>[]) => void) | null,
Expand All @@ -137,6 +143,7 @@ export class Tileset2D {
private _viewport: Viewport | null;
private _zRange: ZRange | null;
private _selectedTiles: Tile2DHeader[] | null;
private _prefetchTiles: Tile2DHeader[] | null;
private _frameNumber: number;
private _modelMatrix: Matrix4;
private _modelMatrixInverse: Matrix4;
Expand Down Expand Up @@ -178,6 +185,7 @@ export class Tileset2D {
this._viewport = null;
this._zRange = null;
this._selectedTiles = null;
this._prefetchTiles = null;
this._frameNumber = 0;

this._modelMatrix = new Matrix4();
Expand Down Expand Up @@ -223,6 +231,7 @@ export class Tileset2D {
this._cache.clear();
this._tiles = [];
this._selectedTiles = null;
this._prefetchTiles = null;
}

reloadAll(): void {
Expand Down Expand Up @@ -269,6 +278,9 @@ export class Tileset2D {
modelMatrixInverse: this._modelMatrixInverse
});
this._selectedTiles = tileIndices.map(index => this._getTile(index, true));
this._prefetchTiles = this._getPrefetchTileIndices(tileIndices).map(index =>
this._getTile(index, true)
);

if (this._dirty) {
// Some new tiles are added
Expand All @@ -277,6 +289,7 @@ export class Tileset2D {
// Check for needed reloads explicitly even if the view/matrix has not changed.
} else if (this.needsReload) {
this._selectedTiles = this._selectedTiles!.map(tile => this._getTile(tile.index, true));
this._prefetchTiles = this._prefetchTiles!.map(tile => this._getTile(tile.index, true));
}

// Update tile states
Expand Down Expand Up @@ -410,12 +423,18 @@ export class Tileset2D {
visibilities[i++] = tile.isVisible;
tile.isSelected = false;
tile.isVisible = false;
tile.isPrefetch = false;
}
// @ts-expect-error called only when _selectedTiles is already defined
for (const tile of this._selectedTiles) {
tile.isSelected = true;
tile.isVisible = true;
}
for (const tile of this._prefetchTiles || []) {
if (!tile.isSelected) {
tile.isPrefetch = true;
}
}

// Strategy-specific state logic
(typeof refinementStrategy === 'function'
Expand Down Expand Up @@ -444,6 +463,9 @@ export class Tileset2D {
if (tile.isVisible) {
return 1e6 + this._getTileDistanceToViewportCenter(tile);
}
if (tile.isPrefetch) {
return 2e6 + this._getTileDistanceToViewportCenter(tile);
}
return -1;
}

Expand Down Expand Up @@ -471,6 +493,53 @@ export class Tileset2D {
return Number.MAX_SAFE_INTEGER;
}

private _getPrefetchTileIndices(tileIndices: TileIndex[]): TileIndex[] {
const {prefetchZoomDelta, prefetchTileRadius} = this.opts;
const zoomDelta = Math.floor(prefetchZoomDelta);
const radius = Math.max(0, Math.floor(prefetchTileRadius));

if (!zoomDelta || tileIndices.length === 0) {
return [];
}

const ids = new Set(tileIndices.map(index => this.getTileId(index)));
const indices: TileIndex[] = [];

for (const tileIndex of tileIndices) {
const tileZoom = this.getTileZoom(tileIndex);
const targetZoom = Math.max(this._minZoom ?? 0, tileZoom - zoomDelta);
const parentDelta = tileZoom - targetZoom;
if (parentDelta <= 0) {
continue;
}

const scale = 2 ** parentDelta;
const worldSize = 2 ** tileZoom;
for (let xOffset = -radius; xOffset <= radius; xOffset++) {
for (let yOffset = -radius; yOffset <= radius; yOffset++) {
const x = tileIndex.x + xOffset;
const y = tileIndex.y + yOffset;
if (y < 0 || y >= worldSize) {
continue;
}

const wrappedX = ((x % worldSize) + worldSize) % worldSize;
const prefetchIndex = {
x: Math.floor(wrappedX / scale),
y: Math.floor(y / scale),
z: targetZoom
};
const id = this.getTileId(prefetchIndex);
if (!ids.has(id)) {
ids.add(id);
indices.push(prefetchIndex);
}
}
}
}
return indices;
}

private _pruneRequests(): void {
const {maxRequests = 0} = this.opts;

Expand Down
2 changes: 1 addition & 1 deletion test/modules/geo-layers/tileset-2d/tile-2d-header.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {test, expect} from 'vitest';
import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers';
import {RequestScheduler} from '@loaders.gl/loader-utils';

const getPriority = tile => (tile.isSelected ? 1 : -1);
const getPriority = tile => (tile.isSelected || tile.isPrefetch ? 1 : -1);

test('Tile2DHeader', async () => {
let onTileLoadCalled = false;
Expand Down
35 changes: 35 additions & 0 deletions test/modules/geo-layers/tileset-2d/tileset-2d.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,41 @@ test('Tileset2D#update', () => {
expect(tileset.tiles[0].bbox, 'tile has metadata').toBeTruthy();
});

test('Tileset2D#prefetch parent tiles', () => {
const tileset = new Tileset2D({
getTileData,
prefetchZoomDelta: 1,
prefetchTileRadius: 0,
onTileLoad: () => {}
});
tileset.update(testViewport);

expect(tileset._cache.get('1171-1566-12').isSelected, 'selected tile is selected').toBeTruthy();
expect(tileset._cache.get('585-783-11').isPrefetch, 'parent tile is prefetched').toBeTruthy();
expect(tileset._cache.get('585-783-11').isVisible, 'prefetch tile is hidden').toBeFalsy();
});

test('Tileset2D#prefetch parent tile ring', () => {
const tileset = new Tileset2D({
getTileData,
prefetchZoomDelta: 1,
prefetchTileRadius: 1,
onTileLoad: () => {}
});
tileset.update(testViewport);

const prefetchTileIds = Array.from(tileset._cache.values())
.filter(tile => tile.isPrefetch)
.map(tile => tile.id)
.sort();
expect(prefetchTileIds, 'prefetches the lower-zoom ring around selected tiles').toEqual([
'585-782-11',
'585-783-11',
'586-782-11',
'586-783-11'
]);
});

test('Tileset2D#updateOnModelMatrix', () => {
const tileset = new Tileset2D({
getTileData,
Expand Down
Loading