Merge pull request #9008 from ahocevar/decouple-group-creation
Decouple render instruction creation from rendering
This commit is contained in:
@@ -6,10 +6,9 @@ import Tile from './Tile.js';
|
||||
import TileState from './TileState.js';
|
||||
import {createCanvasContext2D} from './dom.js';
|
||||
import {listen, unlistenByKey} from './events.js';
|
||||
import {containsExtent, getHeight, getIntersection, getWidth} from './extent.js';
|
||||
import {getHeight, getIntersection, getWidth} from './extent.js';
|
||||
import EventType from './events/EventType.js';
|
||||
import {loadFeaturesXhr} from './featureloader.js';
|
||||
import {VOID} from './functions.js';
|
||||
|
||||
|
||||
/**
|
||||
@@ -40,11 +39,10 @@ class VectorImageTile extends Tile {
|
||||
* instantiate for source tiles.
|
||||
* @param {function(this: import("./source/VectorTile.js").default, import("./events/Event.js").default): void} handleTileChange
|
||||
* Function to call when a source tile's state changes.
|
||||
* @param {number} zoom Integer zoom to render the tile for.
|
||||
*/
|
||||
constructor(tileCoord, state, sourceRevision, format, tileLoadFunction,
|
||||
urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles,
|
||||
pixelRatio, projection, tileClass, handleTileChange, zoom) {
|
||||
pixelRatio, projection, tileClass, handleTileChange) {
|
||||
|
||||
super(tileCoord, state, {transition: 0});
|
||||
|
||||
@@ -72,6 +70,18 @@ class VectorImageTile extends Tile {
|
||||
*/
|
||||
this.sourceTiles_ = sourceTiles;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("./tilegrid/TileGrid.js").default}
|
||||
*/
|
||||
this.sourceTileGrid_ = sourceTileGrid;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.sourceTilesLoaded = false;
|
||||
|
||||
/**
|
||||
* Keys of source tiles used by this tile. Use with {@link #getTile}.
|
||||
* @type {Array<string>}
|
||||
@@ -83,11 +93,6 @@ class VectorImageTile extends Tile {
|
||||
*/
|
||||
this.extent = null;
|
||||
|
||||
/**
|
||||
* @type {number}
|
||||
*/
|
||||
this.sourceRevision_ = sourceRevision;
|
||||
|
||||
/**
|
||||
* @type {import("./tilecoord.js").TileCoord}
|
||||
*/
|
||||
@@ -98,23 +103,22 @@ class VectorImageTile extends Tile {
|
||||
*/
|
||||
this.loadListenerKeys_ = [];
|
||||
|
||||
/**
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.isInterimTile = !sourceTileGrid;
|
||||
|
||||
/**
|
||||
* @type {Array<import("./events.js").EventsKey>}
|
||||
*/
|
||||
this.sourceTileListenerKeys_ = [];
|
||||
|
||||
/**
|
||||
* Use only source tiles that are loaded already
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.useLoadedOnly = zoom != tileCoord[0];
|
||||
this.key = sourceRevision.toString();
|
||||
|
||||
if (urlTileCoord) {
|
||||
if (urlTileCoord && sourceTileGrid) {
|
||||
const extent = this.extent = tileGrid.getTileCoordExtent(urlTileCoord);
|
||||
const resolution = tileGrid.getResolution(zoom);
|
||||
const resolution = this.resolution_ = tileGrid.getResolution(urlTileCoord[0]);
|
||||
const sourceZ = sourceTileGrid.getZForResolution(resolution);
|
||||
const useLoadedOnly = this.useLoadedOnly;
|
||||
let loadCount = 0;
|
||||
sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) {
|
||||
let sharedExtent = getIntersection(extent,
|
||||
sourceTileGrid.getTileCoordExtent(sourceTileCoord));
|
||||
@@ -125,10 +129,9 @@ class VectorImageTile extends Tile {
|
||||
if (getWidth(sharedExtent) / resolution >= 0.5 &&
|
||||
getHeight(sharedExtent) / resolution >= 0.5) {
|
||||
// only include source tile if overlap is at least 1 pixel
|
||||
++loadCount;
|
||||
const sourceTileKey = sourceTileCoord.toString();
|
||||
let sourceTile = sourceTiles[sourceTileKey];
|
||||
if (!sourceTile && !useLoadedOnly) {
|
||||
if (!sourceTile) {
|
||||
const tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection);
|
||||
sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord,
|
||||
tileUrl == undefined ? TileState.EMPTY : TileState.IDLE,
|
||||
@@ -137,61 +140,24 @@ class VectorImageTile extends Tile {
|
||||
this.sourceTileListenerKeys_.push(
|
||||
listen(sourceTile, EventType.CHANGE, handleTileChange));
|
||||
}
|
||||
if (sourceTile && (!useLoadedOnly || sourceTile.getState() == TileState.LOADED)) {
|
||||
sourceTile.consumers++;
|
||||
this.tileKeys.push(sourceTileKey);
|
||||
}
|
||||
sourceTile.consumers++;
|
||||
this.tileKeys.push(sourceTileKey);
|
||||
}
|
||||
}.bind(this));
|
||||
|
||||
if (useLoadedOnly && loadCount == this.tileKeys.length) {
|
||||
this.finishLoading_();
|
||||
}
|
||||
|
||||
this.createInterimTile_ = function() {
|
||||
if (this.getState() !== TileState.LOADED && !useLoadedOnly) {
|
||||
let bestZoom = -1;
|
||||
for (const key in sourceTiles) {
|
||||
const sourceTile = sourceTiles[key];
|
||||
if (sourceTile.getState() === TileState.LOADED) {
|
||||
const sourceTileCoord = sourceTile.tileCoord;
|
||||
const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
|
||||
if (containsExtent(sourceTileExtent, extent) && sourceTileCoord[0] > bestZoom) {
|
||||
bestZoom = sourceTileCoord[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
if (bestZoom !== -1) {
|
||||
const tile = new VectorImageTile(tileCoord, state, sourceRevision,
|
||||
format, tileLoadFunction, urlTileCoord, tileUrlFunction,
|
||||
sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection,
|
||||
tileClass, VOID, bestZoom);
|
||||
this.interimTile = tile;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
getInterimTile() {
|
||||
if (!this.interimTile) {
|
||||
this.createInterimTile_();
|
||||
}
|
||||
return super.getInterimTile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
disposeInternal() {
|
||||
delete this.createInterimTile_;
|
||||
this.state = TileState.ABORT;
|
||||
this.changed();
|
||||
if (!this.isInterimTile) {
|
||||
this.setState(TileState.ABORT);
|
||||
}
|
||||
if (this.interimTile) {
|
||||
this.interimTile.dispose();
|
||||
this.interimTile = null;
|
||||
}
|
||||
|
||||
for (let i = 0, ii = this.tileKeys.length; i < ii; ++i) {
|
||||
const sourceTileKey = this.tileKeys[i];
|
||||
const sourceTile = this.getTile(sourceTileKey);
|
||||
@@ -222,14 +188,65 @@ class VectorImageTile extends Tile {
|
||||
return this.context_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {boolean} Tile has a rendering context for the given layer.
|
||||
*/
|
||||
hasContext(layer) {
|
||||
return getUid(layer) in this.context_;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Canvas for this tile.
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @return {HTMLCanvasElement} Canvas.
|
||||
*/
|
||||
getImage(layer) {
|
||||
return this.getReplayState(layer).renderedTileRevision == -1 ?
|
||||
null : this.getContext(layer).canvas;
|
||||
return this.hasContext(layer) ? this.getContext(layer).canvas : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @override
|
||||
* @return {VectorImageTile} Interim tile.
|
||||
*/
|
||||
getInterimTile() {
|
||||
const sourceTileGrid = this.sourceTileGrid_;
|
||||
const state = this.getState();
|
||||
if (state < TileState.LOADED && !this.interimTile) {
|
||||
let z = this.tileCoord[0];
|
||||
const minZoom = sourceTileGrid.getMinZoom();
|
||||
while (--z > minZoom) {
|
||||
let covered = true;
|
||||
const tileKeys = [];
|
||||
sourceTileGrid.forEachTileCoord(this.extent, z, function(tileCoord) {
|
||||
const key = tileCoord.toString();
|
||||
if (key in this.sourceTiles_ && this.sourceTiles_[key].getState() === TileState.LOADED) {
|
||||
tileKeys.push(key);
|
||||
} else {
|
||||
covered = false;
|
||||
}
|
||||
}.bind(this));
|
||||
if (covered && tileKeys.length) {
|
||||
for (let i = 0, ii = tileKeys.length; i < ii; ++i) {
|
||||
this.sourceTiles_[tileKeys[i]].consumers++;
|
||||
}
|
||||
const tile = new VectorImageTile(this.tileCoord, TileState.IDLE, Number(this.key), null, null,
|
||||
this.wrappedTileCoord, null, null, null, this.sourceTiles_,
|
||||
undefined, null, null, null);
|
||||
tile.extent = this.extent;
|
||||
tile.tileKeys = tileKeys;
|
||||
tile.context_ = this.context_;
|
||||
setTimeout(function() {
|
||||
tile.sourceTilesLoaded = true;
|
||||
tile.changed();
|
||||
}, 16);
|
||||
this.interimTile = tile;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
const interimTile = /** @type {VectorImageTile} */ (this.interimTile);
|
||||
return state === TileState.LOADED ? this : (interimTile || this);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -249,13 +266,6 @@ class VectorImageTile extends Tile {
|
||||
return this.replayState_[key];
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getKey() {
|
||||
return this.tileKeys.join('/') + '-' + this.sourceRevision_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} tileKey Key (tileCoord) of the source tile.
|
||||
* @return {import("./VectorTile.js").default} Source tile.
|
||||
@@ -308,7 +318,7 @@ class VectorImageTile extends Tile {
|
||||
}.bind(this));
|
||||
}
|
||||
if (leftToLoad - Object.keys(errorSourceTiles).length == 0) {
|
||||
setTimeout(this.finishLoading_.bind(this), 0);
|
||||
setTimeout(this.finishLoading_.bind(this), 16);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -331,7 +341,7 @@ class VectorImageTile extends Tile {
|
||||
this.loadListenerKeys_.forEach(unlistenByKey);
|
||||
this.loadListenerKeys_.length = 0;
|
||||
this.sourceTilesLoaded = true;
|
||||
this.setState(TileState.LOADED);
|
||||
this.changed();
|
||||
} else {
|
||||
this.setState(empty == this.tileKeys.length ? TileState.EMPTY : TileState.ERROR);
|
||||
}
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
/**
|
||||
* @module ol/VectorTile
|
||||
*/
|
||||
import {containsExtent} from './extent.js';
|
||||
import {getUid} from './util.js';
|
||||
import Tile from './Tile.js';
|
||||
import TileState from './TileState.js';
|
||||
|
||||
@@ -140,37 +138,12 @@ class VectorTile extends Tile {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @param {string} layerId UID of the layer.
|
||||
* @param {string} key Key.
|
||||
* @return {import("./render/canvas/ExecutorGroup.js").default} Executor group.
|
||||
*/
|
||||
getExecutorGroup(layer, key) {
|
||||
return this.executorGroups_[getUid(layer) + ',' + key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the best matching lower resolution replay group for a given zoom and extent.
|
||||
* @param {import("./layer/Layer").default} layer Layer.
|
||||
* @param {number} zoom Zoom.
|
||||
* @param {import("./extent").Extent} extent Extent.
|
||||
* @return {import("./render/canvas/ExecutorGroup.js").default} Executor groups.
|
||||
*/
|
||||
getLowResExecutorGroup(layer, zoom, extent) {
|
||||
const layerId = getUid(layer);
|
||||
let bestZoom = 0;
|
||||
let replayGroup = null;
|
||||
for (const key in this.executorGroups_) {
|
||||
const keyData = key.split(',');
|
||||
const candidateZoom = Number(keyData[1]);
|
||||
if (keyData[0] === layerId && candidateZoom <= zoom) {
|
||||
const candidate = this.executorGroups_[key];
|
||||
if (containsExtent(candidate.getMaxExtent(), extent) && candidateZoom > bestZoom) {
|
||||
replayGroup = candidate;
|
||||
bestZoom = candidateZoom;
|
||||
}
|
||||
}
|
||||
}
|
||||
return replayGroup;
|
||||
getExecutorGroup(layerId, key) {
|
||||
return this.executorGroups_[layerId + ',' + key];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -242,12 +215,12 @@ class VectorTile extends Tile {
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("./layer/Layer.js").default} layer Layer.
|
||||
* @param {string} layerId UID of the layer.
|
||||
* @param {string} key Key.
|
||||
* @param {import("./render/canvas/ExecutorGroup.js").default} executorGroup Executor group.
|
||||
*/
|
||||
setExecutorGroup(layer, key, executorGroup) {
|
||||
this.executorGroups_[getUid(layer) + ',' + key] = executorGroup;
|
||||
setExecutorGroup(layerId, key, executorGroup) {
|
||||
this.executorGroups_[layerId + ',' + key] = executorGroup;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -280,13 +280,6 @@ class ExecutorGroup {
|
||||
return flatClipCoords;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return {import("../../extent.js").Extent} The extent of the replay group.
|
||||
*/
|
||||
getMaxExtent() {
|
||||
return this.maxExtent_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {number|undefined} zIndex Z index.
|
||||
* @param {import("./BuilderType.js").default} builderType Builder type.
|
||||
|
||||
@@ -48,6 +48,18 @@ class LayerRenderer extends Observable {
|
||||
return abstract();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Object<number, Object<string, import("../Tile.js").default>>} tiles Lookup of loaded tiles by zoom level.
|
||||
* @param {number} zoom Zoom level.
|
||||
* @param {import("../Tile.js").default} tile Tile.
|
||||
*/
|
||||
loadedTileCallback(tiles, zoom, tile) {
|
||||
if (!tiles[zoom]) {
|
||||
tiles[zoom] = {};
|
||||
}
|
||||
tiles[zoom][tile.tileCoord.toString()] = tile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a function that adds loaded tiles to the tile lookup.
|
||||
* @param {import("../source/Tile.js").default} source Tile source.
|
||||
@@ -63,20 +75,13 @@ class LayerRenderer extends Observable {
|
||||
* @param {number} zoom Zoom level.
|
||||
* @param {import("../TileRange.js").default} tileRange Tile range.
|
||||
* @return {boolean} The tile range is fully loaded.
|
||||
* @this {LayerRenderer}
|
||||
*/
|
||||
function(zoom, tileRange) {
|
||||
/**
|
||||
* @param {import("../Tile.js").default} tile Tile.
|
||||
*/
|
||||
function callback(tile) {
|
||||
if (!tiles[zoom]) {
|
||||
tiles[zoom] = {};
|
||||
}
|
||||
tiles[zoom][tile.tileCoord.toString()] = tile;
|
||||
}
|
||||
const callback = this.loadedTileCallback.bind(this, tiles, zoom);
|
||||
return source.forEachLoadedTile(projection, zoom, tileRange, callback);
|
||||
}
|
||||
);
|
||||
).bind(this);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -65,11 +65,11 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @protected
|
||||
* @param {import("../../Tile.js").default} tile Tile.
|
||||
* @return {boolean} Tile is drawable.
|
||||
*/
|
||||
isDrawableTile_(tile) {
|
||||
isDrawableTile(tile) {
|
||||
const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer());
|
||||
const tileState = tile.getState();
|
||||
const useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
|
||||
@@ -99,7 +99,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
this.newTiles_ = true;
|
||||
}
|
||||
}
|
||||
if (!this.isDrawableTile_(tile)) {
|
||||
if (!this.isDrawableTile(tile)) {
|
||||
tile = tile.getInterimTile();
|
||||
}
|
||||
return tile;
|
||||
@@ -177,7 +177,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer {
|
||||
for (let x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (let y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
const tile = this.getTile(z, x, y, pixelRatio, projection);
|
||||
if (this.isDrawableTile_(tile)) {
|
||||
if (this.isDrawableTile(tile)) {
|
||||
const uid = getUid(this);
|
||||
if (tile.getState() == TileState.LOADED) {
|
||||
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
|
||||
|
||||
@@ -3,9 +3,10 @@
|
||||
*/
|
||||
import {getUid} from '../../util.js';
|
||||
import {createCanvasContext2D} from '../../dom.js';
|
||||
import {getValues} from '../../obj.js';
|
||||
import TileState from '../../TileState.js';
|
||||
import ViewHint from '../../ViewHint.js';
|
||||
import {listen, unlisten} from '../../events.js';
|
||||
import {listen, unlisten, unlistenByKey} from '../../events.js';
|
||||
import EventType from '../../events/EventType.js';
|
||||
import rbush from 'rbush';
|
||||
import {buffer, containsCoordinate, equals, getIntersection, getTopLeft, intersects} from '../../extent.js';
|
||||
@@ -122,6 +123,18 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
*/
|
||||
this.renderedLayerRevision_;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array<import("../../VectorImageTile.js").default>}
|
||||
*/
|
||||
this.tilesWithoutImage_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object<string, import("../../events").EventsKey>}
|
||||
*/
|
||||
this.tileChangeKeys_ = {};
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {import("../../transform.js").Transform}
|
||||
@@ -140,29 +153,88 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
*/
|
||||
disposeInternal() {
|
||||
unlisten(labelCache, EventType.CLEAR, this.handleFontsChanged_, this);
|
||||
getValues(this.tileChangeKeys_).forEach(unlistenByKey);
|
||||
super.disposeInternal();
|
||||
}
|
||||
|
||||
/**
|
||||
* Listen to tile changes and mark tile as loaded when source tiles are loaded.
|
||||
* @param {import("../../VectorImageTile").default} tile Tile to listen on.
|
||||
* @param {number} pixelRatio Pixel ratio.
|
||||
* @param {number} projection Projection.
|
||||
* @private
|
||||
*/
|
||||
listenTileChange_(tile, pixelRatio, projection) {
|
||||
const uid = getUid(tile);
|
||||
if (!(uid in this.tileChangeKeys_) && tile.getState() === TileState.IDLE) {
|
||||
this.tileChangeKeys_[uid] = listen(tile, EventType.CHANGE, function() {
|
||||
const state = tile.getState();
|
||||
if (state === TileState.ABORT || tile.sourceTilesLoaded) {
|
||||
unlistenByKey(this.tileChangeKeys_[uid]);
|
||||
delete this.tileChangeKeys_[uid];
|
||||
if (tile.sourceTilesLoaded) {
|
||||
// Create render instructions immediately when all source tiles are available.
|
||||
//TODO Make sure no canvas operations are involved in instruction creation.
|
||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||
//FIXME This should be done by the tile, and VectorImage tiles should be layer specific
|
||||
tile.setState(TileState.LOADED);
|
||||
}
|
||||
}
|
||||
}.bind(this));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getTile(z, x, y, pixelRatio, projection) {
|
||||
const tile = super.getTile(z, x, y, pixelRatio, projection);
|
||||
const tile = /** @type {import("../../VectorImageTile.js").default} */ (super.getTile(z, x, y, pixelRatio, projection));
|
||||
this.listenTileChange_(tile, pixelRatio, projection);
|
||||
if (tile.isInterimTile) {
|
||||
// Register change listener also on the original tile
|
||||
const source = /** @type {import("../../source/VectorTile").default} */ (this.getLayer().getSource());
|
||||
const originalTile = /** @type {import("../../VectorImageTile").default} */ (source.getTile(z, x, y, pixelRatio, projection));
|
||||
this.listenTileChange_(originalTile, pixelRatio, projection);
|
||||
}
|
||||
if (tile.getState() === TileState.LOADED) {
|
||||
this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
||||
if (this.context) {
|
||||
this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection);
|
||||
// Update existing instructions if necessary (e.g. when the style has changed)
|
||||
this.updateExecutorGroup_(tile, pixelRatio, projection);
|
||||
const layer = this.getLayer();
|
||||
if (tile.getReplayState(layer).renderedTileRevision !== -1) {
|
||||
// Update existing tile image if necessary (e.g. when the style has changed)
|
||||
this.renderTileImage_(tile, pixelRatio, projection);
|
||||
} else {
|
||||
// Render new tile images after existing tiles have been drawn to the target canvas.
|
||||
this.tilesWithoutImage_.push(tile);
|
||||
}
|
||||
}
|
||||
return tile;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
loadedTileCallback(tiles, zoom, tile) {
|
||||
if (!tile.hasContext(this.getLayer())) {
|
||||
this.tilesWithoutImage_.push(tile);
|
||||
return false;
|
||||
}
|
||||
return super.loadedTileCallback(tiles, zoom, tile);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
isDrawableTile(tile) {
|
||||
return super.isDrawableTile(tile) && tile.hasContext(this.getLayer());
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritDoc
|
||||
*/
|
||||
getTileImage(tile) {
|
||||
const tileLayer = /** @type {import("../../layer/Tile.js").default} */ (this.getLayer());
|
||||
return /** @type {import("../../VectorImageTile.js").default} */ (tile).getImage(tileLayer);
|
||||
return tile.getImage(tileLayer);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -184,7 +256,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @param {import("../../proj/Projection.js").default} projection Projection.
|
||||
* @private
|
||||
*/
|
||||
createExecutorGroup_(tile, pixelRatio, projection) {
|
||||
updateExecutorGroup_(tile, pixelRatio, projection) {
|
||||
const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer());
|
||||
const revision = layer.getRevision();
|
||||
const renderOrder = /** @type {import("../../render.js").OrderFunction} */ (layer.getRenderOrder()) || null;
|
||||
@@ -207,15 +279,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
if (tile.useLoadedOnly) {
|
||||
const lowResExecutorGroup = sourceTile.getLowResExecutorGroup(layer, zoom, tileExtent);
|
||||
if (lowResExecutorGroup) {
|
||||
// reuse existing replay if we're rendering an interim tile
|
||||
sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), lowResExecutorGroup);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
const sourceTileCoord = sourceTile.tileCoord;
|
||||
const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
|
||||
const sharedExtent = getIntersection(tileExtent, sourceTileExtent);
|
||||
@@ -271,7 +334,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const executorGroupInstructions = builderGroup.finish();
|
||||
const renderingReplayGroup = new CanvasExecutorGroup(sharedExtent, resolution,
|
||||
pixelRatio, source.getOverlaps(), this.declutterTree_, executorGroupInstructions, layer.getRenderBuffer());
|
||||
sourceTile.setExecutorGroup(layer, tile.tileCoord.toString(), renderingReplayGroup);
|
||||
sourceTile.setExecutorGroup(getUid(layer), tile.tileCoord.toString(), renderingReplayGroup);
|
||||
}
|
||||
builderState.renderedRevision = revision;
|
||||
builderState.renderedRenderOrder = renderOrder;
|
||||
@@ -303,7 +366,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer,
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer),
|
||||
tile.tileCoord.toString()));
|
||||
found = found || executorGroup.forEachFeatureAtCoordinate(coordinate, resolution, rotation, hitTolerance, {},
|
||||
/**
|
||||
@@ -374,11 +437,15 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
* @inheritDoc
|
||||
*/
|
||||
renderFrame(frameState, layerState) {
|
||||
this.tilesWithoutImage_ = [];
|
||||
super.renderFrame(frameState, layerState);
|
||||
|
||||
const layer = /** @type {import("../../layer/VectorTile.js").default} */ (this.getLayer());
|
||||
const viewHints = frameState.viewHints;
|
||||
const hifi = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||
const renderMode = layer.getRenderMode();
|
||||
if (renderMode === VectorTileRenderType.IMAGE) {
|
||||
this.renderMissingTileImages_(hifi, frameState);
|
||||
return this.container_;
|
||||
}
|
||||
|
||||
@@ -412,8 +479,6 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
if (declutterReplays) {
|
||||
this.declutterTree_.clear();
|
||||
}
|
||||
const viewHints = frameState.viewHints;
|
||||
const snapToPixel = !(viewHints[ViewHint.ANIMATING] || viewHints[ViewHint.INTERACTING]);
|
||||
const tiles = this.renderedTiles;
|
||||
const tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
|
||||
const clips = [];
|
||||
@@ -431,7 +496,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
if (sourceTile.getState() != TileState.LOADED) {
|
||||
continue;
|
||||
}
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer, tileCoord.toString()));
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer), tileCoord.toString()));
|
||||
if (!executorGroup || !executorGroup.hasExecutors(replayTypes)) {
|
||||
// sourceTile was not yet loaded when this.createReplayGroup_() was
|
||||
// called, or it has no replays of the types we want to render
|
||||
@@ -460,14 +525,14 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
context.clip();
|
||||
}
|
||||
}
|
||||
executorGroup.execute(context, transform, rotation, {}, snapToPixel, replayTypes, declutterReplays);
|
||||
executorGroup.execute(context, transform, rotation, {}, hifi, replayTypes, declutterReplays);
|
||||
context.restore();
|
||||
clips.push(currentClip);
|
||||
zs.push(currentZ);
|
||||
}
|
||||
}
|
||||
if (declutterReplays) {
|
||||
replayDeclutter(declutterReplays, context, rotation, snapToPixel);
|
||||
replayDeclutter(declutterReplays, context, rotation, hifi);
|
||||
}
|
||||
|
||||
const opacity = layerState.opacity;
|
||||
@@ -475,9 +540,34 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
canvas.style.opacity = opacity;
|
||||
}
|
||||
|
||||
// Now that we have rendered the tiles we have already, let's prepare new tiles for the
|
||||
// next frame
|
||||
this.renderMissingTileImages_(hifi, frameState);
|
||||
|
||||
return this.container_;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {boolean} hifi We have time to render a high fidelity map image.
|
||||
* @param {import('../../PluggableMap.js').FrameState} frameState Frame state.
|
||||
*/
|
||||
renderMissingTileImages_(hifi, frameState) {
|
||||
// Even when we have time to render hifi, do not spend more than 100 ms in this render frame,
|
||||
// to avoid delays when the user starts interacting again with the map.
|
||||
while (this.tilesWithoutImage_.length && Date.now() - frameState.time < 100) {
|
||||
frameState.animate = true;
|
||||
const tile = this.tilesWithoutImage_.pop();
|
||||
// When we don't have time to render hifi, only render interim tiles until we have used up
|
||||
// half of the frame budget of 16 ms
|
||||
if (hifi || (tile.isInterimTile && Date.now() - frameState.time < 8)) {
|
||||
this.renderTileImage_(tile, frameState.pixelRatio, frameState.viewState.projection);
|
||||
}
|
||||
}
|
||||
if (this.tilesWithoutImage_.length) {
|
||||
frameState.animate = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {import("../../Feature.js").FeatureLike} feature Feature.
|
||||
* @param {number} squaredTolerance Squared tolerance.
|
||||
@@ -536,7 +626,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer {
|
||||
const transform = resetTransform(this.tmpTransform_);
|
||||
scaleTransform(transform, pixelScale, -pixelScale);
|
||||
translateTransform(transform, -tileExtent[0], -tileExtent[3]);
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(layer,
|
||||
const executorGroup = /** @type {CanvasExecutorGroup} */ (sourceTile.getExecutorGroup(getUid(layer),
|
||||
tile.tileCoord.toString()));
|
||||
executorGroup.execute(context, transform, 0, {}, true, replays);
|
||||
}
|
||||
|
||||
@@ -171,7 +171,7 @@ class VectorTile extends UrlTile {
|
||||
this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction,
|
||||
this.tileGrid, this.getTileGridForProjection(projection),
|
||||
this.sourceTiles_, pixelRatio, projection, this.tileClass,
|
||||
this.handleTileChange.bind(this), tileCoord[0]);
|
||||
this.handleTileChange.bind(this));
|
||||
|
||||
this.tileCache.set(tileCoordKey, tile);
|
||||
return tile;
|
||||
|
||||
@@ -75,8 +75,11 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
tileGrid: createXYZ()
|
||||
});
|
||||
source.getTile = function() {
|
||||
arguments[1] = TileState.LOADED;
|
||||
const tile = VectorTileSource.prototype.getTile.apply(source, arguments);
|
||||
tile.hasContext = function() {
|
||||
return true;
|
||||
};
|
||||
tile.sourceTilesLoaded = true;
|
||||
tile.setState(TileState.LOADED);
|
||||
return tile;
|
||||
};
|
||||
@@ -242,10 +245,9 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
sourceTile.getImage = function() {
|
||||
return document.createElement('canvas');
|
||||
};
|
||||
const tileUrlFunction = function() {};
|
||||
const tile = new VectorImageTile([0, 0, 0], undefined, undefined, undefined,
|
||||
undefined, [0, 0, 0], tileUrlFunction, createXYZ(), createXYZ(), {}, undefined,
|
||||
undefined, VectorTile, undefined, 0);
|
||||
const tile = new VectorImageTile([0, 0, 0], undefined, 1, undefined,
|
||||
undefined, [0, 0, 0], undefined, createXYZ(), createXYZ(), {'0,0,0': sourceTile}, undefined,
|
||||
undefined, undefined, undefined);
|
||||
tile.transition_ = 0;
|
||||
tile.wrappedTileCoord = [0, 0, 0];
|
||||
tile.setState(TileState.LOADED);
|
||||
@@ -256,6 +258,9 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
return tile;
|
||||
};
|
||||
const renderer = new CanvasVectorTileLayerRenderer(layer);
|
||||
renderer.isDrawableTile = function() {
|
||||
return true;
|
||||
};
|
||||
const proj = getProjection('EPSG:3857');
|
||||
const frameState = {
|
||||
extent: proj.getExtent(),
|
||||
@@ -333,7 +338,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() {
|
||||
}
|
||||
};
|
||||
frameState.layerStates[getUid(layer)] = {};
|
||||
renderer.renderedTiles = [new TileClass([0, 0, -1])];
|
||||
renderer.renderedTiles = [new TileClass([0, 0, -1], undefined, 1)];
|
||||
renderer.forEachFeatureAtCoordinate(
|
||||
coordinate, frameState, 0, spy, undefined);
|
||||
expect(spy.callCount).to.be(1);
|
||||
|
||||
@@ -17,7 +17,7 @@ describe('ol.VectorImageTile', function() {
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
const sourceTile = tile.getTile(tile.tileKeys[0]);
|
||||
@@ -30,7 +30,7 @@ describe('ol.VectorImageTile', function() {
|
||||
});
|
||||
});
|
||||
|
||||
it('sets LOADED state when previously failed source tiles are loaded', function(done) {
|
||||
it('sets sourceTilesLoaded when previously failed source tiles are loaded', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/unavailable.json';
|
||||
let sourceTile;
|
||||
@@ -41,13 +41,17 @@ describe('ol.VectorImageTile', function() {
|
||||
}, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
let calls = 0;
|
||||
listen(tile, 'change', function(e) {
|
||||
++calls;
|
||||
expect(tile.getState()).to.be(calls == 2 ? TileState.LOADED : TileState.ERROR);
|
||||
if (calls === 1) {
|
||||
expect(tile.sourceTilesLoaded).to.be(false);
|
||||
} else if (calls === 2) {
|
||||
expect(tile.sourceTilesLoaded).to.be(true);
|
||||
}
|
||||
if (calls == 2) {
|
||||
done();
|
||||
} else {
|
||||
@@ -65,7 +69,7 @@ describe('ol.VectorImageTile', function() {
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
|
||||
@@ -81,7 +85,7 @@ describe('ol.VectorImageTile', function() {
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {},
|
||||
createXYZ(), createXYZ(), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
|
||||
@@ -105,7 +109,7 @@ describe('ol.VectorImageTile', function() {
|
||||
return url;
|
||||
}, tileGrid,
|
||||
createXYZ({extent: [-180, -90, 180, 90], tileSize: 512}),
|
||||
sourceTiles, 1, getProjection('EPSG:4326'), VectorTile, function() {}, 1);
|
||||
sourceTiles, 1, getProjection('EPSG:4326'), VectorTile, function() {});
|
||||
tile.load();
|
||||
expect(tile.tileKeys.length).to.be(1);
|
||||
expect(tile.getTile(tile.tileKeys[0]).tileCoord).to.eql([0, 16, 9]);
|
||||
@@ -118,7 +122,7 @@ describe('ol.VectorImageTile', function() {
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ({tileSize: 512}), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
expect(tile.loadListenerKeys_.length).to.be(4);
|
||||
@@ -131,18 +135,19 @@ describe('ol.VectorImageTile', function() {
|
||||
expect(tile.getState()).to.be(TileState.ABORT);
|
||||
});
|
||||
|
||||
it('#dispose() when loaded', function(done) {
|
||||
it('#dispose() when source tiles are loaded', function(done) {
|
||||
const format = new GeoJSON();
|
||||
const url = 'spec/ol/data/point.json';
|
||||
const tile = new VectorImageTile([0, 0, 0], 0, url, format,
|
||||
defaultLoadFunction, [0, 0, 0], function() {
|
||||
return url;
|
||||
}, createXYZ(), createXYZ({tileSize: 512}), {},
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {}, 0);
|
||||
1, getProjection('EPSG:3857'), VectorTile, function() {});
|
||||
|
||||
tile.load();
|
||||
listenOnce(tile, 'change', function() {
|
||||
expect(tile.getState()).to.be(TileState.LOADED);
|
||||
expect(tile.getState()).to.be(TileState.LOADING);
|
||||
expect(tile.sourceTilesLoaded).to.be.ok();
|
||||
expect(tile.loadListenerKeys_.length).to.be(0);
|
||||
expect(tile.tileKeys.length).to.be(4);
|
||||
tile.dispose();
|
||||
|
||||
Reference in New Issue
Block a user