diff --git a/src/ol/Tile.js b/src/ol/Tile.js index f7a9dc0e78..91c4ca3f69 100644 --- a/src/ol/Tile.js +++ b/src/ol/Tile.js @@ -146,9 +146,10 @@ class Tile extends EventTarget { * Get the interim tile most suitable for rendering using the chain of interim * tiles. This corresponds to the most recent tile that has been loaded, if no * such tile exists, the original tile is returned. + * @param {import("./layer/Layer").default} layer Layer. * @return {!Tile} Best tile for rendering. */ - getInterimTile() { + getInterimTile(layer) { if (!this.interimTile) { //empty chain return this; diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index ce1459f3bc..829d7734d3 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -72,6 +72,12 @@ class VectorImageTile extends Tile { */ this.sourceTiles_ = sourceTiles; + /** + * @private + * @type {boolean} + */ + this.sourceTilesLoaded = false; + /** * Keys of source tiles used by this tile. Use with {@link #getTile}. * @type {Array} @@ -107,13 +113,13 @@ class VectorImageTile extends Tile { * Use only source tiles that are loaded already * @type {boolean} */ - this.useLoadedOnly = zoom != tileCoord[0]; + this.isInterimTile = zoom != tileCoord[0]; if (urlTileCoord) { const extent = this.extent = tileGrid.getTileCoordExtent(urlTileCoord); const resolution = tileGrid.getResolution(zoom); const sourceZ = sourceTileGrid.getZForResolution(resolution); - const useLoadedOnly = this.useLoadedOnly; + const isInterimTile = this.isInterimTile; let loadCount = 0; sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) { let sharedExtent = getIntersection(extent, @@ -128,7 +134,7 @@ class VectorImageTile extends Tile { ++loadCount; const sourceTileKey = sourceTileCoord.toString(); let sourceTile = sourceTiles[sourceTileKey]; - if (!sourceTile && !useLoadedOnly) { + if (!sourceTile && !isInterimTile) { const tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection); sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord, tileUrl == undefined ? TileState.EMPTY : TileState.IDLE, @@ -137,19 +143,19 @@ class VectorImageTile extends Tile { this.sourceTileListenerKeys_.push( listen(sourceTile, EventType.CHANGE, handleTileChange)); } - if (sourceTile && (!useLoadedOnly || sourceTile.getState() == TileState.LOADED)) { + if (sourceTile && (!isInterimTile || sourceTile.getState() == TileState.LOADED)) { sourceTile.consumers++; this.tileKeys.push(sourceTileKey); } } }.bind(this)); - if (useLoadedOnly && loadCount == this.tileKeys.length) { + if (isInterimTile && loadCount == this.tileKeys.length) { this.finishLoading_(); } - this.createInterimTile_ = function() { - if (this.getState() !== TileState.LOADED && !useLoadedOnly) { + this.createInterimTile_ = function(layerId) { + if (this.getState() !== TileState.LOADED && !isInterimTile) { let bestZoom = -1; for (const key in sourceTiles) { const sourceTile = sourceTiles[key]; @@ -157,16 +163,20 @@ class VectorImageTile extends Tile { const sourceTileCoord = sourceTile.tileCoord; const sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); if (containsExtent(sourceTileExtent, extent) && sourceTileCoord[0] > bestZoom) { - bestZoom = sourceTileCoord[0]; + const lowResExecutorGroup = sourceTile.getLowResExecutorGroup(layerId, zoom, extent); + if (lowResExecutorGroup) { + // reuse existing replay if we're rendering an interim tile + sourceTile.setExecutorGroup(layerId, this.tileCoord.toString(), lowResExecutorGroup); + bestZoom = sourceTileCoord[0]; + } } } } if (bestZoom !== -1) { - const tile = new VectorImageTile(tileCoord, state, sourceRevision, + this.interimTile = new VectorImageTile(tileCoord, state, sourceRevision, format, tileLoadFunction, urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection, tileClass, VOID, bestZoom); - this.interimTile = tile; } } }; @@ -174,11 +184,11 @@ class VectorImageTile extends Tile { } - getInterimTile() { + getInterimTile(layer) { if (!this.interimTile) { - this.createInterimTile_(); + this.createInterimTile_(getUid(layer)); } - return super.getInterimTile(); + return super.getInterimTile(layer); } /** @@ -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); } diff --git a/src/ol/VectorTile.js b/src/ol/VectorTile.js index b6e1c5373e..3e64403891 100644 --- a/src/ol/VectorTile.js +++ b/src/ol/VectorTile.js @@ -2,7 +2,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,23 +139,22 @@ 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]; + getExecutorGroup(layerId, key) { + return this.executorGroups_[layerId + ',' + key]; } /** * Get the best matching lower resolution replay group for a given zoom and extent. - * @param {import("./layer/Layer").default} layer Layer. + * @param {string} layerId UID of the 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); + getLowResExecutorGroup(layerId, zoom, extent) { let bestZoom = 0; let replayGroup = null; for (const key in this.executorGroups_) { @@ -242,12 +240,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; } /** diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index ef9b0059a6..eca296b5c8 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -100,7 +100,7 @@ class CanvasTileLayerRenderer extends CanvasLayerRenderer { } } if (!this.isDrawableTile_(tile)) { - tile = tile.getInterimTile(); + tile = tile.getInterimTile(tileLayer); } return tile; } diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index e80e18abdb..029403edf3 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -5,7 +5,7 @@ import {getUid} from '../../util.js'; import {createCanvasContext2D} from '../../dom.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'; @@ -147,11 +147,20 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { * @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)); + if (tile.getState() === TileState.IDLE) { + const key = listen(tile, EventType.CHANGE, function() { + if (tile.sourceTilesLoaded) { + this.createExecutorGroup_(tile, pixelRatio, projection); + unlistenByKey(key); + tile.setState(TileState.LOADED); + } + }.bind(this)); + } if (tile.getState() === TileState.LOADED) { - this.createExecutorGroup_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + this.createExecutorGroup_(tile, pixelRatio, projection); if (this.context) { - this.renderTileImage_(/** @type {import("../../VectorImageTile.js").default} */ (tile), pixelRatio, projection); + this.renderTileImage_(tile, pixelRatio, projection); } } return tile; @@ -162,7 +171,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ 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); } /** @@ -186,6 +195,7 @@ class CanvasVectorTileLayerRenderer extends CanvasTileLayerRenderer { */ createExecutorGroup_(tile, pixelRatio, projection) { const layer = /** @type {import("../../layer/Vector.js").default} */ (this.getLayer()); + const layerId = getUid(layer); const revision = layer.getRevision(); const renderOrder = /** @type {import("../../render.js").OrderFunction} */ (layer.getRenderOrder()) || null; @@ -207,13 +217,11 @@ 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; - } + if (tile.isInterimTile) { + // reuse existing replay if we're rendering an interim tile + sourceTile.setExecutorGroup(layerId, tile.tileCoord.toString(), + sourceTile.getLowResExecutorGroup(layerId, zoom, tileExtent)); + continue; } const sourceTileCoord = sourceTile.tileCoord; @@ -271,7 +279,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 +311,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, {}, /** @@ -431,7 +439,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 @@ -536,7 +544,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); }