diff --git a/src/ol/PluggableMap.js b/src/ol/PluggableMap.js index 3bdf0d6593..136039e3f2 100644 --- a/src/ol/PluggableMap.js +++ b/src/ol/PluggableMap.js @@ -48,6 +48,7 @@ import {removeNode} from './dom.js'; * @property {import("./transform.js").Transform} coordinateToPixelTransform CoordinateToPixelTransform. * @property {import("rbush").default} declutterTree DeclutterTree. * @property {null|import("./extent.js").Extent} extent Extent. + * @property {import("./extent.js").Extent} [nextExtent] Next extent during an animation series. * @property {number} index Index. * @property {Array} layerStatesArray LayerStatesArray. * @property {number} layerIndex LayerIndex. @@ -1436,6 +1437,18 @@ class PluggableMap extends BaseObject { viewHints: viewHints, wantedTiles: {}, }; + if (viewState.nextCenter && viewState.nextResolution) { + const rotation = isNaN(viewState.nextRotation) + ? viewState.rotation + : viewState.nextRotation; + + frameState.nextExtent = getForViewAndSize( + viewState.nextCenter, + viewState.nextResolution, + rotation, + size + ); + } } this.frameState_ = frameState; diff --git a/src/ol/View.js b/src/ol/View.js index c2a494f460..12ed35e69e 100644 --- a/src/ol/View.js +++ b/src/ol/View.js @@ -208,6 +208,9 @@ import {fromExtent as polygonFromExtent} from './geom/Polygon.js'; * @property {import("./coordinate.js").Coordinate} center Center. * @property {import("./proj/Projection.js").default} projection Projection. * @property {number} resolution Resolution. + * @property {import("./coordinate.js").Coordinate} [nextCenter] The next center during an animation series. + * @property {number} [nextResolution] The next resolution during an animation series. + * @property {number} [nextRotation] The next rotation during an animation series. * @property {number} rotation Rotation. * @property {number} zoom Zoom. */ @@ -374,6 +377,24 @@ class View extends BaseObject { */ this.targetRotation_; + /** + * @private + * @type {import("./coordinate.js").Coordinate} + */ + this.nextCenter_ = null; + + /** + * @private + * @type {number} + */ + this.nextResolution_; + + /** + * @private + * @type {number} + */ + this.nextRotation_; + /** * @private * @type {import("./coordinate.js").Coordinate|undefined} @@ -714,6 +735,9 @@ class View extends BaseObject { } this.animations_.length = 0; this.cancelAnchor_ = anchor; + this.nextCenter_ = null; + this.nextResolution_ = NaN; + this.nextRotation_ = NaN; } /** @@ -752,6 +776,7 @@ class View extends BaseObject { const y0 = animation.sourceCenter[1]; const x1 = animation.targetCenter[0]; const y1 = animation.targetCenter[1]; + this.nextCenter_ = animation.targetCenter; const x = x0 + progress * (x1 - x0); const y = y0 + progress * (y1 - y0); this.targetCenter_ = [x, y]; @@ -776,6 +801,7 @@ class View extends BaseObject { animation.anchor ); } + this.nextResolution_ = animation.targetResolution; this.targetResolution_ = resolution; this.applyTargetState_(true); } @@ -800,6 +826,7 @@ class View extends BaseObject { animation.anchor ); } + this.nextRotation_ = animation.targetRotation; this.targetRotation_ = rotation; } this.applyTargetState_(true); @@ -811,6 +838,9 @@ class View extends BaseObject { if (seriesComplete) { this.animations_[i] = null; this.setHint(ViewHint.ANIMATING, -1); + this.nextCenter_ = null; + this.nextResolution_ = NaN; + this.nextRotation_ = NaN; const callback = series[0].callback; if (callback) { animationCallback(callback, true); @@ -1191,7 +1221,7 @@ class View extends BaseObject { */ getState() { const projection = this.getProjection(); - const resolution = /** @type {number} */ (this.getResolution()); + const resolution = this.getResolution(); const rotation = this.getRotation(); let center = /** @type {import("./coordinate.js").Coordinate} */ ( this.getCenterInternal() @@ -1211,6 +1241,9 @@ class View extends BaseObject { center: center.slice(0), projection: projection !== undefined ? projection : null, resolution: resolution, + nextCenter: this.nextCenter_, + nextResolution: this.nextResolution_, + nextRotation: this.nextRotation_, rotation: rotation, zoom: this.getZoom(), }; diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js index 81f8f77bb3..f17a8c2e6d 100644 --- a/src/ol/renderer/webgl/TileLayer.js +++ b/src/ol/renderer/webgl/TileLayer.js @@ -82,13 +82,12 @@ function addTileTextureToLookup(tileTexturesByZ, tileTexture, z) { } /** - * * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @return {import("../../extent.js").Extent} Extent. + * @param {import("../../extent.js").Extent} extent The frame extent. + * @return {import("../../extent.js").Extent} Frame extent intersected with layer extents. */ -function getRenderExtent(frameState) { +function getRenderExtent(frameState, extent) { const layerState = frameState.layerStatesArray[frameState.layerIndex]; - let extent = frameState.extent; if (layerState.extent) { extent = getIntersection( extent, @@ -213,7 +212,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { * @return {boolean} Layer is ready to be rendered. */ prepareFrame(frameState) { - if (isEmpty(getRenderExtent(frameState))) { + if (isEmpty(getRenderExtent(frameState, frameState.extent))) { return false; } const source = this.getLayer().getSource(); @@ -223,30 +222,11 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { return source.getState() === State.READY; } - /** - * Render the layer. - * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. - * @return {HTMLElement} The rendered element. - */ - renderFrame(frameState) { - this.preRender(frameState); - + enqueueTiles(frameState, extent, z, tileTexturesByZ) { const viewState = frameState.viewState; - const layerState = frameState.layerStatesArray[frameState.layerIndex]; - const extent = getRenderExtent(frameState); const tileLayer = this.getLayer(); const tileSource = tileLayer.getSource(); const tileGrid = tileSource.getTileGridForProjection(viewState.projection); - const z = tileGrid.getZForResolution( - viewState.resolution, - tileSource.zDirection - ); - - /** - * @type {Object>} - */ - const tileTexturesByZ = {}; - const tileTextureCache = this.tileTextureCache_; const tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); @@ -304,6 +284,42 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { } } } + } + + /** + * Render the layer. + * @param {import("../../PluggableMap.js").FrameState} frameState Frame state. + * @return {HTMLElement} The rendered element. + */ + renderFrame(frameState) { + this.preRender(frameState); + + const viewState = frameState.viewState; + const layerState = frameState.layerStatesArray[frameState.layerIndex]; + const extent = getRenderExtent(frameState, frameState.extent); + const tileLayer = this.getLayer(); + const tileSource = tileLayer.getSource(); + const tileGrid = tileSource.getTileGridForProjection(viewState.projection); + const z = tileGrid.getZForResolution( + viewState.resolution, + tileSource.zDirection + ); + + /** + * @type {Object>} + */ + const tileTexturesByZ = {}; + + if (frameState.nextExtent) { + const targetZ = tileGrid.getZForResolution( + viewState.nextResolution, + tileSource.zDirection + ); + const nextExtent = getRenderExtent(frameState, frameState.nextExtent); + this.enqueueTiles(frameState, nextExtent, targetZ, tileTexturesByZ); + } + + this.enqueueTiles(frameState, extent, z, tileTexturesByZ); /** * A lookup of alpha values for tiles at the target rendering resolution @@ -474,6 +490,7 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { this.renderedOpacity_ = opacity; } + const tileTextureCache = this.tileTextureCache_; while (tileTextureCache.canExpireCache()) { const tileTexture = tileTextureCache.pop(); tileTexture.dispose(); diff --git a/test/browser/spec/ol/view.test.js b/test/browser/spec/ol/View.test.js similarity index 99% rename from test/browser/spec/ol/view.test.js rename to test/browser/spec/ol/View.test.js index 3ed8942958..bd369b940c 100644 --- a/test/browser/spec/ol/view.test.js +++ b/test/browser/spec/ol/View.test.js @@ -12,7 +12,7 @@ import ViewHint from '../../../../src/ol/ViewHint.js'; import {clearUserProjection, useGeographic} from '../../../../src/ol/proj.js'; import {createEmpty} from '../../../../src/ol/extent.js'; -describe('ol.View', function () { +describe('ol/View', function () { describe('constructor (defaults)', function () { let view; @@ -688,13 +688,15 @@ describe('ol.View', function () { }, function (complete) { expect(complete).to.be(true); + expect(isNaN(view.nextResolution_)).to.be(true); expect(view.getCenter()).to.eql([0, 0]); expect(view.getZoom()).to.eql(4); - expect(view.getAnimating()).to.eql(false); + expect(view.getAnimating()).to.be(false); done(); } ); expect(view.getAnimating()).to.eql(true); + expect(isNaN(view.nextResolution_)).to.be(false); }); it('allows duration to be zero', function (done) {