Add render options for vector tile layers

This commit is contained in:
Andreas Hocevar
2016-04-05 23:18:13 +02:00
parent 0e4692e69e
commit f7147960a9
12 changed files with 510 additions and 403 deletions

View File

@@ -4,91 +4,107 @@ goog.require('goog.asserts');
goog.require('ol.events');
goog.require('goog.vec.Mat4');
goog.require('ol.Feature');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.VectorTile');
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');
goog.require('ol.proj.Units');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.vector');
goog.require('ol.size');
goog.require('ol.source.VectorTile');
goog.require('ol.vec.Mat4');
/**
* @const
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
*/
ol.renderer.canvas.IMAGE_REPLAYS = {
'image': ol.render.REPLAY_ORDER,
'hybrid': [ol.render.ReplayType.POLYGON, ol.render.ReplayType.LINE_STRING]
};
/**
* @const
* @type {!Object.<string, Array.<ol.render.ReplayType>>}
*/
ol.renderer.canvas.VECTOR_REPLAYS = {
'hybrid': [ol.render.ReplayType.IMAGE, ol.render.ReplayType.TEXT],
'vector': ol.render.REPLAY_ORDER
};
/**
* @constructor
* @extends {ol.renderer.canvas.Layer}
* @extends {ol.renderer.canvas.TileLayer}
* @param {ol.layer.VectorTile} layer VectorTile layer.
*/
ol.renderer.canvas.VectorTileLayer = function(layer) {
goog.base(this, layer);
/**
* @private
* @type {CanvasRenderingContext2D}
*/
this.context_ = ol.dom.createCanvasContext2D();
/**
* @private
* @type {boolean}
*/
this.dirty_ = false;
/**
* @private
* @type {Array.<ol.VectorTile>}
*/
this.renderedTiles_ = [];
/**
* @private
* @type {ol.Extent}
*/
this.tmpExtent_ = ol.extent.createEmpty();
/**
* @private
* @type {ol.Size}
*/
this.tmpSize_ = [NaN, NaN];
/**
* @private
* @type {!goog.vec.Mat4.Number}
*/
this.tmpTransform_ = goog.vec.Mat4.createNumber();
// Use lower resolution for pure vector rendering. Closest resolution otherwise.
this.zDirection =
layer.getRenderMode() == ol.layer.VectorTileRenderType.VECTOR ? 1 : 0;
};
goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.Layer);
goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.TileLayer);
/**
* @inheritDoc
*/
ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState, layerState, context) {
ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(
frameState, layerState, context) {
var transform = this.getTransform(frameState, 0);
this.dispatchPreComposeEvent(context, frameState, transform);
this.renderTileImages(context, frameState, layerState);
this.renderTileReplays_(context, frameState, layerState);
this.dispatchPostComposeEvent(context, frameState, transform);
};
/**
* @param {CanvasRenderingContext2D} context Context.
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @private
*/
ol.renderer.canvas.VectorTileLayer.prototype.renderTileReplays_ = function(
context, frameState, layerState) {
var layer = this.getLayer();
var replays = ol.renderer.canvas.VECTOR_REPLAYS[layer.getRenderMode()];
if (!replays) {
return;
}
var pixelRatio = frameState.pixelRatio;
var skippedFeatureUids = layerState.managed ?
frameState.skippedFeatureUids : {};
var viewState = frameState.viewState;
var center = viewState.center;
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');
@@ -96,14 +112,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState,
var transform = this.getTransform(frameState, 0);
this.dispatchPreComposeEvent(context, frameState, transform);
var replayContext;
if (layer.hasListener(ol.render.EventType.RENDER)) {
// resize and clear
this.context_.canvas.width = context.canvas.width;
this.context_.canvas.height = context.canvas.height;
replayContext = this.context_;
this.context.canvas.width = context.canvas.width;
this.context.canvas.height = context.canvas.height;
replayContext = this.context;
} else {
replayContext = context;
}
@@ -113,80 +127,39 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState,
var alpha = replayContext.globalAlpha;
replayContext.globalAlpha = layerState.opacity;
var tilesToDraw = this.renderedTiles_;
var tilesToDraw = this.renderedTiles;
var tileGrid = source.getTileGrid();
var currentZ, height, i, ii, insertPoint, insertTransform, offsetX, offsetY;
var origin, pixelSpace, replayState, resolutionRatio, tile, tileCenter;
var tileContext, tileExtent, tilePixelResolution, tilePixelSize;
var tileResolution, tileSize, tileTransform, width;
var currentZ, i, ii, offsetX, offsetY, origin, pixelSpace, replayState;
var tile, tileExtent, tilePixelResolution, tileResolution, tileTransform;
for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
tile = tilesToDraw[i];
replayState = tile.getReplayState();
tileExtent = tileGrid.getTileCoordExtent(
tile.getTileCoord(), this.tmpExtent_);
tile.getTileCoord(), this.tmpExtent);
currentZ = tile.getTileCoord()[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;
resolutionRatio = tileResolution / resolution;
offsetX = Math.round(pixelRatio * size[0] / 2);
offsetY = Math.round(pixelRatio * size[1] / 2);
width = tileSize[0] * pixelRatio * resolutionRatio;
height = tileSize[1] * pixelRatio * resolutionRatio;
var unscaledPixelTileSize = tileSize[0] * pixelRatio;
if (width < unscaledPixelTileSize / 4 || width > unscaledPixelTileSize * 4) {
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);
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 {
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.drawImage(tileContext.canvas,
Math.round(insertPoint[0] + offsetX),
Math.round(insertPoint[1]) + offsetY);
tileTransform = transform;
}
ol.render.canvas.rotateAtOffset(replayContext, -rotation, offsetX, offsetY);
replayState.replayGroup.replay(replayContext, pixelRatio,
tileTransform, rotation, skippedFeatureUids, replays);
ol.render.canvas.rotateAtOffset(replayContext, rotation, offsetX, offsetY);
}
if (replayContext != context) {
@@ -194,19 +167,18 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = function(frameState,
context.drawImage(replayContext.canvas, 0, 0);
}
replayContext.globalAlpha = alpha;
this.dispatchPostComposeEvent(context, frameState, transform);
};
/**
* @param {ol.VectorTile} tile Tile.
* @param {ol.layer.VectorTile} layer Vector tile layer.
* @param {number} pixelRatio Pixel ratio.
* @param {ol.proj.Projection} projection Projection.
* @param {olx.FrameState} frameState Frame state.
*/
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
layer, pixelRatio, projection) {
frameState) {
var layer = this.getLayer();
var pixelRatio = frameState.pixelRatio;
var projection = frameState.viewState.projection;
var revision = layer.getRevision();
var renderOrder = layer.getRenderOrder() || null;
@@ -226,21 +198,20 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
var tileCoord = tile.getTileCoord();
var tileProjection = tile.getProjection();
var pixelSpace = tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS;
var extent, reproject;
var resolution = tileGrid.getResolution(tileCoord[0]);
var extent, reproject, tileResolution;
if (pixelSpace) {
var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio,
tile.getProjection());
extent = [0, 0, tilePixelSize[0], tilePixelSize[1]];
var tilePixelRatio = tileResolution = source.getTilePixelRatio(pixelRatio);
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);
}
}
var resolution = tileGrid.getResolution(tileCoord[0]);
var tileResolution =
pixelSpace ? source.getTilePixelRatio(pixelRatio) : resolution;
replayState.dirty = false;
var replayGroup = new ol.render.canvas.ReplayGroup(0, extent,
tileResolution, layer.getRenderBuffer());
@@ -307,7 +278,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
/** @type {Object.<string, boolean>} */
var features = {};
var replayables = this.renderedTiles_;
var replayables = this.renderedTiles;
var source = layer.getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
@@ -319,7 +290,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi
tile = replayables[i];
tileCoord = tile.getTileCoord();
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord,
this.tmpExtent_);
this.tmpExtent);
if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
continue;
}
@@ -369,116 +340,17 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function(
* @inheritDoc
*/
ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = function(frameState, layerState) {
var layer = /** @type {ol.layer.Vector} */ (this.getLayer());
goog.asserts.assertInstanceof(layer, ol.layer.VectorTile,
'layer is an instance of ol.layer.VectorTile');
var source = layer.getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
this.updateAttributions(
frameState.attributions, source.getAttributions());
this.updateLogos(frameState, source);
var animating = frameState.viewHints[ol.ViewHint.ANIMATING];
var interacting = frameState.viewHints[ol.ViewHint.INTERACTING];
var updateWhileAnimating = layer.getUpdateWhileAnimating();
var updateWhileInteracting = layer.getUpdateWhileInteracting();
if (!this.dirty_ && (!updateWhileAnimating && animating) ||
(!updateWhileInteracting && interacting)) {
return true;
}
var extent = frameState.extent;
if (layerState.extent) {
extent = ol.extent.getIntersection(extent, layerState.extent);
}
if (ol.extent.isEmpty(extent)) {
// Return false to prevent the rendering of the layer.
return false;
}
var viewState = frameState.viewState;
var projection = viewState.projection;
var resolution = viewState.resolution;
var pixelRatio = frameState.pixelRatio;
var tileGrid = source.getTileGrid();
var resolutions = tileGrid.getResolutions();
var z = resolutions.length - 1;
while (z > 0 && resolutions[z] < resolution) {
--z;
}
var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
this.updateUsedTiles(frameState.usedTiles, source, z, tileRange);
this.manageTilePyramid(frameState, source, tileGrid, pixelRatio,
projection, extent, z, layer.getPreload());
this.scheduleExpireCache(frameState, source);
/**
* @type {Object.<number, Object.<string, ol.VectorTile>>}
*/
var tilesToDrawByZ = {};
tilesToDrawByZ[z] = {};
var findLoadedTiles = this.createLoadedTileFinder(source, projection,
tilesToDrawByZ);
var useInterimTilesOnError = layer.getUseInterimTilesOnError();
var tmpExtent = this.tmpExtent_;
var tmpTileRange = new ol.TileRange(0, 0, 0, 0);
var childTileRange, fullyLoaded, tile, tileState, x, y;
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
tile = source.getTile(z, x, y, pixelRatio, projection);
goog.asserts.assertInstanceof(tile, ol.VectorTile,
'Tile is an ol.VectorTile');
tileState = tile.getState();
if (tileState == ol.TileState.LOADED ||
tileState == ol.TileState.EMPTY ||
(tileState == ol.TileState.ERROR && !useInterimTilesOnError)) {
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
continue;
}
fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
if (!fullyLoaded) {
childTileRange = tileGrid.getTileCoordChildTileRange(
tile.tileCoord, tmpTileRange, tmpExtent);
if (childTileRange) {
findLoadedTiles(z + 1, childTileRange);
}
}
var prepared = goog.base(this, 'prepareFrame', frameState, layerState);
if (prepared) {
var skippedFeatures = Object.keys(frameState.skippedFeatureUids_ || {});
for (var i = 0, ii = this.renderedTiles.length; i < ii; ++i) {
var tile = this.renderedTiles[i];
goog.asserts.assertInstanceof(tile, ol.VectorTile, 'got an ol.VectorTile');
this.createReplayGroup(tile, frameState);
this.renderTileImage(tile, frameState, layerState, skippedFeatures);
}
}
this.dirty_ = false;
/** @type {Array.<number>} */
var zs = Object.keys(tilesToDrawByZ).map(Number);
zs.sort(ol.array.numberSafeCompareFunction);
var replayables = [];
var i, ii, currentZ, tileCoordKey, tilesToDraw;
for (i = 0, ii = zs.length; i < ii; ++i) {
currentZ = zs[i];
tilesToDraw = tilesToDrawByZ[currentZ];
for (tileCoordKey in tilesToDraw) {
tile = tilesToDraw[tileCoordKey];
if (tile.getState() == ol.TileState.LOADED) {
replayables.push(tile);
this.createReplayGroup(tile, layer, pixelRatio, projection);
}
}
}
this.renderedTiles_ = replayables;
return true;
return prepared;
};
@@ -508,3 +380,65 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, s
}
return loading;
};
/**
* @param {ol.VectorTile} tile Tile.
* @param {olx.FrameState} frameState Frame state.
* @param {ol.layer.LayerState} layerState Layer state.
* @param {Array.<string>} skippedFeatures Skipped features.
*/
ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage = function(
tile, frameState, layerState, skippedFeatures) {
var layer = this.getLayer();
var replays = ol.renderer.canvas.IMAGE_REPLAYS[layer.getRenderMode()];
if (!replays) {
return;
}
var pixelRatio = frameState.pixelRatio;
var replayState = tile.getReplayState();
var revision = layer.getRevision();
if (!ol.array.equals(replayState.skippedFeatures, skippedFeatures) ||
replayState.renderedTileRevision !== revision) {
replayState.skippedFeatures = skippedFeatures;
replayState.renderedTileRevision = revision;
var tileContext = tile.getContext();
var source = layer.getSource();
var tileGrid = source.getTileGrid();
var currentZ = tile.getTileCoord()[0];
var resolution = tileGrid.getResolution(currentZ);
var tileSize = ol.size.toSize(tileGrid.getTileSize(currentZ));
var tileResolution = tileGrid.getResolution(currentZ);
var resolutionRatio = tileResolution / resolution;
var width = tileSize[0] * pixelRatio * resolutionRatio;
var height = tileSize[1] * pixelRatio * resolutionRatio;
tileContext.canvas.width = width / resolutionRatio + 0.5;
tileContext.canvas.height = height / resolutionRatio + 0.5;
tileContext.scale(1 / resolutionRatio, 1 / resolutionRatio)
tileContext.translate(width / 2, height / 2);
var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
var pixelScale = pixelRatio / resolution;
var tilePixelRatio = source.getTilePixelRatio(pixelRatio);
var tilePixelResolution = tileResolution / tilePixelRatio;
var tileExtent = tileGrid.getTileCoordExtent(
tile.getTileCoord(), this.tmpExtent);
var tileTransform;
if (pixelSpace) {
tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_,
0, 0,
pixelScale * tilePixelResolution, pixelScale * tilePixelResolution,
0,
-tileSize[0] * tilePixelRatio / 2, -tileSize[1] * tilePixelRatio / 2);
} else {
var tileCenter = ol.extent.getCenter(tileExtent);
tileTransform = ol.vec.Mat4.makeTransform2D(this.tmpTransform_,
0, 0,
pixelScale, -pixelScale,
0,
-tileCenter[0], -tileCenter[1]);
}
replayState.replayGroup.replay(tileContext, pixelRatio,
tileTransform, 0, frameState.skippedFeatureUids || {}, replays);
}
}