UNPKG

@deck.gl/geo-layers

Version:

deck.gl layers supporting geospatial use cases and GIS formats

431 lines (354 loc) 9.49 kB
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