Add VectorTile renderer

This commit is contained in:
Andreas Hocevar
2015-10-13 14:34:26 +02:00
parent 7d3fc3ccc7
commit 4b942bc4f6
5 changed files with 489 additions and 3 deletions

View File

@@ -124,6 +124,14 @@ ol.ENABLE_TILE = true;
ol.ENABLE_VECTOR = true;
/**
* @define {boolean} Enable rendering of ol.layer.VectorTile based layers.
* Default is `true`. Setting this to false at compile time in advanced mode
* removes all code supporting VectorTile layers from the build.
*/
ol.ENABLE_VECTOR_TILE = true;
/**
* @define {boolean} Enable the WebGL renderer. Default is `true`. Setting
* this to false at compile time in advanced mode removes all code

View File

@@ -1158,6 +1158,8 @@ ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) {
miterLimit: undefined
};
this.rightHanded_ = false;
};
goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay);
@@ -1290,7 +1292,9 @@ ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry =
state.miterLimit, state.lineDash]);
}
var ends = polygonGeometry.getEnds();
var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates();
var flatCoordinates = this.rightHanded_ ?
polygonGeometry.getFlatCoordinates() :
polygonGeometry.getOrientedFlatCoordinates();
var stride = polygonGeometry.getStride();
this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride);
this.endGeometry(polygonGeometry, feature);
@@ -1375,6 +1379,16 @@ ol.render.canvas.PolygonReplay.prototype.getBufferedMaxExtent = function() {
};
/**
* @param {boolean} rightHanded Whether polygons are assumed to follow
* the right-hand rule.
*/
ol.render.canvas.PolygonReplay.prototype.setRightHanded =
function(rightHanded) {
this.rightHanded_ = rightHanded;
};
/**
* @inheritDoc
*/
@@ -1793,10 +1807,13 @@ ol.render.canvas.TextReplay.prototype.setTextStyle = function(textStyle) {
* @param {ol.Extent} maxExtent Max extent.
* @param {number} resolution Resolution.
* @param {number=} opt_renderBuffer Optional rendering buffer.
* @param {boolean=} opt_rightHandedPolygons Assume that polygons follow the
* right-hand rule.
* @struct
*/
ol.render.canvas.ReplayGroup = function(
tolerance, maxExtent, resolution, opt_renderBuffer) {
tolerance, maxExtent, resolution, opt_renderBuffer,
opt_rightHandedPolygons) {
/**
* @private
@@ -1841,6 +1858,13 @@ ol.render.canvas.ReplayGroup = function(
*/
this.hitDetectionTransform_ = goog.vec.Mat4.createNumber();
/**
* @private
* @type {boolean}
*/
this.rightHandedPolygons_ = goog.isDef(opt_rightHandedPolygons) ?
opt_rightHandedPolygons : false;
};
@@ -1928,6 +1952,10 @@ ol.render.canvas.ReplayGroup.prototype.getReplay =
' constructor missing from ol.render.canvas.BATCH_CONSTRUCTORS_');
replay = new Constructor(this.tolerance_, this.maxExtent_,
this.resolution_);
if (replayType == ol.render.ReplayType.POLYGON) {
goog.asserts.assertInstanceof(replay, ol.render.canvas.PolygonReplay);
replay.setRightHanded(this.rightHandedPolygons_);
}
replays[replayType] = replay;
}
return replay;

View File

@@ -271,7 +271,8 @@ ol.renderer.vector.renderPointGeometry_ =
var textReplay = replayGroup.getReplay(
style.getZIndex(), ol.render.ReplayType.TEXT);
textReplay.setTextStyle(textStyle);
textReplay.drawText(geometry.getCoordinates(), 0, 2, 2, geometry, feature);
textReplay.drawText(geometry.getFlatCoordinates(), 0, 2, 2, geometry,
feature);
}
};

View File

@@ -15,6 +15,7 @@ goog.require('ol.layer.Image');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.layer.Vector');
goog.require('ol.layer.VectorTile');
goog.require('ol.render.Event');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas.Immediate');
@@ -23,6 +24,7 @@ goog.require('ol.renderer.canvas.ImageLayer');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.canvas.TileLayer');
goog.require('ol.renderer.canvas.VectorLayer');
goog.require('ol.renderer.canvas.VectorTileLayer');
goog.require('ol.source.State');
goog.require('ol.vec.Mat4');
@@ -79,6 +81,8 @@ ol.renderer.canvas.Map.prototype.createLayerRenderer = function(layer) {
return new ol.renderer.canvas.ImageLayer(layer);
} else if (ol.ENABLE_TILE && layer instanceof ol.layer.Tile) {
return new ol.renderer.canvas.TileLayer(layer);
} else if (ol.ENABLE_VECTOR_TILE && layer instanceof ol.layer.VectorTile) {
return new ol.renderer.canvas.VectorTileLayer(layer);
} else if (ol.ENABLE_VECTOR && layer instanceof ol.layer.Vector) {
return new ol.renderer.canvas.VectorLayer(layer);
} else {

View File

@@ -0,0 +1,445 @@
goog.provide('ol.renderer.canvas.VectorTileLayer');
goog.require('goog.array');
goog.require('goog.asserts');
goog.require('goog.events');
goog.require('goog.object');
goog.require('goog.vec.Mat4');
goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.ViewHint');
goog.require('ol.dom');
goog.require('ol.extent');
goog.require('ol.layer.VectorTile');
goog.require('ol.proj.Units');
goog.require('ol.render.EventType');
goog.require('ol.render.canvas.ReplayGroup');
goog.require('ol.renderer.canvas.Layer');
goog.require('ol.renderer.vector');
goog.require('ol.size');
goog.require('ol.source.VectorTile');
goog.require('ol.tilecoord');
goog.require('ol.vec.Mat4');
/**
* @constructor
* @extends {ol.renderer.canvas.Layer}
* @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();
};
goog.inherits(ol.renderer.canvas.VectorTileLayer, ol.renderer.canvas.Layer);
/**
* @inheritDoc
*/
ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
function(frameState, layerState, context) {
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 source = this.getLayer().getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
var transform = this.getTransform(frameState, 0);
this.dispatchPreComposeEvent(context, frameState, transform);
var layer = this.getLayer();
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_;
} else {
replayContext = context;
}
// for performance reasons, context.save / context.restore is not used
// to save and restore the transformation matrix and the opacity.
// see http://jsperf.com/context-save-restore-versus-variable
var alpha = replayContext.globalAlpha;
replayContext.globalAlpha = layerState.opacity;
var tilesToDraw = this.renderedTiles_;
var tileGrid = source.getTileGrid();
var currentZ, i, ii, origin, scale, tile, tileExtent, tileSize;
var tilePixelRatio, tilePixelResolution, tilePixelSize, tileResolution;
for (i = 0, ii = tilesToDraw.length; i < ii; ++i) {
tile = tilesToDraw[i];
currentZ = tile.getTileCoord()[0];
tileSize = tileGrid.getTileSize(currentZ);
tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection);
tilePixelRatio = tilePixelSize[0] /
ol.size.toSize(tileGrid.getTileSize(currentZ), this.tmpSize_)[0];
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);
}
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);
}
replayContext.globalAlpha = alpha;
this.dispatchPostComposeEvent(context, frameState, transform);
};
/**
* @param {ol.VectorTile} tile Tile.
* @param {ol.layer.VectorTile} layer Vector tile layer.
* @param {number} resolution Resolution.
* @param {number} pixelRatio Pixel ratio.
* @return {boolean} Success.
*/
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
layer, resolution, pixelRatio) {
var revision = layer.getRevision();
var renderOrder = layer.getRenderOrder();
if (!goog.isDef(renderOrder)) {
renderOrder = null;
}
var replayState = tile.getReplayState();
if (!replayState.dirty &&
replayState.renderedResolution == resolution &&
replayState.renderedRevision == revision &&
replayState.renderedRenderOrder == renderOrder) {
return false;
}
// FIXME dispose of old replayGroup in post render
goog.dispose(replayState.replayGroup);
replayState.replayGroup = null;
replayState.dirty = false;
var source = layer.getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
var tileGrid = source.getTileGrid();
var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS;
var extent = pixelSpace ?
[0, 0].concat(source.getTilePixelSize(
tileGrid.getZForResolution(resolution), pixelRatio,
tile.getProjection())) :
tileGrid.getTileCoordExtent(tile.getTileCoord());
var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution;
var replayGroup = new ol.render.canvas.ReplayGroup(pixelSpace ? 0 :
ol.renderer.vector.getTolerance(tileResolution, pixelRatio), extent,
tileResolution, layer.getRenderBuffer(),
source.getRightHandedPolygons());
/**
* @param {ol.Feature} feature Feature.
* @this {ol.renderer.canvas.VectorTileLayer}
*/
function renderFeature(feature) {
var styles;
if (goog.isDef(feature.getStyleFunction())) {
styles = feature.getStyleFunction().call(feature, resolution);
} else if (goog.isDef(layer.getStyleFunction())) {
styles = layer.getStyleFunction()(feature, resolution);
}
var squaredTolerance = pixelSpace ? -1 :
ol.renderer.vector.getSquaredTolerance(resolution, pixelRatio);
if (goog.isDefAndNotNull(styles)) {
var dirty = this.renderFeature(feature, squaredTolerance, styles,
replayGroup);
replayState.dirty = replayState.dirty || dirty;
this.dirty_ = this.dirty_ || replayState.dirty;
}
}
var features = tile.getFeatures();
if (!goog.isNull(renderOrder) &&
renderOrder !== replayState.renderedRenderOrder) {
goog.array.sort(features, renderOrder);
}
goog.array.forEach(features, renderFeature, this);
replayGroup.finish();
replayState.renderedResolution = resolution;
replayState.renderedRevision = revision;
replayState.renderedRenderOrder = renderOrder;
replayState.replayGroup = replayGroup;
return true;
};
/**
* @inheritDoc
*/
ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate =
function(coordinate, frameState, callback, thisArg) {
var resolution = frameState.viewState.resolution;
var rotation = frameState.viewState.rotation;
var layer = this.getLayer();
var layerState = frameState.layerStates[goog.getUid(layer)];
/** @type {Object.<string, boolean>} */
var features = {};
var replayables = this.renderedTiles_;
var found, tileSpaceCoordinate;
var i, ii, origin, replayGroup, source;
var tile, tileCoord, tileExtent, tilePixelRatio, tileResolution, tileSize;
for (i = 0, ii = replayables.length; i < ii; ++i) {
tile = replayables[i];
tileCoord = tile.getTileCoord();
source = layer.getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord);
if (!ol.extent.containsCoordinate(tileExtent, coordinate)) {
continue;
}
if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) {
origin = ol.extent.getTopLeft(
source.getTileGrid().getTileCoordExtent(tileCoord));
tilePixelRatio = source.getTilePixelRatio();
tileResolution = resolution / tilePixelRatio;
tileSize = ol.size.toSize(
source.getTileGrid().getTileSize(tileCoord[0]));
tileSpaceCoordinate = [
(coordinate[0] - origin[0]) / tileResolution,
(origin[1] - coordinate[1]) / tileResolution
];
resolution = tilePixelRatio;
} else {
tileSpaceCoordinate = coordinate;
}
replayGroup = tile.getReplayState().replayGroup;
found = found || replayGroup.forEachFeatureAtCoordinate(
tileSpaceCoordinate, resolution, rotation,
layerState.managed ? frameState.skippedFeatureUids : {},
/**
* @param {ol.Feature} feature Feature.
* @return {?} Callback result.
*/
function(feature) {
goog.asserts.assert(goog.isDef(feature), 'received a feature');
var key = goog.getUid(feature).toString();
if (!(key in features)) {
features[key] = true;
return callback.call(thisArg, feature, layer);
}
});
}
return found;
};
/**
* Handle changes in image style state.
* @param {goog.events.Event} event Image style change event.
* @private
*/
ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ =
function(event) {
this.renderIfReadyAndVisible();
};
/**
* @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 (goog.isDef(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 z = tileGrid.getZForResolution(resolution);
var tileRange = tileGrid.getTileRangeForExtentAndResolution(
extent, resolution);
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.Tile>>}
*/
var tilesToDrawByZ = {};
tilesToDrawByZ[z] = {};
var findLoadedTiles = this.createLoadedTileFinder(source, 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);
tileState = tile.getState();
if (tileState == ol.TileState.LOADED ||
tileState == ol.TileState.EMPTY ||
(tileState == ol.TileState.ERROR && !useInterimTilesOnError)) {
tilesToDrawByZ[z][ol.tilecoord.toString(tile.tileCoord)] = tile;
continue;
}
fullyLoaded = tileGrid.forEachTileCoordParentTileRange(
tile.tileCoord, findLoadedTiles, null, tmpTileRange, tmpExtent);
if (!fullyLoaded) {
childTileRange = tileGrid.getTileCoordChildTileRange(
tile.tileCoord, tmpTileRange, tmpExtent);
if (!goog.isNull(childTileRange)) {
findLoadedTiles(z + 1, childTileRange);
}
}
}
}
/** @type {Array.<number>} */
var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number);
goog.array.sort(zs);
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);
}
}
}
for (i = 0, ii = replayables.length; i < ii; ++i) {
tile = replayables[i];
this.dirty_ = this.createReplayGroup(tile, layer, resolution, pixelRatio) ||
this.dirty_;
}
this.renderedTiles_ = replayables;
return true;
};
/**
* @param {ol.Feature} feature Feature.
* @param {number} squaredTolerance Squared tolerance.
* @param {Array.<ol.style.Style>} styles Array of styles
* @param {ol.render.canvas.ReplayGroup} replayGroup Replay group.
* @return {boolean} `true` if an image is loading.
*/
ol.renderer.canvas.VectorTileLayer.prototype.renderFeature =
function(feature, squaredTolerance, styles, replayGroup) {
if (!goog.isDefAndNotNull(styles)) {
return false;
}
var i, ii, loading = false;
for (i = 0, ii = styles.length; i < ii; ++i) {
loading = ol.renderer.vector.renderFeature(
replayGroup, feature, styles[i], squaredTolerance,
this.handleStyleImageChange_, this) || loading;
}
return loading;
};