diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 6c6668ca75..3b676add9b 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -139,6 +139,12 @@ ol.render.canvas.Replay = function(tolerance, maxExtent, resolution) { * @type {!goog.vec.Mat4.Number} */ this.tmpLocalTransform_ = goog.vec.Mat4.createNumber(); + + /** + * @private + * @type {!goog.vec.Mat4.Number} + */ + this.tmpLocalTransformInv_ = goog.vec.Mat4.createNumber(); }; goog.inherits(ol.render.canvas.Replay, ol.render.VectorContext); @@ -252,6 +258,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var d = 0; // data index var dd; // end of per-instruction data var localTransform = this.tmpLocalTransform_; + var localTransformInv = this.tmpLocalTransformInv_; var prevX, prevY, roundX, roundY; while (i < ii) { var instruction = instructions[i]; @@ -331,7 +338,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( ol.vec.Mat4.makeTransform2D( localTransform, centerX, centerY, scale, scale, rotation, -centerX, -centerY); - context.setTransform( + context.transform( goog.vec.Mat4.getElement(localTransform, 0, 0), goog.vec.Mat4.getElement(localTransform, 1, 0), goog.vec.Mat4.getElement(localTransform, 0, 1), @@ -351,7 +358,14 @@ ol.render.canvas.Replay.prototype.replay_ = function( context.globalAlpha = alpha; } if (scale != 1 || rotation !== 0) { - context.setTransform(1, 0, 0, 1, 0, 0); + goog.vec.Mat4.invert(localTransform, localTransformInv); + context.transform( + goog.vec.Mat4.getElement(localTransformInv, 0, 0), + goog.vec.Mat4.getElement(localTransformInv, 1, 0), + goog.vec.Mat4.getElement(localTransformInv, 0, 1), + goog.vec.Mat4.getElement(localTransformInv, 1, 1), + goog.vec.Mat4.getElement(localTransformInv, 0, 3), + goog.vec.Mat4.getElement(localTransformInv, 1, 3)); } } ++i; @@ -390,7 +404,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( if (scale != 1 || rotation !== 0) { ol.vec.Mat4.makeTransform2D( localTransform, x, y, scale, scale, rotation, -x, -y); - context.setTransform( + context.transform( goog.vec.Mat4.getElement(localTransform, 0, 0), goog.vec.Mat4.getElement(localTransform, 1, 0), goog.vec.Mat4.getElement(localTransform, 0, 1), @@ -427,7 +441,14 @@ ol.render.canvas.Replay.prototype.replay_ = function( } if (scale != 1 || rotation !== 0) { - context.setTransform(1, 0, 0, 1, 0, 0); + goog.vec.Mat4.invert(localTransform, localTransformInv); + context.transform( + goog.vec.Mat4.getElement(localTransformInv, 0, 0), + goog.vec.Mat4.getElement(localTransformInv, 1, 0), + goog.vec.Mat4.getElement(localTransformInv, 0, 1), + goog.vec.Mat4.getElement(localTransformInv, 1, 1), + goog.vec.Mat4.getElement(localTransformInv, 0, 3), + goog.vec.Mat4.getElement(localTransformInv, 1, 3)); } } ++i; @@ -1995,32 +2016,35 @@ ol.render.canvas.ReplayGroup.prototype.isEmpty = function() { * @param {number} viewRotation View rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. + * @param {boolean=} opt_clip Clip at `maxExtent`. Default is true. */ -ol.render.canvas.ReplayGroup.prototype.replay = function( - context, pixelRatio, transform, viewRotation, skippedFeaturesHash) { +ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio, + transform, viewRotation, skippedFeaturesHash, opt_clip) { /** @type {Array.} */ var zs = Object.keys(this.replaysByZIndex_).map(Number); zs.sort(ol.array.numberSafeCompareFunction); - // setup clipping so that the parts of over-simplified geometries are not - // visible outside the current extent when panning - var maxExtent = this.maxExtent_; - var minX = maxExtent[0]; - var minY = maxExtent[1]; - var maxX = maxExtent[2]; - var maxY = maxExtent[3]; - var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; - ol.geom.flat.transform.transform2D( - flatClipCoords, 0, 8, 2, transform, flatClipCoords); - context.save(); - context.beginPath(); - context.moveTo(flatClipCoords[0], flatClipCoords[1]); - context.lineTo(flatClipCoords[2], flatClipCoords[3]); - context.lineTo(flatClipCoords[4], flatClipCoords[5]); - context.lineTo(flatClipCoords[6], flatClipCoords[7]); - context.closePath(); - context.clip(); + if (opt_clip !== false) { + // setup clipping so that the parts of over-simplified geometries are not + // visible outside the current extent when panning + var maxExtent = this.maxExtent_; + var minX = maxExtent[0]; + var minY = maxExtent[1]; + var maxX = maxExtent[2]; + var maxY = maxExtent[3]; + var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY]; + ol.geom.flat.transform.transform2D( + flatClipCoords, 0, 8, 2, transform, flatClipCoords); + context.save(); + context.beginPath(); + context.moveTo(flatClipCoords[0], flatClipCoords[1]); + context.lineTo(flatClipCoords[2], flatClipCoords[3]); + context.lineTo(flatClipCoords[4], flatClipCoords[5]); + context.lineTo(flatClipCoords[6], flatClipCoords[7]); + context.closePath(); + context.clip(); + } var i, ii, j, jj, replays, replay; for (i = 0, ii = zs.length; i < ii; ++i) { diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index b299254f80..3f53c28aa5 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -11,6 +11,7 @@ goog.require('ol.ViewHint'); goog.require('ol.array'); goog.require('ol.dom'); goog.require('ol.extent'); +goog.require('ol.geom.flat.transform'); goog.require('ol.layer.VectorTile'); goog.require('ol.proj.Units'); goog.require('ol.render.EventType'); @@ -87,10 +88,14 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = var projection = viewState.projection; var resolution = viewState.resolution; var rotation = viewState.rotation; + var size = frameState.size; + var pixelScale = pixelRatio / resolution; var layer = this.getLayer(); var source = layer.getSource(); goog.asserts.assertInstanceof(source, ol.source.VectorTile, 'Source is an ol.source.VectorTile'); + var tilePixelRatio = source.getTilePixelRatio(); + var maxScale = tilePixelRatio / pixelRatio; var transform = this.getTransform(frameState, 0); @@ -114,35 +119,81 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = var tilesToDraw = this.renderedTiles_; var tileGrid = source.getTileGrid(); - var currentZ, i, ii, origin, tile, tileSize; - var tilePixelRatio, tilePixelResolution, tilePixelSize, tileResolution; + var currentZ, height, i, ii, insertPoint, insertTransform, offsetX, offsetY; + var origin, pixelSpace, replayState, scale, tile, tileCenter, tileContext; + var tileExtent, tilePixelResolution, tilePixelSize, tileResolution, tileSize; + var tileTransform, width; for (i = 0, ii = tilesToDraw.length; i < ii; ++i) { tile = tilesToDraw[i]; + replayState = tile.getReplayState(); + tileExtent = tileGrid.getTileCoordExtent( + tile.getTileCoord(), this.tmpExtent_); currentZ = tile.getTileCoord()[0]; - tileSize = tileGrid.getTileSize(currentZ); - tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); - tilePixelRatio = tilePixelSize[0] / - ol.size.toSize(tileSize, this.tmpSize_)[0]; + tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ), this.tmpSize_); + pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; tileResolution = tileGrid.getResolution(currentZ); tilePixelResolution = tileResolution / tilePixelRatio; - if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { - origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent( - tile.getTileCoord(), this.tmpExtent_)); - transform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, - pixelRatio * frameState.size[0] / 2, - pixelRatio * frameState.size[1] / 2, - pixelRatio * tilePixelResolution / resolution, - pixelRatio * tilePixelResolution / resolution, - viewState.rotation, - (origin[0] - center[0]) / tilePixelResolution, - (center[1] - origin[1]) / tilePixelResolution); + scale = tileResolution / resolution; + offsetX = Math.round(pixelRatio * size[0] / 2); + offsetY = Math.round(pixelRatio * size[1] / 2); + width = tileSize[0] * pixelRatio * scale; + height = tileSize[1] * pixelRatio * scale; + if (width < 1 || scale > maxScale) { + if (pixelSpace) { + origin = ol.extent.getTopLeft(tileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + offsetX, offsetY, + pixelScale * tilePixelResolution, + pixelScale * tilePixelResolution, + rotation, + (origin[0] - center[0]) / tilePixelResolution, + (center[1] - origin[1]) / tilePixelResolution); + } else { + tileTransform = transform; + } + replayState.replayGroup.replay(replayContext, pixelRatio, + tileTransform, rotation, skippedFeatureUids); + } else { + tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); + if (pixelSpace) { + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + 0, 0, + pixelScale * tilePixelResolution, pixelScale * tilePixelResolution, + rotation, + -tilePixelSize[0] / 2, -tilePixelSize[1] / 2); + } else { + tileCenter = ol.extent.getCenter(tileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + 0, 0, + pixelScale, -pixelScale, + -rotation, + -tileCenter[0], -tileCenter[1]); + } + tileContext = tile.getContext(); + if (replayState.resolution !== resolution || + replayState.rotation !== rotation) { + replayState.resolution = resolution; + replayState.rotation = rotation; + tileContext.canvas.width = width + 0.5; + tileContext.canvas.height = height + 0.5; + tileContext.translate(width / 2, height / 2); + tileContext.rotate(-rotation); + replayState.replayGroup.replay(tileContext, pixelRatio, + tileTransform, rotation, skippedFeatureUids, false); + } + insertTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + 0, 0, pixelScale, -pixelScale, 0, -center[0], -center[1]); + insertPoint = ol.geom.flat.transform.transform2D( + ol.extent.getTopLeft(tileExtent), 0, 1, 2, insertTransform); + replayContext.translate(offsetX, offsetY); + replayContext.rotate(rotation); + replayContext.drawImage(tileContext.canvas, + Math.round(insertPoint[0]), Math.round(insertPoint[1])); + replayContext.rotate(-rotation); + replayContext.translate(-offsetX, -offsetY); } - tile.getReplayState().replayGroup.replay(replayContext, pixelRatio, - transform, rotation, skippedFeatureUids); } - transform = this.getTransform(frameState, 0); - if (replayContext != context) { this.dispatchRenderEvent(replayContext, frameState, transform); context.drawImage(replayContext.canvas, 0, 0); @@ -234,6 +285,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, replayState.renderedRevision = revision; replayState.renderedRenderOrder = renderOrder; replayState.replayGroup = replayGroup; + replayState.resolution = NaN; }; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index 5a5e1efe2c..55e22eb4cb 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -4,6 +4,7 @@ goog.require('ol.Tile'); goog.require('ol.TileCoord'); goog.require('ol.TileLoadFunctionType'); goog.require('ol.TileState'); +goog.require('ol.dom'); goog.require('ol.proj.Projection'); @@ -33,6 +34,12 @@ ol.VectorTile = goog.base(this, tileCoord, state); + /** + * @private + * @type {CanvasRenderingContext2D} + */ + this.context_ = ol.dom.createCanvasContext2D(); + /** * @private * @type {ol.format.Feature} @@ -84,6 +91,14 @@ ol.VectorTile = goog.inherits(ol.VectorTile, ol.Tile); +/** + * @return {CanvasRenderingContext2D} + */ +ol.VectorTile.prototype.getContext = function() { + return this.context_; +}; + + /** * @inheritDoc */