diff --git a/src/ol/extent.js b/src/ol/extent.js index 5595382e7c..e20c5bfb17 100644 --- a/src/ol/extent.js +++ b/src/ol/extent.js @@ -45,6 +45,35 @@ ol.Extent.boundingExtent = function(var_args) { }; +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {number} rotation Rotation. + * @param {ol.Size} size Size. + * @return {ol.Extent} Extent. + */ +ol.Extent.getForView2DAndSize = function(center, resolution, rotation, size) { + var dx = resolution * size.width / 2; + var dy = resolution * size.height / 2; + var cosRotation = Math.cos(rotation); + var sinRotation = Math.sin(rotation); + var xs = [-dx, -dx, dx, dx]; + var ys = [-dy, dy, -dy, dy]; + var i, x, y; + for (i = 0; i < 4; ++i) { + x = xs[i]; + y = ys[i]; + xs[i] = center.x + x * cosRotation - y * sinRotation; + ys[i] = center.y + x * sinRotation + y * cosRotation; + } + var minX = Math.min.apply(null, xs); + var minY = Math.min.apply(null, ys); + var maxX = Math.max.apply(null, xs); + var maxY = Math.max.apply(null, ys); + return new ol.Extent(minX, minY, maxX, maxY); +}; + + /** * Checks if the passed coordinate is contained or on the edge * of the extent. diff --git a/src/ol/map.js b/src/ol/map.js index 7e8e04dfcb..3d13c77fd1 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -728,24 +728,8 @@ ol.Map.prototype.renderFrame_ = function(time) { if (!goog.isNull(frameState)) { // FIXME works for View2D only - var center = view2DState.center; - var resolution = view2DState.resolution; - var rotation = view2DState.rotation; - var x = resolution * size.width / 2; - var y = resolution * size.height / 2; - var corners = [ - new ol.Coordinate(-x, -y), - new ol.Coordinate(-x, y), - new ol.Coordinate(x, -y), - new ol.Coordinate(x, y) - ]; - var corner; - for (i = 0; i < 4; ++i) { - corner = corners[i]; - corner.rotate(rotation); - corner.add(center); - } - frameState.extent = ol.Extent.boundingExtent.apply(null, corners); + frameState.extent = ol.Extent.getForView2DAndSize(view2DState.center, + view2DState.resolution, view2DState.rotation, frameState.size); } this.frameState_ = frameState; diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 6c083c9c50..ac03dd7652 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -6,6 +6,7 @@ goog.provide('ol.renderer.canvas.TileLayer'); goog.require('goog.array'); goog.require('goog.dom'); goog.require('goog.vec.Mat4'); +goog.require('ol.Extent'); goog.require('ol.Size'); goog.require('ol.Tile'); goog.require('ol.TileCoord'); @@ -103,8 +104,17 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = var z = tileGrid.getZForResolution(view2DState.resolution); var tileSize = tileGrid.getTileSize(z); var tileResolution = tileGrid.getResolution(z); + var center = view2DState.center; + var extent; + if (tileResolution == view2DState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.Extent.getForView2DAndSize( + center, tileResolution, view2DState.rotation, frameState.size); + } else { + extent = frameState.extent; + } var tileRange = tileGrid.getTileRangeForExtentAndResolution( - frameState.extent, tileResolution); + extent, tileResolution); var tileRangeWidth = tileRange.getWidth(); var tileRangeHeight = tileRange.getHeight(); @@ -238,7 +248,7 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - tileSource.useLowResolutionTiles(z, frameState.extent, tileGrid); + tileSource.useLowResolutionTiles(z, extent, tileGrid); this.scheduleExpireCache(frameState, tileSource); var transform = this.transform_; @@ -253,8 +263,8 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame = 1); goog.vec.Mat4.translate( transform, - (origin.x - view2DState.center.x) / tileResolution, - (view2DState.center.y - origin.y) / tileResolution, + (origin.x - center.x) / tileResolution, + (center.y - origin.y) / tileResolution, 0); }; diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index fd1e221106..a3d99ff47d 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -90,8 +90,17 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = } var z = tileGrid.getZForResolution(view2DState.resolution); var tileResolution = tileGrid.getResolution(z); + var center = view2DState.center; + var extent; + if (tileResolution == view2DState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.Extent.getForView2DAndSize( + center, tileResolution, view2DState.rotation, frameState.size); + } else { + extent = frameState.extent; + } var tileRange = tileGrid.getTileRangeForExtentAndResolution( - frameState.extent, tileResolution); + extent, tileResolution); /** @type {Object.>} */ var tilesToDrawByZ = {}; @@ -146,7 +155,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = tileLayerZ = this.tileLayerZs_[tileLayerZKey]; } else { tileCoordOrigin = - tileGrid.getTileCoordForCoordAndZ(view2DState.center, tileLayerZKey); + tileGrid.getTileCoordForCoordAndZ(center, tileLayerZKey); tileLayerZ = new ol.renderer.dom.TileLayerZ_(tileGrid, tileCoordOrigin); newTileLayerZKeys[tileLayerZKey] = true; this.tileLayerZs_[tileLayerZKey] = tileLayerZ; @@ -183,8 +192,8 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = resolution / view2DState.resolution, 1); goog.vec.Mat4.translate( transform, - (origin.x - view2DState.center.x) / resolution, - (view2DState.center.y - origin.y) / resolution, + (origin.x - center.x) / resolution, + (center.y - origin.y) / resolution, 0); tileLayerZ.setTransform(transform); if (tileLayerZKey in newTileLayerZKeys) { @@ -201,7 +210,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = } else { if (!frameState.viewHints[ol.ViewHint.ANIMATING] && !frameState.viewHints[ol.ViewHint.INTERACTING]) { - tileLayerZ.removeTilesOutsideExtent(frameState.extent); + tileLayerZ.removeTilesOutsideExtent(extent); } } } @@ -217,7 +226,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - tileSource.useLowResolutionTiles(z, frameState.extent, tileGrid); + tileSource.useLowResolutionTiles(z, extent, tileGrid); this.scheduleExpireCache(frameState, tileSource); }; diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index f48ec2e312..f00a47f365 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -3,6 +3,7 @@ goog.provide('ol.renderer.Layer'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('ol.Attribution'); +goog.require('ol.Coordinate'); goog.require('ol.FrameState'); goog.require('ol.Image'); goog.require('ol.ImageState'); @@ -299,3 +300,18 @@ ol.renderer.Layer.prototype.createGetTileIfLoadedFunction = return isLoadedFunction(tile) ? tile : null; }; }; + + +/** + * @param {ol.Coordinate} center Center. + * @param {number} resolution Resolution. + * @param {ol.Size} size Size. + * @return {ol.Coordinate} Snapped center. + * @protected + */ +ol.renderer.Layer.prototype.snapCenterToPixel = + function(center, resolution, size) { + return new ol.Coordinate( + resolution * (Math.round(center.x / resolution) + (size.width % 2) / 2), + resolution * (Math.round(center.y / resolution) + (size.height % 2) / 2)); +}; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index 2c6fcae043..ff586257fc 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -282,7 +282,6 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = var view2DState = frameState.view2DState; var projection = view2DState.projection; - var center = view2DState.center; var tileLayer = this.getTileLayer(); var tileSource = tileLayer.getTileSource(); @@ -293,8 +292,17 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = } var z = tileGrid.getZForResolution(view2DState.resolution); var tileResolution = tileGrid.getResolution(z); + var center = view2DState.center; + var extent; + if (tileResolution == view2DState.resolution) { + center = this.snapCenterToPixel(center, tileResolution, frameState.size); + extent = ol.Extent.getForView2DAndSize( + center, tileResolution, view2DState.rotation, frameState.size); + } else { + extent = frameState.extent; + } var tileRange = tileGrid.getTileRangeForExtentAndResolution( - frameState.extent, tileResolution); + extent, tileResolution); var framebufferExtent; if (!goog.isNull(this.renderedTileRange_) && @@ -460,14 +468,14 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = } this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange); - tileSource.useLowResolutionTiles(z, frameState.extent, tileGrid); + tileSource.useLowResolutionTiles(z, extent, tileGrid); this.scheduleExpireCache(frameState, tileSource); goog.vec.Mat4.makeIdentity(this.texCoordMatrix_); goog.vec.Mat4.translate(this.texCoordMatrix_, - (view2DState.center.x - framebufferExtent.minX) / + (center.x - framebufferExtent.minX) / (framebufferExtent.maxX - framebufferExtent.minX), - (view2DState.center.y - framebufferExtent.minY) / + (center.y - framebufferExtent.minY) / (framebufferExtent.maxY - framebufferExtent.minY), 0); goog.vec.Mat4.rotateZ(this.texCoordMatrix_, view2DState.rotation); diff --git a/test/spec/ol/extent.test.js b/test/spec/ol/extent.test.js index a2c735e6d7..da5a76c6f4 100644 --- a/test/spec/ol/extent.test.js +++ b/test/spec/ol/extent.test.js @@ -97,8 +97,59 @@ describe('ol.Extent', function() { }); }); + + describe('getForView2DAndSize', function() { + + it('works for a unit square', function() { + var extent = ol.Extent.getForView2DAndSize( + new ol.Coordinate(0, 0), 1, 0, new ol.Size(1, 1)); + expect(extent.minX).toBe(-0.5); + expect(extent.minY).toBe(-0.5); + expect(extent.maxX).toBe(0.5); + expect(extent.maxY).toBe(0.5); + }); + + it('works for center', function() { + var extent = ol.Extent.getForView2DAndSize( + new ol.Coordinate(5, 10), 1, 0, new ol.Size(1, 1)); + expect(extent.minX).toBe(4.5); + expect(extent.minY).toBe(9.5); + expect(extent.maxX).toBe(5.5); + expect(extent.maxY).toBe(10.5); + }); + + it('works for rotation', function() { + var extent = ol.Extent.getForView2DAndSize( + new ol.Coordinate(0, 0), 1, Math.PI / 4, new ol.Size(1, 1)); + expect(extent.minX).toRoughlyEqual(-Math.sqrt(0.5), 1e-9); + expect(extent.minY).toRoughlyEqual(-Math.sqrt(0.5), 1e-9); + expect(extent.maxX).toRoughlyEqual(Math.sqrt(0.5), 1e-9); + expect(extent.maxY).toRoughlyEqual(Math.sqrt(0.5), 1e-9); + }); + + it('works for resolution', function() { + var extent = ol.Extent.getForView2DAndSize( + new ol.Coordinate(0, 0), 2, 0, new ol.Size(1, 1)); + expect(extent.minX).toBe(-1); + expect(extent.minY).toBe(-1); + expect(extent.maxX).toBe(1); + expect(extent.maxY).toBe(1); + }); + + it('works for size', function() { + var extent = ol.Extent.getForView2DAndSize( + new ol.Coordinate(0, 0), 1, 0, new ol.Size(10, 5)); + expect(extent.minX).toBe(-5); + expect(extent.minY).toBe(-2.5); + expect(extent.maxX).toBe(5); + expect(extent.maxY).toBe(2.5); + }); + + }); + }); goog.require('ol.Coordinate'); goog.require('ol.Extent'); +goog.require('ol.Size'); goog.require('ol.projection');