Decouple source and rendered tile grid of vector tile sources

This commit is contained in:
Andreas Hocevar
2017-05-08 17:19:55 +02:00
parent 2486b25e5e
commit 785e7135a7
18 changed files with 620 additions and 287 deletions

View File

@@ -1,6 +1,7 @@
goog.provide('ol.renderer.canvas.VectorTileLayer');
goog.require('ol');
goog.require('ol.TileState');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.proj');
@@ -12,7 +13,6 @@ goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.render.replay');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.vector');
goog.require('ol.size');
goog.require('ol.transform');
@@ -23,6 +23,9 @@ goog.require('ol.transform');
*/
ol.renderer.canvas.VectorTileLayer = function(layer) {
/**
* @type {CanvasRenderingContext2D}
*/
this.context = null;
ol.renderer.canvas.TileLayer.call(this, layer);
@@ -95,12 +98,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState,
/**
* @param {ol.VectorTile} tile Tile.
* @param {ol.VectorImageTile} tile Tile.
* @param {olx.FrameState} frameState Frame state.
* @private
*/
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile,
frameState) {
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(
tile, frameState) {
var layer = this.getLayer();
var pixelRatio = frameState.pixelRatio;
var projection = frameState.viewState.projection;
@@ -113,78 +116,89 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile,
return;
}
replayState.replayGroup = null;
replayState.dirty = false;
var sourceTiles = tile.getSourceTiles();
for (var t = 0, tt = sourceTiles.length; t < tt; ++t) {
var sourceTile = sourceTiles[t];
sourceTile.replayGroup = null;
replayState.dirty = false;
var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
var tileGrid = source.getTileGrid();
var tileCoord = tile.tileCoord;
var tileProjection = tile.getProjection();
var resolution = tileGrid.getResolution(tileCoord[0]);
var extent, reproject, tileResolution;
if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
var tilePixelRatio = tileResolution = source.getTilePixelRatio();
var tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0]));
extent = [0, 0, tileSize[0] * tilePixelRatio, tileSize[1] * tilePixelRatio];
} else {
tileResolution = resolution;
extent = tileGrid.getTileCoordExtent(tileCoord);
if (!ol.proj.equivalent(projection, tileProjection)) {
reproject = true;
tile.setProjection(projection);
}
}
replayState.dirty = false;
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
tileResolution, source.getOverlaps(), layer.getRenderBuffer());
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
tileResolution, pixelRatio);
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @this {ol.renderer.canvas.VectorTileLayer}
*/
function renderFeature(feature) {
var styles;
var styleFunction = feature.getStyleFunction();
if (styleFunction) {
styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
var sourceTileGrid = source.getTileGrid();
var sourceTileCoord = sourceTile.tileCoord;
var tileProjection = sourceTile.getProjection();
var tileGrid = source.getTileGridForProjection(projection);
var resolution = tileGrid.getResolution(tile.tileCoord[0]);
var sourceTileResolution = sourceTileGrid.getResolution(sourceTile.tileCoord[0]);
var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord);
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord);
var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent);
var extent, reproject, tileResolution;
if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) {
var tilePixelRatio = tileResolution = source.getTilePixelRatio();
var transform = ol.transform.compose(this.tmpTransform_,
0, 0,
1 / sourceTileResolution * tilePixelRatio, -1 / sourceTileResolution * tilePixelRatio,
0,
-sourceTileExtent[0], -sourceTileExtent[3]);
extent = (ol.transform.apply(transform, [sharedExtent[0], sharedExtent[3]])
.concat(ol.transform.apply(transform, [sharedExtent[2], sharedExtent[1]])));
} else {
styleFunction = layer.getStyleFunction();
tileResolution = resolution;
extent = sharedExtent;
if (!ol.proj.equivalent(projection, tileProjection)) {
reproject = true;
sourceTile.setProjection(projection);
}
}
replayState.dirty = false;
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
tileResolution, source.getOverlaps(), layer.getRenderBuffer());
var squaredTolerance = ol.renderer.vector.getSquaredTolerance(
tileResolution, pixelRatio);
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @this {ol.renderer.canvas.VectorTileLayer}
*/
var renderFeature = function(feature) {
var styles;
var styleFunction = feature.getStyleFunction();
if (styleFunction) {
styles = styleFunction(feature, resolution);
styles = styleFunction.call(/** @type {ol.Feature} */ (feature), resolution);
} else {
styleFunction = layer.getStyleFunction();
if (styleFunction) {
styles = styleFunction(feature, resolution);
}
}
}
if (styles) {
if (!Array.isArray(styles)) {
styles = [styles];
if (styles) {
if (!Array.isArray(styles)) {
styles = [styles];
}
var dirty = this.renderFeature(feature, squaredTolerance, styles,
replayGroup);
this.dirty_ = this.dirty_ || dirty;
replayState.dirty = replayState.dirty || dirty;
}
var dirty = this.renderFeature(feature, squaredTolerance, styles,
replayGroup);
this.dirty_ = this.dirty_ || dirty;
replayState.dirty = replayState.dirty || dirty;
};
var features = sourceTile.getFeatures();
if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
features.sort(renderOrder);
}
}
var features = tile.getFeatures();
if (renderOrder && renderOrder !== replayState.renderedRenderOrder) {
features.sort(renderOrder);
}
var feature;
for (var i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (reproject) {
feature.getGeometry().transform(tileProjection, projection);
var feature;
for (var i = 0, ii = features.length; i < ii; ++i) {
feature = features[i];
if (reproject) {
feature.getGeometry().transform(tileProjection, projection);
}
renderFeature.call(this, feature);
}
renderFeature.call(this, feature);
replayGroup.finish();
sourceTile.setReplayGroup(tile.tileCoord.toString(), replayGroup);
}
replayGroup.finish();
replayState.renderedRevision = revision;
replayState.renderedRenderOrder = renderOrder;
replayState.replayGroup = replayGroup;
replayState.resolution = NaN;
};
@@ -193,10 +207,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function(tile,
*/
ol.renderer.canvas.VectorTileLayer.prototype.drawTileImage = function(
tile, frameState, layerState, x, y, w, h, gutter) {
var vectorTile = /** @type {ol.VectorTile} */ (tile);
this.createReplayGroup_(vectorTile, frameState);
var vectorImageTile = /** @type {ol.VectorImageTile} */ (tile);
this.createReplayGroup_(vectorImageTile, frameState);
if (this.context) {
this.renderTileImage_(vectorTile, frameState, layerState);
this.renderTileImage_(vectorImageTile, frameState, layerState);
ol.renderer.canvas.TileLayer.prototype.drawTileImage.apply(this, arguments);
}
};
@@ -213,54 +227,62 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
/** @type {Object.<string, boolean>} */
var features = {};
/** @type {Array.<ol.VectorTile>} */
var replayables = this.renderedTiles;
/** @type {Array.<ol.VectorImageTile>} */
var renderedTiles = this.renderedTiles;
var source = /** @type {ol.source.VectorTile} */ (layer.getSource());
var tileGrid = source.getTileGrid();
var found, tileSpaceCoordinate;
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var sourceTileGrid = source.getTileGrid();
var bufferedExtent, found, tileSpaceCoordinate;
var i, ii, origin, replayGroup;
var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution;
for (i = 0, ii = replayables.length; i < ii; ++i) {
tile = replayables[i];
for (i = 0, ii = renderedTiles.length; i < ii; ++i) {
tile = renderedTiles[i];
tileCoord = tile.tileCoord;
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent);
if (!ol.extent.containsCoordinate(ol.extent.buffer(tileExtent, hitTolerance * resolution), coordinate)) {
tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
bufferedExtent = ol.extent.buffer(tileExtent, hitTolerance * resolution, bufferedExtent);
if (!ol.extent.containsCoordinate(bufferedExtent, coordinate)) {
continue;
}
if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
origin = ol.extent.getTopLeft(tileExtent);
tilePixelRatio = source.getTilePixelRatio();
tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio;
tileSpaceCoordinate = [
(coordinate[0] - origin[0]) / tileResolution,
(origin[1] - coordinate[1]) / tileResolution
];
resolution = tilePixelRatio;
} else {
tileSpaceCoordinate = coordinate;
var sourceTiles = tile.getSourceTiles();
for (var t = 0, tt = sourceTiles.length; t < tt; ++t) {
var sourceTile = sourceTiles[t];
if (sourceTile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
var sourceTileCoord = sourceTile.tileCoord;
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
origin = ol.extent.getTopLeft(sourceTileExtent);
tilePixelRatio = source.getTilePixelRatio();
tileResolution = sourceTileGrid.getResolution(sourceTileCoord[0]) / tilePixelRatio;
tileSpaceCoordinate = [
(coordinate[0] - origin[0]) / tileResolution,
(origin[1] - coordinate[1]) / tileResolution
];
resolution = tilePixelRatio;
} else {
tileSpaceCoordinate = coordinate;
}
replayGroup = sourceTile.getReplayGroup(tile.tileCoord);
found = found || replayGroup.forEachFeatureAtCoordinate(
tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @return {?} Callback result.
*/
function(feature) {
var key = ol.getUid(feature).toString();
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
}
});
}
replayGroup = tile.getReplayState().replayGroup;
found = found || replayGroup.forEachFeatureAtCoordinate(
tileSpaceCoordinate, resolution, rotation, hitTolerance, {},
/**
* @param {ol.Feature|ol.render.Feature} feature Feature.
* @return {?} Callback result.
*/
function(feature) {
var key = ol.getUid(feature).toString();
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
}
});
}
return found;
};
/**
* @param {ol.Tile} tile Tile.
* @param {ol.VectorTile} tile Tile.
* @param {olx.FrameState} frameState Frame state.
* @return {ol.Transform} transform Transform.
* @private
@@ -308,7 +330,9 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(
* @inheritDoc
*/
ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) {
var renderMode = this.getLayer().getRenderMode();
var layer = this.getLayer();
var source = layer.getSource();
var renderMode = layer.getRenderMode();
var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode];
if (replays) {
var pixelRatio = frameState.pixelRatio;
@@ -317,40 +341,55 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra
var offsetX = Math.round(pixelRatio * size[0] / 2);
var offsetY = Math.round(pixelRatio * size[1] / 2);
var tiles = this.renderedTiles;
var tilePixelRatio = layer.getSource().getTilePixelRatio();
var sourceTileGrid = source.getTileGrid();
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var clips = [];
var zs = [];
for (var i = tiles.length - 1; i >= 0; --i) {
var tile = /** @type {ol.VectorTile} */ (tiles[i]);
// Create a clip mask for regions in this low resolution tile that are
// already filled by a higher resolution tile
var transform = this.getReplayTransform_(tile, frameState);
var currentClip = tile.getReplayState().replayGroup.getClipCoords(transform);
var currentZ = tile.tileCoord[0];
context.save();
context.globalAlpha = layerState.opacity;
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
for (var j = 0, jj = clips.length; j < jj; ++j) {
var clip = clips[j];
if (currentZ < zs[j]) {
context.beginPath();
// counter-clockwise (outer ring) for current tile
context.moveTo(currentClip[0], currentClip[1]);
context.lineTo(currentClip[2], currentClip[3]);
context.lineTo(currentClip[4], currentClip[5]);
context.lineTo(currentClip[6], currentClip[7]);
// clockwise (inner ring) for higher resolution tile
context.moveTo(clip[6], clip[7]);
context.lineTo(clip[4], clip[5]);
context.lineTo(clip[2], clip[3]);
context.lineTo(clip[0], clip[1]);
context.clip();
}
var tile = /** @type {ol.VectorImageTile} */ (tiles[i]);
if (tile.getState() == ol.TileState.ABORT) {
continue;
}
var tileCoord = tile.tileCoord;
var worldOffset = tileGrid.getTileCoordExtent(tileCoord)[0] -
tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0];
var sourceTiles = tile.getSourceTiles();
for (var t = 0, tt = sourceTiles.length; t < tt; ++t) {
var sourceTile = sourceTiles[t];
var currentZ = sourceTile.tileCoord[0];
var sourceResolution = sourceTileGrid.getResolution(currentZ);
var transform = this.getReplayTransform_(sourceTile, frameState);
ol.transform.translate(transform, worldOffset * tilePixelRatio / sourceResolution, 0);
var replayGroup = sourceTile.getReplayGroup(tileCoord.toString());
var currentClip = replayGroup.getClipCoords(transform);
context.save();
context.globalAlpha = layerState.opacity;
ol.render.canvas.rotateAtOffset(context, -rotation, offsetX, offsetY);
// Create a clip mask for regions in this low resolution tile that are
// already filled by a higher resolution tile
for (var j = 0, jj = clips.length; j < jj; ++j) {
var clip = clips[j];
if (currentZ < zs[j]) {
context.beginPath();
// counter-clockwise (outer ring) for current tile
context.moveTo(currentClip[0], currentClip[1]);
context.lineTo(currentClip[2], currentClip[3]);
context.lineTo(currentClip[4], currentClip[5]);
context.lineTo(currentClip[6], currentClip[7]);
// clockwise (inner ring) for higher resolution tile
context.moveTo(clip[6], clip[7]);
context.lineTo(clip[4], clip[5]);
context.lineTo(clip[2], clip[3]);
context.lineTo(clip[0], clip[1]);
context.clip();
}
}
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
context.restore();
clips.push(currentClip);
zs.push(currentZ);
}
var replayGroup = tile.getReplayState().replayGroup;
replayGroup.replay(context, pixelRatio, transform, rotation, {}, replays);
context.restore();
clips.push(currentClip);
zs.push(currentZ);
}
}
ol.renderer.canvas.TileLayer.prototype.postCompose.apply(this, arguments);
@@ -386,7 +425,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s
/**
* @param {ol.VectorTile} tile Tile.
* @param {ol.VectorImageTile} tile Tile.
* @param {olx.FrameState} frameState Frame state.
* @param {ol.LayerState} layerState Layer state.
* @private
@@ -399,28 +438,39 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function(
var replays = ol.renderer.canvas.VectorTileLayer.IMAGE_REPLAYS[layer.getRenderMode()];
if (replays && replayState.renderedTileRevision !== revision) {
replayState.renderedTileRevision = revision;
var tileCoord = tile.tileCoord;
var z = tile.tileCoord[0];
var tileCoord = tile.wrappedTileCoord;
var z = tileCoord[0];
var pixelRatio = frameState.pixelRatio;
var source = layer.getSource();
var tileGrid = source.getTileGrid();
var sourceTileGrid = source.getTileGrid();
var tileGrid = source.getTileGridForProjection(frameState.viewState.projection);
var resolution = tileGrid.getResolution(z);
var tilePixelRatio = source.getTilePixelRatio();
var transform = ol.transform.reset(this.tmpTransform_);
if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
var renderPixelRatio = pixelRatio / tilePixelRatio;
ol.transform.scale(transform, renderPixelRatio, renderPixelRatio);
} else {
var resolution = tileGrid.getResolution(z);
var pixelScale = pixelRatio / resolution;
var tileExtent = tileGrid.getTileCoordExtent(tileCoord, this.tmpExtent);
ol.transform.scale(transform, pixelScale, -pixelScale);
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
}
var context = tile.getContext();
var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection);
context.canvas.width = size[0];
context.canvas.height = size[1];
replayState.replayGroup.replay(context, pixelRatio, transform, 0, {}, replays);
var tileExtent = tileGrid.getTileCoordExtent(tileCoord);
var sourceTiles = tile.getSourceTiles();
for (var i = 0, ii = sourceTiles.length; i < ii; ++i) {
var sourceTile = sourceTiles[i];
var sourceTileCoord = sourceTile.tileCoord;
var pixelScale = pixelRatio / resolution;
var transform = ol.transform.reset(this.tmpTransform_);
if (sourceTile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) {
var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent);
var sourceResolution = sourceTileGrid.getResolution(sourceTileCoord[0]);
var renderPixelRatio = pixelRatio / tilePixelRatio * sourceResolution / resolution;
ol.transform.scale(transform, renderPixelRatio, renderPixelRatio);
var offsetX = (sourceTileExtent[0] - tileExtent[0]) / sourceResolution * tilePixelRatio;
var offsetY = (tileExtent[3] - sourceTileExtent[3]) / sourceResolution * tilePixelRatio;
ol.transform.translate(transform, Math.round(offsetX), Math.round(offsetY));
} else {
ol.transform.scale(transform, pixelScale, -pixelScale);
ol.transform.translate(transform, -tileExtent[0], -tileExtent[3]);
}
var replayGroup = sourceTile.getReplayGroup(tile.tileCoord.toString());
replayGroup.replay(context, pixelRatio, transform, 0, {}, replays);
}
}
};