Conditionally render tiles to a separate tile canvas
Because clip geometries are anti-aliased in most browsers, there will be tiny gaps between tiles. If tiles are rendered to a tile canvas which is then drawn to the map canvas upon composition, these gaps can be avoided. For rotated views, it is stil necessary to clip the tile, but in this case a 1-pixel buffer is used. This change also brings a huge performance improvement for panning, because the fully rendered tiles can be reused. Because of the added cost of using drawImage in addition to replaying the tile replay group, we fall back to directly drawing to the map canvas when the tile canvas would be too large, or during interaction/animation when resolution or rotation change.
This commit is contained in:
@@ -1995,32 +1995,35 @@ ol.render.canvas.ReplayGroup.prototype.isEmpty = function() {
|
|||||||
* @param {number} viewRotation View rotation.
|
* @param {number} viewRotation View rotation.
|
||||||
* @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
|
* @param {Object.<string, boolean>} skippedFeaturesHash Ids of features
|
||||||
* to skip.
|
* to skip.
|
||||||
|
* @param {boolean=} opt_clip Clip at `maxExtent`. Default is true.
|
||||||
*/
|
*/
|
||||||
ol.render.canvas.ReplayGroup.prototype.replay = function(
|
ol.render.canvas.ReplayGroup.prototype.replay = function(context, pixelRatio,
|
||||||
context, pixelRatio, transform, viewRotation, skippedFeaturesHash) {
|
transform, viewRotation, skippedFeaturesHash, opt_clip) {
|
||||||
|
|
||||||
/** @type {Array.<number>} */
|
/** @type {Array.<number>} */
|
||||||
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
var zs = Object.keys(this.replaysByZIndex_).map(Number);
|
||||||
zs.sort(ol.array.numberSafeCompareFunction);
|
zs.sort(ol.array.numberSafeCompareFunction);
|
||||||
|
|
||||||
// setup clipping so that the parts of over-simplified geometries are not
|
if (opt_clip !== false) {
|
||||||
// visible outside the current extent when panning
|
// setup clipping so that the parts of over-simplified geometries are not
|
||||||
var maxExtent = this.maxExtent_;
|
// visible outside the current extent when panning
|
||||||
var minX = maxExtent[0];
|
var maxExtent = this.maxExtent_;
|
||||||
var minY = maxExtent[1];
|
var minX = maxExtent[0];
|
||||||
var maxX = maxExtent[2];
|
var minY = maxExtent[1];
|
||||||
var maxY = maxExtent[3];
|
var maxX = maxExtent[2];
|
||||||
var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
|
var maxY = maxExtent[3];
|
||||||
ol.geom.flat.transform.transform2D(
|
var flatClipCoords = [minX, minY, minX, maxY, maxX, maxY, maxX, minY];
|
||||||
flatClipCoords, 0, 8, 2, transform, flatClipCoords);
|
ol.geom.flat.transform.transform2D(
|
||||||
context.save();
|
flatClipCoords, 0, 8, 2, transform, flatClipCoords);
|
||||||
context.beginPath();
|
context.save();
|
||||||
context.moveTo(flatClipCoords[0], flatClipCoords[1]);
|
context.beginPath();
|
||||||
context.lineTo(flatClipCoords[2], flatClipCoords[3]);
|
context.moveTo(flatClipCoords[0], flatClipCoords[1]);
|
||||||
context.lineTo(flatClipCoords[4], flatClipCoords[5]);
|
context.lineTo(flatClipCoords[2], flatClipCoords[3]);
|
||||||
context.lineTo(flatClipCoords[6], flatClipCoords[7]);
|
context.lineTo(flatClipCoords[4], flatClipCoords[5]);
|
||||||
context.closePath();
|
context.lineTo(flatClipCoords[6], flatClipCoords[7]);
|
||||||
context.clip();
|
context.closePath();
|
||||||
|
context.clip();
|
||||||
|
}
|
||||||
|
|
||||||
var i, ii, j, jj, replays, replay;
|
var i, ii, j, jj, replays, replay;
|
||||||
for (i = 0, ii = zs.length; i < ii; ++i) {
|
for (i = 0, ii = zs.length; i < ii; ++i) {
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ goog.require('ol.ViewHint');
|
|||||||
goog.require('ol.array');
|
goog.require('ol.array');
|
||||||
goog.require('ol.dom');
|
goog.require('ol.dom');
|
||||||
goog.require('ol.extent');
|
goog.require('ol.extent');
|
||||||
|
goog.require('ol.geom.flat.transform');
|
||||||
goog.require('ol.layer.VectorTile');
|
goog.require('ol.layer.VectorTile');
|
||||||
goog.require('ol.proj.Units');
|
goog.require('ol.proj.Units');
|
||||||
goog.require('ol.render.EventType');
|
goog.require('ol.render.EventType');
|
||||||
@@ -51,6 +52,18 @@ ol.renderer.canvas.VectorTileLayer = function(layer) {
|
|||||||
*/
|
*/
|
||||||
this.renderedTiles_ = [];
|
this.renderedTiles_ = [];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.resolution_ = NaN;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {number}
|
||||||
|
*/
|
||||||
|
this.rotation_ = NaN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.Extent}
|
* @type {ol.Extent}
|
||||||
@@ -110,44 +123,101 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
|
|||||||
// see http://jsperf.com/context-save-restore-versus-variable
|
// see http://jsperf.com/context-save-restore-versus-variable
|
||||||
var alpha = replayContext.globalAlpha;
|
var alpha = replayContext.globalAlpha;
|
||||||
replayContext.globalAlpha = layerState.opacity;
|
replayContext.globalAlpha = layerState.opacity;
|
||||||
|
var imageSmoothingEnabled = replayContext.imageSmoothingEnabled;
|
||||||
|
replayContext.imageSmoothingEnabled = false;
|
||||||
|
|
||||||
|
|
||||||
var tilesToDraw = this.renderedTiles_;
|
var tilesToDraw = this.renderedTiles_;
|
||||||
var tileGrid = source.getTileGrid();
|
var tileGrid = source.getTileGrid();
|
||||||
|
|
||||||
var currentZ, i, ii, origin, tile, tileSize;
|
var currentZ, height, i, ii, insertPoint, insertTransform, origin, pixelScale;
|
||||||
var tilePixelRatio, tilePixelResolution, tilePixelSize, tileResolution;
|
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) {
|
for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
|
||||||
tile = tilesToDraw[i];
|
tile = tilesToDraw[i];
|
||||||
|
replayState = tile.getReplayState();
|
||||||
|
tileExtent = tileGrid.getTileCoordExtent(
|
||||||
|
tile.getTileCoord(), this.tmpExtent_);
|
||||||
currentZ = tile.getTileCoord()[0];
|
currentZ = tile.getTileCoord()[0];
|
||||||
tileSize = tileGrid.getTileSize(currentZ);
|
tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ), this.tmpSize_);
|
||||||
tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection);
|
pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
|
||||||
tilePixelRatio = tilePixelSize[0] /
|
size = frameState.size;
|
||||||
ol.size.toSize(tileSize, this.tmpSize_)[0];
|
|
||||||
tileResolution = tileGrid.getResolution(currentZ);
|
tileResolution = tileGrid.getResolution(currentZ);
|
||||||
tilePixelResolution = tileResolution / tilePixelRatio;
|
tilePixelResolution = tileResolution / source.getTilePixelRatio();
|
||||||
if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
|
pixelScale = pixelRatio / resolution;
|
||||||
origin = ol.extent.getTopLeft(tileGrid.getTileCoordExtent(
|
scale = tileResolution / resolution;
|
||||||
tile.getTileCoord(), this.tmpExtent_));
|
offsetX = Math.round(pixelRatio * size[0] / 2);
|
||||||
transform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_,
|
offsetY = Math.round(pixelRatio * size[1] / 2);
|
||||||
pixelRatio * frameState.size[0] / 2,
|
width = tileSize[0] * pixelRatio * scale;
|
||||||
pixelRatio * frameState.size[1] / 2,
|
height = tileSize[1] * pixelRatio * scale;
|
||||||
pixelRatio * tilePixelResolution / resolution,
|
if (width < 1 || width > size[0]) {
|
||||||
pixelRatio * tilePixelResolution / resolution,
|
if (pixelSpace) {
|
||||||
viewState.rotation,
|
origin = ol.extent.getTopLeft(tileExtent);
|
||||||
(origin[0] - center[0]) / tilePixelResolution,
|
tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_,
|
||||||
(center[1] - origin[1]) / tilePixelResolution);
|
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) {
|
if (replayContext != context) {
|
||||||
this.dispatchRenderEvent(replayContext, frameState, transform);
|
this.dispatchRenderEvent(replayContext, frameState, transform);
|
||||||
context.drawImage(replayContext.canvas, 0, 0);
|
context.drawImage(replayContext.canvas, 0, 0);
|
||||||
}
|
}
|
||||||
replayContext.globalAlpha = alpha;
|
replayContext.globalAlpha = alpha;
|
||||||
|
replayContext.imageSmoothingEnabled = imageSmoothingEnabled;
|
||||||
|
|
||||||
this.dispatchPostComposeEvent(context, frameState, transform);
|
this.dispatchPostComposeEvent(context, frameState, transform);
|
||||||
};
|
};
|
||||||
@@ -179,16 +249,19 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
|
|||||||
'Source is an ol.source.VectorTile');
|
'Source is an ol.source.VectorTile');
|
||||||
var tileGrid = source.getTileGrid();
|
var tileGrid = source.getTileGrid();
|
||||||
var tileCoord = tile.getTileCoord();
|
var tileCoord = tile.getTileCoord();
|
||||||
|
var buffer = 1;
|
||||||
|
var resolution = tileGrid.getResolution(tileCoord[0]);
|
||||||
var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
|
var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
|
||||||
var extent;
|
var extent;
|
||||||
if (pixelSpace) {
|
if (pixelSpace) {
|
||||||
var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio,
|
var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio,
|
||||||
tile.getProjection());
|
tile.getProjection());
|
||||||
extent = [0, 0, tilePixelSize[0], tilePixelSize[1]];
|
extent = [-buffer, -buffer,
|
||||||
|
tilePixelSize[0] + buffer, tilePixelSize[1] + buffer];
|
||||||
} else {
|
} 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;
|
var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution;
|
||||||
replayState.dirty = false;
|
replayState.dirty = false;
|
||||||
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
|
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.renderedRevision = revision;
|
||||||
replayState.renderedRenderOrder = renderOrder;
|
replayState.renderedRenderOrder = renderOrder;
|
||||||
replayState.replayGroup = replayGroup;
|
replayState.replayGroup = replayGroup;
|
||||||
|
replayState.resolution = NaN;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ goog.require('ol.Tile');
|
|||||||
goog.require('ol.TileCoord');
|
goog.require('ol.TileCoord');
|
||||||
goog.require('ol.TileLoadFunctionType');
|
goog.require('ol.TileLoadFunctionType');
|
||||||
goog.require('ol.TileState');
|
goog.require('ol.TileState');
|
||||||
|
goog.require('ol.dom');
|
||||||
goog.require('ol.proj.Projection');
|
goog.require('ol.proj.Projection');
|
||||||
|
|
||||||
|
|
||||||
@@ -33,6 +34,12 @@ ol.VectorTile =
|
|||||||
|
|
||||||
goog.base(this, tileCoord, state);
|
goog.base(this, tileCoord, state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
* @type {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
this.context_ = ol.dom.createCanvasContext2D();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
* @type {ol.format.Feature}
|
* @type {ol.format.Feature}
|
||||||
@@ -84,6 +91,14 @@ ol.VectorTile =
|
|||||||
goog.inherits(ol.VectorTile, ol.Tile);
|
goog.inherits(ol.VectorTile, ol.Tile);
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return {CanvasRenderingContext2D}
|
||||||
|
*/
|
||||||
|
ol.VectorTile.prototype.getContext = function() {
|
||||||
|
return this.context_;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
|
|||||||
Reference in New Issue
Block a user