diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 15d26e5db0..29eecd190b 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -2,6 +2,10 @@ ### Next release +#### `transition` option of `ol/source/VectorTile` is ignored + +The `transition` option to get an opacity transition to fade in tiles has been disabled for `ol/source/VectorTile`. Vector tiles are now always rendered without an opacity transition. + #### `ol/style/Fill` with `CanvasGradient` or `CanvasPattern` The origin for gradients and patterns has changed from the top-left corner of the extent of the geometry being filled to 512 css pixel increments from map coordinate `[0, 0]`. This allows repeat patterns to be aligned properly with vector tiles. For seamless repeat patterns, width and height of the pattern image must be a factor of two (2, 4, 8, ..., 512). diff --git a/src/ol/VectorImageTile.js b/src/ol/VectorImageTile.js index ed70e8f9f1..2884f4d5e6 100644 --- a/src/ol/VectorImageTile.js +++ b/src/ol/VectorImageTile.js @@ -9,6 +9,7 @@ import {listen, unlistenByKey} from './events.js'; import {getHeight, getIntersection, getWidth} from './extent.js'; import EventType from './events/EventType.js'; import {loadFeaturesXhr} from './featureloader.js'; +import {UNDEFINED} from './functions.js'; /** @@ -40,13 +41,13 @@ import {loadFeaturesXhr} from './featureloader.js'; * instantiate for source tiles. * @param {function(this: module:ol/source/VectorTile, module:ol/events/Event)} handleTileChange * Function to call when a source tile's state changes. - * @param {module:ol/Tile~Options=} opt_options Tile options. + * @param {number} zoom Integer zoom to render the tile for. */ const VectorImageTile = function(tileCoord, state, sourceRevision, format, tileLoadFunction, urlTileCoord, tileUrlFunction, sourceTileGrid, tileGrid, - sourceTiles, pixelRatio, projection, tileClass, handleTileChange, opt_options) { + sourceTiles, pixelRatio, projection, tileClass, handleTileChange, zoom) { - Tile.call(this, tileCoord, state, opt_options); + Tile.call(this, tileCoord, state, {transition: 0}); /** * @private @@ -78,6 +79,11 @@ const VectorImageTile = function(tileCoord, state, sourceRevision, format, */ this.tileKeys = []; + /** + * @type {module:ol/extent~Extent} + */ + this.extent = null; + /** * @type {number} */ @@ -99,9 +105,11 @@ const VectorImageTile = function(tileCoord, state, sourceRevision, format, this.sourceTileListenerKeys_ = []; if (urlTileCoord) { - const extent = tileGrid.getTileCoordExtent(urlTileCoord); - const resolution = tileGrid.getResolution(tileCoord[0]); + const extent = this.extent = tileGrid.getTileCoordExtent(urlTileCoord); + const resolution = tileGrid.getResolution(zoom); const sourceZ = sourceTileGrid.getZForResolution(resolution); + const useLoadedOnly = zoom != tileCoord[0]; + let loadCount = 0; sourceTileGrid.forEachTileCoord(extent, sourceZ, function(sourceTileCoord) { let sharedExtent = getIntersection(extent, sourceTileGrid.getTileCoordExtent(sourceTileCoord)); @@ -112,9 +120,10 @@ const VectorImageTile = function(tileCoord, state, sourceRevision, format, 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) { + if (!sourceTile && !useLoadedOnly) { const tileUrl = tileUrlFunction(sourceTileCoord, pixelRatio, projection); sourceTile = sourceTiles[sourceTileKey] = new tileClass(sourceTileCoord, tileUrl == undefined ? TileState.EMPTY : TileState.IDLE, @@ -123,10 +132,29 @@ const VectorImageTile = function(tileCoord, state, sourceRevision, format, this.sourceTileListenerKeys_.push( listen(sourceTile, EventType.CHANGE, handleTileChange)); } - sourceTile.consumers++; - this.tileKeys.push(sourceTileKey); + if (sourceTile && (!useLoadedOnly || sourceTile.getState() == TileState.LOADED)) { + sourceTile.consumers++; + this.tileKeys.push(sourceTileKey); + } } }.bind(this)); + + if (useLoadedOnly && loadCount == this.tileKeys.length) { + this.finishLoading_(); + } + + if (zoom <= tileCoord[0] && this.state != TileState.LOADED) { + while (zoom > tileGrid.getMinZoom()) { + const tile = new VectorImageTile(tileCoord, state, sourceRevision, + format, tileLoadFunction, urlTileCoord, tileUrlFunction, + sourceTileGrid, tileGrid, sourceTiles, pixelRatio, projection, + tileClass, UNDEFINED, --zoom); + if (tile.state == TileState.LOADED) { + this.interimTile = tile; + break; + } + } + } } }; diff --git a/src/ol/renderer/canvas/TileLayer.js b/src/ol/renderer/canvas/TileLayer.js index 6ff2ce5d39..9f1c5f8916 100644 --- a/src/ol/renderer/canvas/TileLayer.js +++ b/src/ol/renderer/canvas/TileLayer.js @@ -51,6 +51,12 @@ const CanvasTileLayerRenderer = function(tileLayer) { */ this.renderedTiles = []; + /** + * @private + * @type {boolean} + */ + this.newTiles_ = false; + /** * @protected * @type {module:ol/extent~Extent} @@ -114,6 +120,34 @@ CanvasTileLayerRenderer.prototype.isDrawableTile_ = function(tile) { tileState == TileState.ERROR && !useInterimTilesOnError; }; + +/** + * @param {number} z Tile coordinate z. + * @param {number} x Tile coordinate x. + * @param {number} y Tile coordinate y. + * @param {number} pixelRatio Pixel ratio. + * @param {module:ol/proj/Projection} projection Projection. + * @return {!module:ol/Tile} Tile. + */ +CanvasTileLayerRenderer.prototype.getTile = function(z, x, y, pixelRatio, projection) { + const layer = this.getLayer(); + const source = /** @type {module:ol/source/Tile} */ (layer.getSource()); + let tile = source.getTile(z, x, y, pixelRatio, projection); + if (tile.getState() == TileState.ERROR) { + if (!layer.getUseInterimTilesOnError()) { + // When useInterimTilesOnError is false, we consider the error tile as loaded. + tile.setState(TileState.LOADED); + } else if (layer.getPreload() > 0) { + // Preloaded tiles for lower resolutions might have finished loading. + this.newTiles_ = true; + } + } + if (!this.isDrawableTile_(tile)) { + tile = tile.getInterimTile(); + } + return tile; +}; + /** * @inheritDoc */ @@ -157,32 +191,26 @@ CanvasTileLayerRenderer.prototype.prepareFrame = function(frameState, layerState const findLoadedTiles = this.createLoadedTileFinder( tileSource, projection, tilesToDrawByZ); + const hints = frameState.viewHints; + const animatingOrInteracting = hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]; + const tmpExtent = this.tmpExtent; const tmpTileRange = this.tmpTileRange_; - let newTiles = false; + this.newTiles_ = false; let tile, x, y; for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - tile = tileSource.getTile(z, x, y, pixelRatio, projection); - if (tile.getState() == TileState.ERROR) { - if (!tileLayer.getUseInterimTilesOnError()) { - // When useInterimTilesOnError is false, we consider the error tile as loaded. - tile.setState(TileState.LOADED); - } else if (tileLayer.getPreload() > 0) { - // Preloaded tiles for lower resolutions might have finished loading. - newTiles = true; - } - } - if (!this.isDrawableTile_(tile)) { - tile = tile.getInterimTile(); + if (Date.now() - frameState.time > 16 && animatingOrInteracting) { + continue; } + tile = this.getTile(z, x, y, pixelRatio, projection); if (this.isDrawableTile_(tile)) { const uid = getUid(this); if (tile.getState() == TileState.LOADED) { tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; const inTransition = tile.inTransition(uid); - if (!newTiles && (inTransition || this.renderedTiles.indexOf(tile) === -1)) { - newTiles = true; + if (!this.newTiles_ && (inTransition || this.renderedTiles.indexOf(tile) === -1)) { + this.newTiles_ = true; } } if (tile.getAlpha(uid, frameState.time) === 1) { @@ -206,10 +234,8 @@ CanvasTileLayerRenderer.prototype.prepareFrame = function(frameState, layerState } const renderedResolution = tileResolution * pixelRatio / tilePixelRatio * oversampling; - const hints = frameState.viewHints; - const animatingOrInteracting = hints[ViewHint.ANIMATING] || hints[ViewHint.INTERACTING]; if (!(this.renderedResolution && Date.now() - frameState.time > 16 && animatingOrInteracting) && ( - newTiles || + this.newTiles_ || !(this.renderedExtent_ && containsExtent(this.renderedExtent_, extent)) || this.renderedRevision != sourceRevision || oversampling != this.oversampling_ || diff --git a/src/ol/renderer/canvas/VectorTileLayer.js b/src/ol/renderer/canvas/VectorTileLayer.js index e4a7035691..0ec433cdc9 100644 --- a/src/ol/renderer/canvas/VectorTileLayer.js +++ b/src/ol/renderer/canvas/VectorTileLayer.js @@ -126,6 +126,21 @@ CanvasVectorTileLayerRenderer.prototype.disposeInternal = function() { }; +/** + * @inheritDoc + */ +CanvasVectorTileLayerRenderer.prototype.getTile = function(z, x, y, pixelRatio, projection) { + const tile = CanvasTileLayerRenderer.prototype.getTile.call(this, z, x, y, pixelRatio, projection); + if (tile.getState() === TileState.LOADED) { + this.createReplayGroup_(tile, pixelRatio, projection); + if (this.context) { + this.renderTileImage_(tile, pixelRatio, projection); + } + } + return tile; +}; + + /** * @inheritDoc */ @@ -149,13 +164,12 @@ CanvasVectorTileLayerRenderer.prototype.prepareFrame = function(frameState, laye /** * @param {module:ol/VectorImageTile} tile Tile. - * @param {module:ol/PluggableMap~FrameState} frameState Frame state. + * @param {number} pixelRatio Pixel ratio. + * @param {module:ol/proj/Projection} projection Projection. * @private */ -CanvasVectorTileLayerRenderer.prototype.createReplayGroup_ = function(tile, frameState) { +CanvasVectorTileLayerRenderer.prototype.createReplayGroup_ = function(tile, pixelRatio, projection) { const layer = this.getLayer(); - const pixelRatio = frameState.pixelRatio; - const projection = frameState.viewState.projection; const revision = layer.getRevision(); const renderOrder = /** @type {module:ol/render~OrderFunction} */ (layer.getRenderOrder()) || null; @@ -169,7 +183,7 @@ CanvasVectorTileLayerRenderer.prototype.createReplayGroup_ = function(tile, fram const sourceTileGrid = source.getTileGrid(); const tileGrid = source.getTileGridForProjection(projection); const resolution = tileGrid.getResolution(tile.tileCoord[0]); - const tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord); + const tileExtent = tile.extent; const zIndexKeys = {}; for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) { @@ -241,20 +255,6 @@ CanvasVectorTileLayerRenderer.prototype.createReplayGroup_ = function(tile, fram }; -/** - * @inheritDoc - */ -CanvasVectorTileLayerRenderer.prototype.drawTileImage = function( - tile, frameState, layerState, x, y, w, h, gutter, transition) { - const vectorImageTile = /** @type {module:ol/VectorImageTile} */ (tile); - this.createReplayGroup_(vectorImageTile, frameState); - if (this.context) { - this.renderTileImage_(vectorImageTile, frameState, layerState); - CanvasTileLayerRenderer.prototype.drawTileImage.apply(this, arguments); - } -}; - - /** * @inheritDoc */ @@ -269,16 +269,11 @@ CanvasVectorTileLayerRenderer.prototype.forEachFeatureAtCoordinate = function(co /** @type {Array.} */ const renderedTiles = this.renderedTiles; - const source = /** @type {module:ol/source/VectorTile} */ (layer.getSource()); - const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); let bufferedExtent, found; let i, ii, replayGroup; - let tile, tileCoord, tileExtent; for (i = 0, ii = renderedTiles.length; i < ii; ++i) { - tile = renderedTiles[i]; - tileCoord = tile.wrappedTileCoord; - tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); - bufferedExtent = buffer(tileExtent, hitTolerance * resolution, bufferedExtent); + const tile = renderedTiles[i]; + bufferedExtent = buffer(tile.extent, hitTolerance * resolution, bufferedExtent); if (!containsCoordinate(bufferedExtent, coordinate)) { continue; } @@ -388,8 +383,7 @@ CanvasVectorTileLayerRenderer.prototype.postCompose = function(context, frameSta continue; } const tileCoord = tile.tileCoord; - const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - - tileGrid.getTileCoordExtent(tile.wrappedTileCoord, this.tmpExtent)[0]; + const worldOffset = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent)[0] - tile.extent[0]; let transform = undefined; for (let t = 0, tt = tile.tileKeys.length; t < tt; ++t) { const sourceTile = tile.getTile(tile.tileKeys[t]); @@ -472,12 +466,12 @@ CanvasVectorTileLayerRenderer.prototype.renderFeature = function(feature, square /** * @param {module:ol/VectorImageTile} tile Tile. - * @param {module:ol/PluggableMap~FrameState} frameState Frame state. - * @param {module:ol/layer/Layer~State} layerState Layer state. + * @param {number} pixelRatio Pixel ratio. + * @param {module:ol/proj/Projection} projection Projection. * @private */ CanvasVectorTileLayerRenderer.prototype.renderTileImage_ = function( - tile, frameState, layerState) { + tile, pixelRatio, projection) { const layer = this.getLayer(); const replayState = tile.getReplayState(layer); const revision = layer.getRevision(); @@ -486,12 +480,11 @@ CanvasVectorTileLayerRenderer.prototype.renderTileImage_ = function( replayState.renderedTileRevision = revision; const tileCoord = tile.wrappedTileCoord; const z = tileCoord[0]; - const pixelRatio = frameState.pixelRatio; const source = /** @type {module:ol/source/VectorTile} */ (layer.getSource()); - const tileGrid = source.getTileGridForProjection(frameState.viewState.projection); + const tileGrid = source.getTileGridForProjection(projection); const resolution = tileGrid.getResolution(z); const context = tile.getContext(layer); - const size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); + const size = source.getTilePixelSize(z, pixelRatio, projection); context.canvas.width = size[0]; context.canvas.height = size[1]; const tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent); @@ -509,4 +502,5 @@ CanvasVectorTileLayerRenderer.prototype.renderTileImage_ = function( } } }; + export default CanvasVectorTileLayerRenderer; diff --git a/src/ol/source/VectorTile.js b/src/ol/source/VectorTile.js index 8fbfea3c9b..20d83a4a88 100644 --- a/src/ol/source/VectorTile.js +++ b/src/ol/source/VectorTile.js @@ -47,8 +47,6 @@ import {createXYZ, extentFromProjection, createForProjection} from '../tilegrid. * When set to `false`, only one world * will be rendered. When set to `true`, tiles will be wrapped horizontally to * render multiple worlds. - * @property {number} [transition] Duration of the opacity transition for rendering. - * To disable the opacity transition, pass `transition: 0`. */ @@ -168,8 +166,7 @@ VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) { this.format_, this.tileLoadFunction, urlTileCoord, this.tileUrlFunction, this.tileGrid, this.getTileGridForProjection(projection), this.sourceTiles_, pixelRatio, projection, this.tileClass, - this.handleTileChange.bind(this), - this.tileOptions); + this.handleTileChange.bind(this), tileCoord[0]); this.tileCache.set(tileCoordKey, tile); return tile; diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index c5d76856bb..1c66cc7d61 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -251,7 +251,9 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { sourceTile.getImage = function() { return document.createElement('canvas'); }; - const tile = new VectorImageTile([0, 0, 0]); + const tile = new VectorImageTile([0, 0, 0], undefined, undefined, undefined, + undefined, undefined, undefined, undefined, undefined, undefined, undefined, + undefined, undefined, undefined, 0); tile.transition_ = 0; tile.wrappedTileCoord = [0, 0, 0]; tile.setState(TileState.LOADED); @@ -262,7 +264,6 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { return tile; }; const renderer = new CanvasVectorTileLayerRenderer(layer); - renderer.renderTileImage_ = sinon.spy(); const proj = getProjection('EPSG:3857'); const frameState = { extent: proj.getExtent(), @@ -279,12 +280,13 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { wantedTiles: {} }; renderer.prepareFrame(frameState, {}); - expect(renderer.renderTileImage_.getCalls().length).to.be(1); + const replayState = renderer.renderedTiles[0].getReplayState(layer); + const revision = replayState.renderedTileRevision; renderer.prepareFrame(frameState, {}); - expect(renderer.renderTileImage_.getCalls().length).to.be(1); + expect(replayState.renderedTileRevision).to.be(revision); layer.changed(); renderer.prepareFrame(frameState, {}); - expect(renderer.renderTileImage_.getCalls().length).to.be(2); + expect(replayState.renderedTileRevision).to.be(revision + 1); }); }); @@ -292,6 +294,7 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { let layer, renderer, replayGroup; const TileClass = function() { VectorImageTile.apply(this, arguments); + this.extent = [-Infinity, -Infinity, Infinity, Infinity]; this.setState(TileState.LOADED); const sourceTile = new VectorTile([0, 0, 0]); sourceTile.setState(TileState.LOADED); diff --git a/test/spec/ol/vectorimagetile.test.js b/test/spec/ol/vectorimagetile.test.js index 625f648ae3..d4d7b9376a 100644 --- a/test/spec/ol/vectorimagetile.test.js +++ b/test/spec/ol/vectorimagetile.test.js @@ -17,7 +17,7 @@ describe('ol.VectorImageTile', function() { defaultLoadFunction, [0, 0, -1], function() { return url; }, createXYZ(), createXYZ(), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); const sourceTile = tile.getTile(tile.tileKeys[0]); @@ -41,7 +41,7 @@ describe('ol.VectorImageTile', function() { }, [0, 0, -1], function() { return url; }, createXYZ(), createXYZ(), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); let calls = 0; @@ -65,7 +65,7 @@ describe('ol.VectorImageTile', function() { defaultLoadFunction, [0, 0, -1], function() { return url; }, createXYZ(), createXYZ(), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); @@ -81,7 +81,7 @@ describe('ol.VectorImageTile', function() { const tile = new VectorImageTile([0, 0, -1], 0, url, format, defaultLoadFunction, [0, 0, -1], function() {}, createXYZ(), createXYZ(), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); @@ -105,7 +105,7 @@ describe('ol.VectorImageTile', function() { return url; }, tileGrid, createXYZ({extent: [-180, -90, 180, 90], tileSize: 512}), - sourceTiles, 1, getProjection('EPSG:4326'), VectorTile, function() {}); + sourceTiles, 1, getProjection('EPSG:4326'), VectorTile, function() {}, 1); tile.load(); expect(tile.tileKeys.length).to.be(1); expect(tile.getTile(tile.tileKeys[0]).tileCoord).to.eql([0, 16, -10]); @@ -118,7 +118,7 @@ describe('ol.VectorImageTile', function() { defaultLoadFunction, [0, 0, -1], function() { return url; }, createXYZ(), createXYZ({tileSize: 512}), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); expect(tile.loadListenerKeys_.length).to.be(4); @@ -138,7 +138,7 @@ describe('ol.VectorImageTile', function() { defaultLoadFunction, [0, 0, -1], function() { return url; }, createXYZ(), createXYZ({tileSize: 512}), {}, - 1, getProjection('EPSG:3857'), VectorTile, function() {}); + 1, getProjection('EPSG:3857'), VectorTile, function() {}, 0); tile.load(); listenOnce(tile, 'change', function() {