diff --git a/src/ol/ol.js b/src/ol/ol.js
index 23c754f49a..67f3cc5ddc 100644
--- a/src/ol/ol.js
+++ b/src/ol/ol.js
@@ -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
diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js
index 3c3f091b85..0e2d7189a9 100644
--- a/src/ol/render/canvas/canvasreplay.js
+++ b/src/ol/render/canvas/canvasreplay.js
@@ -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;
diff --git a/src/ol/render/vector.js b/src/ol/render/vector.js
index 30b4d4c32e..5c91a013d9 100644
--- a/src/ol/render/vector.js
+++ b/src/ol/render/vector.js
@@ -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);
}
};
diff --git a/src/ol/renderer/canvas/canvasmaprenderer.js b/src/ol/renderer/canvas/canvasmaprenderer.js
index 21c9f88285..447bb44bd8 100644
--- a/src/ol/renderer/canvas/canvasmaprenderer.js
+++ b/src/ol/renderer/canvas/canvasmaprenderer.js
@@ -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 {
diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js
new file mode 100644
index 0000000000..169641285b
--- /dev/null
+++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js
@@ -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.
}
+ */
+ 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.} */
+ 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.>}
+ */
+ 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.} */
+ 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.} 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;
+};