Merge pull request #9008 from ahocevar/decouple-group-creation

Decouple render instruction creation from rendering
This commit is contained in:
Andreas Hocevar
2018-12-07 14:33:54 +01:00
committed by GitHub
9 changed files with 251 additions and 170 deletions

View File

@@ -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);
}

View File

@@ -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;
}
/**

View File

@@ -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.

View File

@@ -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);
}
/**

View File

@@ -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;

View File

@@ -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);
}

View File

@@ -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;

View File

@@ -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);

View File

@@ -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();