diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 6c6668ca75..c53ca842f8 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1995,32 +1995,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..fe7109d3cc 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'); @@ -51,6 +52,18 @@ ol.renderer.canvas.VectorTileLayer = function(layer) { */ this.renderedTiles_ = []; + /** + * @private + * @type {number} + */ + this.resolution_ = NaN; + + /** + * @private + * @type {number} + */ + this.rotation_ = NaN; + /** * @private * @type {ol.Extent} @@ -110,44 +123,101 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = // see http://jsperf.com/context-save-restore-versus-variable var alpha = replayContext.globalAlpha; replayContext.globalAlpha = layerState.opacity; + var imageSmoothingEnabled = replayContext.imageSmoothingEnabled; + replayContext.imageSmoothingEnabled = false; + 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, origin, pixelScale; + var pixelSpace, replayState, rotatedTileExtent, rotatedTileSize, size, tile; + var tileCenter, tileContext, tileExtent, tilePixelResolution, tilePixelSize; + var tileResolution, tileSize, 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; + size = frameState.size; 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); + tilePixelResolution = tileResolution / source.getTilePixelRatio(); + pixelScale = pixelRatio / resolution; + 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 || width > size[0]) { + if (pixelSpace) { + origin = ol.extent.getTopLeft(tileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + pixelRatio * size[0] / 2, pixelRatio * size[1] / 2, + 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 { + rotatedTileExtent = ol.extent.getForViewAndSize( + ol.extent.getCenter(tileExtent), tileResolution, rotation, tileSize); + rotatedTileSize = [ol.extent.getWidth(rotatedTileExtent), + ol.extent.getHeight(rotatedTileExtent)]; + tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); + if (pixelSpace) { + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + width / 2, height / 2, + pixelScale * tilePixelResolution, pixelScale * tilePixelResolution, + rotation, + -tilePixelSize[0] / 2, -tilePixelSize[1] / 2); + } else { + tileCenter = ol.extent.getCenter(rotatedTileExtent); + tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + width / 2, height / 2, + 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; + replayState.replayGroup.replay(tileContext, pixelRatio, + tileTransform, rotation, skippedFeatureUids, rotation !== 0); + } + insertTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_, + (pixelRatio * size[0] - width) / 2, + (pixelRatio * size[1] - height) / 2, + pixelScale, -pixelScale, + -rotation, + -center[0], -center[1]); + insertPoint = ol.geom.flat.transform.transform2D( + ol.extent.getCenter(rotatedTileExtent), 0, 1, 2, insertTransform); + replayContext.drawImage(tileContext.canvas, + insertPoint[0], insertPoint[1]); } - tile.getReplayState().replayGroup.replay(replayContext, pixelRatio, - transform, rotation, skippedFeatureUids); } - transform = this.getTransform(frameState, 0); + this.resolution_ = resolution; + this.rotation_ = rotation; if (replayContext != context) { this.dispatchRenderEvent(replayContext, frameState, transform); context.drawImage(replayContext.canvas, 0, 0); } replayContext.globalAlpha = alpha; + replayContext.imageSmoothingEnabled = imageSmoothingEnabled; this.dispatchPostComposeEvent(context, frameState, transform); }; @@ -179,16 +249,19 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, 'Source is an ol.source.VectorTile'); var tileGrid = source.getTileGrid(); var tileCoord = tile.getTileCoord(); + var buffer = 1; + var resolution = tileGrid.getResolution(tileCoord[0]); var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; var extent; if (pixelSpace) { var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio, tile.getProjection()); - extent = [0, 0, tilePixelSize[0], tilePixelSize[1]]; + extent = [-buffer, -buffer, + tilePixelSize[0] + buffer, tilePixelSize[1] + buffer]; } else { - extent = tileGrid.getTileCoordExtent(tileCoord); + extent = ol.extent.buffer(tileGrid.getTileCoordExtent(tileCoord), + buffer * resolution); } - var resolution = tileGrid.getResolution(tileCoord[0]); var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution; replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, @@ -234,6 +307,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 */