@deck.gl/geo-layers
Version:
deck.gl layers supporting geospatial use cases and GIS formats
431 lines (354 loc) • 9.49 kB
JavaScript
import Tile2DHeader from './tile-2d-header';
import { getTileIndices, tileToBoundingBox } from './utils';
import { RequestScheduler } from '@loaders.gl/loader-utils';
import { Matrix4 } from '@math.gl/core';
const TILE_STATE_VISITED = 1;
const TILE_STATE_VISIBLE = 2;
export const STRATEGY_NEVER = 'never';
export const STRATEGY_REPLACE = 'no-overlap';
export const STRATEGY_DEFAULT = 'best-available';
const DEFAULT_CACHE_SCALE = 5;
const STRATEGIES = {
[STRATEGY_DEFAULT]: updateTileStateDefault,
[STRATEGY_REPLACE]: updateTileStateReplace,
[STRATEGY_NEVER]: () => {}
};
export default class Tileset2D {
constructor(opts) {
this.opts = opts;
this.onTileLoad = tile => {
this.opts.onTileLoad(tile);
if (this.opts.maxCacheByteSize) {
this._cacheByteSize += tile.byteLength;
this._resizeCache();
}
};
this._requestScheduler = new RequestScheduler({
maxRequests: opts.maxRequests,
throttleRequests: opts.maxRequests > 0
});
this._cache = new Map();
this._tiles = [];
this._dirty = false;
this._cacheByteSize = 0;
this._viewport = null;
this._selectedTiles = null;
this._frameNumber = 0;
this._modelMatrix = new Matrix4();
this._modelMatrixInverse = new Matrix4();
this.setOptions(opts);
}
get tiles() {
return this._tiles;
}
get selectedTiles() {
return this._selectedTiles;
}
get isLoaded() {
return this._selectedTiles.every(tile => tile.isLoaded);
}
get needsReload() {
return this._selectedTiles.some(tile => tile.needsReload);
}
setOptions(opts) {
Object.assign(this.opts, opts);
if (Number.isFinite(opts.maxZoom)) {
this._maxZoom = Math.floor(opts.maxZoom);
}
if (Number.isFinite(opts.minZoom)) {
this._minZoom = Math.ceil(opts.minZoom);
}
}
finalize() {
for (const tile of this._cache.values()) {
if (tile.isLoading) {
tile.abort();
}
}
this._cache.clear();
this._tiles = [];
this._selectedTiles = null;
}
reloadAll() {
for (const tileId of this._cache.keys()) {
const tile = this._cache.get(tileId);
if (!this._selectedTiles.includes(tile)) {
this._cache.delete(tileId);
} else {
tile.setNeedsReload();
}
}
}
update(viewport, {
zRange,
modelMatrix
} = {}) {
const modelMatrixAsMatrix4 = new Matrix4(modelMatrix);
const isModelMatrixNew = !modelMatrixAsMatrix4.equals(this._modelMatrix);
if (!viewport.equals(this._viewport) || isModelMatrixNew) {
if (isModelMatrixNew) {
this._modelMatrixInverse = modelMatrixAsMatrix4.clone().invert();
this._modelMatrix = modelMatrixAsMatrix4;
}
this._viewport = viewport;
const tileIndices = this.getTileIndices({
viewport,
maxZoom: this._maxZoom,
minZoom: this._minZoom,
zRange,
modelMatrix: this._modelMatrix,
modelMatrixInverse: this._modelMatrixInverse
});
this._selectedTiles = tileIndices.map(index => this._getTile(index, true));
if (this._dirty) {
this._rebuildTree();
}
} else if (this.needsReload) {
this._selectedTiles = this._selectedTiles.map(tile => this._getTile({
x: tile.x,
y: tile.y,
z: tile.z
}));
}
const changed = this.updateTileStates();
this._pruneRequests();
if (this._dirty) {
this._resizeCache();
}
if (changed) {
this._frameNumber++;
}
return this._frameNumber;
}
getTileIndices({
viewport,
maxZoom,
minZoom,
zRange,
modelMatrix,
modelMatrixInverse
}) {
const {
tileSize,
extent,
zoomOffset
} = this.opts;
return getTileIndices({
viewport,
maxZoom,
minZoom,
zRange,
tileSize,
extent,
modelMatrix,
modelMatrixInverse,
zoomOffset
});
}
getTileMetadata({
x,
y,
z
}) {
const {
tileSize
} = this.opts;
return {
bbox: tileToBoundingBox(this._viewport, x, y, z, tileSize)
};
}
getParentIndex(tileIndex) {
tileIndex.x = Math.floor(tileIndex.x / 2);
tileIndex.y = Math.floor(tileIndex.y / 2);
tileIndex.z -= 1;
return tileIndex;
}
updateTileStates() {
const refinementStrategy = this.opts.refinementStrategy || STRATEGY_DEFAULT;
const visibilities = new Array(this._cache.size);
let i = 0;
for (const tile of this._cache.values()) {
visibilities[i++] = tile.isVisible;
tile.isSelected = false;
tile.isVisible = false;
}
for (const tile of this._selectedTiles) {
tile.isSelected = true;
tile.isVisible = true;
}
(typeof refinementStrategy === 'function' ? refinementStrategy : STRATEGIES[refinementStrategy])(Array.from(this._cache.values()));
i = 0;
for (const tile of this._cache.values()) {
if (visibilities[i++] !== tile.isVisible) {
return true;
}
}
return false;
}
_pruneRequests() {
const {
maxRequests
} = this.opts;
const abortCandidates = [];
let ongoingRequestCount = 0;
for (const tile of this._cache.values()) {
if (tile.isLoading) {
ongoingRequestCount++;
if (!tile.isSelected && !tile.isVisible) {
abortCandidates.push(tile);
}
}
}
while (maxRequests > 0 && ongoingRequestCount > maxRequests && abortCandidates.length > 0) {
const tile = abortCandidates.shift();
tile.abort();
ongoingRequestCount--;
}
}
_rebuildTree() {
const {
_cache
} = this;
for (const tile of _cache.values()) {
tile.parent = null;
tile.children.length = 0;
}
for (const tile of _cache.values()) {
const parent = this._getNearestAncestor(tile.x, tile.y, tile.z);
tile.parent = parent;
if (parent) {
parent.children.push(tile);
}
}
}
_resizeCache() {
const {
_cache,
opts
} = this;
const maxCacheSize = opts.maxCacheSize || (opts.maxCacheByteSize ? Infinity : DEFAULT_CACHE_SCALE * this.selectedTiles.length);
const maxCacheByteSize = opts.maxCacheByteSize || Infinity;
const overflown = _cache.size > maxCacheSize || this._cacheByteSize > maxCacheByteSize;
if (overflown) {
for (const [tileId, tile] of _cache) {
if (!tile.isVisible) {
this._cacheByteSize -= opts.maxCacheByteSize ? tile.byteLength : 0;
_cache.delete(tileId);
this.opts.onTileUnload(tile);
}
if (_cache.size <= maxCacheSize && this._cacheByteSize <= maxCacheByteSize) {
break;
}
}
this._rebuildTree();
this._dirty = true;
}
if (this._dirty) {
this._tiles = Array.from(this._cache.values()).sort((t1, t2) => t1.z - t2.z);
this._dirty = false;
}
}
_getTile({
x,
y,
z
}, create) {
const tileId = "".concat(x, ",").concat(y, ",").concat(z);
let tile = this._cache.get(tileId);
let needsReload = false;
if (!tile && create) {
tile = new Tile2DHeader({
x,
y,
z
});
Object.assign(tile, this.getTileMetadata(tile));
needsReload = true;
this._cache.set(tileId, tile);
this._dirty = true;
} else if (tile && tile.needsReload) {
needsReload = true;
}
if (needsReload) {
tile.loadData({
getData: this.opts.getTileData,
requestScheduler: this._requestScheduler,
onLoad: this.onTileLoad,
onError: this.opts.onTileError
});
}
return tile;
}
_getNearestAncestor(x, y, z) {
const {
_minZoom = 0
} = this;
let index = {
x,
y,
z
};
while (index.z > _minZoom) {
index = this.getParentIndex(index);
const parent = this._getTile(index);
if (parent) {
return parent;
}
}
return null;
}
}
function updateTileStateDefault(allTiles) {
for (const tile of allTiles) {
tile.state = 0;
}
for (const tile of allTiles) {
if (tile.isSelected && !getPlaceholderInAncestors(tile)) {
getPlaceholderInChildren(tile);
}
}
for (const tile of allTiles) {
tile.isVisible = Boolean(tile.state & TILE_STATE_VISIBLE);
}
}
function updateTileStateReplace(allTiles) {
for (const tile of allTiles) {
tile.state = 0;
}
for (const tile of allTiles) {
if (tile.isSelected) {
getPlaceholderInAncestors(tile);
}
}
const sortedTiles = Array.from(allTiles).sort((t1, t2) => t1.z - t2.z);
for (const tile of sortedTiles) {
tile.isVisible = Boolean(tile.state & TILE_STATE_VISIBLE);
if (tile.isVisible || tile.state & TILE_STATE_VISITED) {
for (const child of tile.children) {
child.state = TILE_STATE_VISITED;
}
} else if (tile.isSelected) {
getPlaceholderInChildren(tile);
}
}
}
function getPlaceholderInAncestors(tile) {
while (tile) {
if (tile.isLoaded || tile.content) {
tile.state |= TILE_STATE_VISIBLE;
return true;
}
tile = tile.parent;
}
return false;
}
function getPlaceholderInChildren(tile) {
for (const child of tile.children) {
if (child.isLoaded || child.content) {
child.state |= TILE_STATE_VISIBLE;
} else {
getPlaceholderInChildren(child);
}
}
}
//# sourceMappingURL=tileset-2d.js.map