Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Preloading tiles for camera animation #11328

Merged
merged 19 commits into from
Dec 22, 2021
Merged
Show file tree
Hide file tree
Changes from 15 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
99 changes: 99 additions & 0 deletions debug/preload-tiles.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
<!DOCTYPE html>
<html>
<head>
<title>Mapbox GL JS debug page</title>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
<link rel='stylesheet' href='../dist/mapbox-gl.css' />
<style>
body { margin: 0; padding: 0; }
html, body, #map { height: 100%; }
#controls { position: absolute; top: 0; left: 0; }
</style>
</head>

<body>
<div id='map'></div>
<div id='controls'>
<button id="flyTo-preload">Preload flyTo</button><br />
<button id="flyTo-run">Run flyTo</button><br />
<button id="easeTo-preload">Preload easeTo</button><br />
<button id="easeTo-run">Run easeTo</button><br />
<button id="enable-dem">Enable DEM</button><br />
</div>

<script src='../dist/mapbox-gl-dev.js'></script>
<script src='../debug/access_token_generated.js'></script>
<script>

var map = window.map = new mapboxgl.Map({
container: 'map',
zoom: 10,
center: [-119.5375, 37.7425],
style: 'mapbox://styles/mapbox/streets-v11',
minTileCacheSize: 1000,
});

const halfDome = [-119.5329, 37.7460];
const elCapitan = [-119.6358, 37.7422];

const flyToOptions = {
center: halfDome,
zoom: 13,
bearing: 135,
pitch: 45,
duration: 5000
};

const easeToOptions = {
center: elCapitan,
zoom: 14,
bearing: 90,
pitch: 65,
duration: 5000
};

document.getElementById('flyTo-preload').onclick = async function () {
this.innerText = 'Preload flyTo 🔄';
map.flyTo({...flyToOptions, preloadOnly: true});
await map.once('idle');
this.innerText = 'Preload flyTo ✅';
};

document.getElementById('flyTo-run').onclick = async function () {
this.innerText = 'Run flyTo 🔄';
map.flyTo(flyToOptions);
await map.once('idle');
this.innerText = 'Run flyTo ✅';
};

document.getElementById('easeTo-preload').onclick = async function () {
this.innerText = 'Preload easeTo 🔄';
map.easeTo({...easeToOptions, preloadOnly: true});
await map.once('idle');
this.innerText = 'Preload easeTo ✅';
};

document.getElementById('easeTo-run').onclick = async function () {
this.innerText = 'Run easeTo 🔄';
map.easeTo(easeToOptions);
await map.once('idle');
this.innerText = 'Run easeTo ✅';
};

document.getElementById('enable-dem').onclick = async function () {
map.addSource('mapbox-dem', {
'type': 'raster-dem',
'url': 'mapbox://mapbox.mapbox-terrain-dem-v1',
'tileSize': 512
});

map.setTerrain({'source': 'mapbox-dem', 'exaggeration': 1.5});
await map.once('idle');

this.innerText = 'Enable DEM ✅';
};

</script>
</body>
</html>
61 changes: 55 additions & 6 deletions src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
import Tile from './tile.js';
import {Event, ErrorEvent, Evented} from '../util/evented.js';
import TileCache from './tile_cache.js';
import {keysDifference, values} from '../util/util.js';
import {asyncAll, keysDifference, values} from '../util/util.js';
import Context from '../gl/context.js';
import Point from '@mapbox/point-geometry';
import browser from '../util/browser.js';
Expand All @@ -12,7 +12,7 @@ import assert from 'assert';
import SourceFeatureState from './source_state.js';

import type {Source} from './source.js';
import type Map from '../ui/map.js';
import type {default as MapboxMap} from '../ui/map.js';
import type Style from '../style/style.js';
import type Transform from '../geo/transform.js';
import type {TileState} from './tile.js';
Expand All @@ -32,7 +32,7 @@ import type {QueryGeometry, TilespaceQueryGeometry} from '../style/query_geometr
*/
class SourceCache extends Evented {
id: string;
map: Map;
map: MapboxMap;
style: Style;

_source: Source;
Expand All @@ -43,6 +43,7 @@ class SourceCache extends Evented {
_cache: TileCache;
_timers: {[_: any]: TimeoutID};
_cacheTimers: {[_: any]: TimeoutID};
_minTileCacheSize: ?number;
_maxTileCacheSize: ?number;
_paused: boolean;
_shouldReloadOnResume: boolean;
Expand Down Expand Up @@ -88,15 +89,17 @@ class SourceCache extends Evented {
this._cache = new TileCache(0, this._unloadTile.bind(this));
this._timers = {};
this._cacheTimers = {};
this._minTileCacheSize = null;
this._maxTileCacheSize = null;
this._loadedParentTiles = {};

this._coveredTiles = {};
this._state = new SourceFeatureState();
}

onAdd(map: Map) {
onAdd(map: MapboxMap) {
this.map = map;
this._minTileCacheSize = map ? map._minTileCacheSize : null;
this._maxTileCacheSize = map ? map._maxTileCacheSize : null;
}

Expand Down Expand Up @@ -408,7 +411,8 @@ class SourceCache extends Evented {
const commonZoomRange = 5;

const viewDependentMaxSize = Math.floor(approxTilesInView * commonZoomRange);
const maxSize = typeof this._maxTileCacheSize === 'number' ? Math.min(this._maxTileCacheSize, viewDependentMaxSize) : viewDependentMaxSize;
const minSize = typeof this._minTileCacheSize === 'number' ? Math.max(this._minTileCacheSize, viewDependentMaxSize) : viewDependentMaxSize;
const maxSize = typeof this._maxTileCacheSize === 'number' ? Math.min(this._maxTileCacheSize, minSize) : minSize;

this._cache.setMaxSize(maxSize);
}
Expand Down Expand Up @@ -738,7 +742,8 @@ class SourceCache extends Evented {
const cached = Boolean(tile);
if (!cached) {
const painter = this.map ? this.map.painter : null;
tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor(), this.transform.tileZoom, painter, this._source.type === 'raster' || this._source.type === 'raster-dem');
const isRaster = this._source.type === 'raster' || this._source.type === 'raster-dem';
tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor(), this.transform.tileZoom, painter, isRaster);
this._loadTile(tile, this._tileLoaded.bind(this, tile, tileID.key, tile.state));
}

Expand Down Expand Up @@ -921,6 +926,50 @@ class SourceCache extends Evented {
}
this._cache.filter(tile => !tile.hasDependency(namespaces, keys));
}

/**
* Preloads all tiles that will be requested for one or a series of transformations
*
* @private
* @returns {Object} Returns `this` | Promise.
*/
_preloadTiles(transform: Transform | Array<Transform>, callback: Callback<any>) {
const coveringTilesIDs: Map<number, OverscaledTileID> = new Map();
const transforms = Array.isArray(transform) ? transform : [transform];

const terrain = this.map.painter.terrain;
const tileSize = this.usedForTerrain && terrain ? terrain.getScaledDemTileSize() : this._source.tileSize;

for (const tr of transforms) {
const tileIDs = tr.coveringTiles({
tileSize,
minzoom: this._source.minzoom,
maxzoom: this._source.maxzoom,
roundZoom: this._source.roundZoom && !this.usedForTerrain,
reparseOverscaled: this._source.reparseOverscaled,
isTerrainDEM: this.usedForTerrain
});

for (const tileID of tileIDs) {
coveringTilesIDs.set(tileID.key, tileID);
}

if (this.usedForTerrain) {
tr.updateElevation(false);
}
}

const tileIDs = Array.from(coveringTilesIDs.values());
const isRaster = this._source.type === 'raster' || this._source.type === 'raster-dem';

asyncAll(tileIDs, (tileID, done) => {
const tile = new Tile(tileID, this._source.tileSize * tileID.overscaleFactor(), this.transform.tileZoom, this.map.painter, isRaster);
this._loadTile(tile, (err) => {
if (this._source.type === 'raster-dem' && tile.dem) this._backfillDEM(tile);
done(err, tile);
});
}, callback);
}
}

SourceCache.maxOverzooming = 10;
Expand Down
11 changes: 8 additions & 3 deletions src/terrain/terrain.js
Original file line number Diff line number Diff line change
Expand Up @@ -274,11 +274,10 @@ export class Terrain extends Elevation {
'This leads to lower resolution of hillshade. For full hillshade resolution but higher memory consumption, define another raster DEM source.');
}
// Lower tile zoom is sufficient for terrain, given the size of terrain grid.
const demScale = this.sourceCache.getSource().tileSize / GRID_DIM;
const proxyTileSize = this.proxySourceCache.getSource().tileSize;
const scaledDemTileSize = this.getScaledDemTileSize();
// Dem tile needs to be parent or at least of the same zoom level as proxy tile.
// Tile cover roundZoom behavior is set to the same as for proxy (false) in SourceCache.update().
this.sourceCache.update(transform, demScale * proxyTileSize, true);
this.sourceCache.update(transform, scaledDemTileSize, true);
// As a result of update, we get new set of tiles: reset lookup cache.
this._findCoveringTileCache[this.sourceCache.id] = {};
};
Expand Down Expand Up @@ -308,6 +307,12 @@ export class Terrain extends Elevation {
}
}

getScaledDemTileSize(): number {
const demScale = this.sourceCache.getSource().tileSize / GRID_DIM;
const proxyTileSize = this.proxySourceCache.getSource().tileSize;
return demScale * proxyTileSize;
}

_checkRenderCacheEfficiency() {
const renderCacheInfo = this.renderCacheEfficiency(this._style);
if (this._style.map._optimizeForTerrain) {
Expand Down
Loading