diff --git a/examples/mapbox-vector-tiles-simple.css b/examples/mapbox-vector-tiles-advanced.css
similarity index 100%
rename from examples/mapbox-vector-tiles-simple.css
rename to examples/mapbox-vector-tiles-advanced.css
diff --git a/examples/mapbox-vector-tiles-advanced.html b/examples/mapbox-vector-tiles-advanced.html
new file mode 100644
index 0000000000..0edbe9fe50
--- /dev/null
+++ b/examples/mapbox-vector-tiles-advanced.html
@@ -0,0 +1,17 @@
+---
+template: example.html
+title: Advanced Mapbox vector tiles example
+shortdesc: Example of a Mapbox vector tiles map with custom tile grid.
+docs: >
+ A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwith on mobile devices. **Note**: No map will be visible when the access token has expired.
+tags: "mapbox, vector, tiles, mobile"
+resources:
+ - resources/mapbox-streets-v6-style.js
+cloak:
+ pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg: Your Mapbox access token from http://mapbox.com/ here
+---
+
diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js
new file mode 100644
index 0000000000..aaa0736fcc
--- /dev/null
+++ b/examples/mapbox-vector-tiles-advanced.js
@@ -0,0 +1,73 @@
+goog.require('ol.Attribution');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.format.MVT');
+goog.require('ol.layer.VectorTile');
+goog.require('ol.proj');
+goog.require('ol.source.VectorTile');
+goog.require('ol.style.Fill');
+goog.require('ol.style.Icon');
+goog.require('ol.style.Stroke');
+goog.require('ol.style.Style');
+goog.require('ol.style.Text');
+goog.require('ol.tilegrid.TileGrid');
+
+
+var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
+
+// For how many zoom levels do we want to use the same vector tiles?
+// 1 means "use tiles from all zoom levels". 2 means "use the same tiles for 2
+// subsequent zoom levels".
+var reuseZoomLevels = 2;
+
+// Offset of loaded tiles from web mercator zoom level 0.
+// 0 means "At map zoom level 0, use tiles from zoom level 0". 1 means "At map
+// zoom level 0, use tiles from zoom level 1".
+var zoomOffset = 1;
+
+// Calculation of tile urls
+var resolutions = [];
+for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) {
+ resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels));
+}
+function tileUrlFunction(tileCoord) {
+ return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
+ '{z}/{x}/{y}.vector.pbf?access_token=' + key)
+ .replace('{z}', String(tileCoord[0] * reuseZoomLevels + zoomOffset))
+ .replace('{x}', String(tileCoord[1]))
+ .replace('{y}', String(-tileCoord[2] - 1))
+ .replace('{a-d}', 'abcd'.substr(
+ ((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1));
+}
+
+var map = new ol.Map({
+ layers: [
+ new ol.layer.VectorTile({
+ preload: Infinity,
+ source: new ol.source.VectorTile({
+ attributions: [new ol.Attribution({
+ html: '© Mapbox ' +
+ '© ' +
+ 'OpenStreetMap contributors'
+ })],
+ format: new ol.format.MVT(),
+ tileGrid: new ol.tilegrid.TileGrid({
+ extent: ol.proj.get('EPSG:3857').getExtent(),
+ resolutions: resolutions
+ }),
+ tilePixelRatio: 16,
+ tileUrlFunction: tileUrlFunction
+ }),
+ style: createMapboxStreetsV6Style()
+ })
+ ],
+ target: 'map',
+ view: new ol.View({
+ center: [0, 0],
+ minZoom: 1,
+ zoom: 2
+ })
+});
+
+// ol.style.Fill, ol.style.Icon, ol.style.Stroke, ol.style.Style and
+// ol.style.Text are required for createMapboxStreetsV6Style()
diff --git a/examples/mapbox-vector-tiles-simple.html b/examples/mapbox-vector-tiles-simple.html
deleted file mode 100644
index 9a9d32764a..0000000000
--- a/examples/mapbox-vector-tiles-simple.html
+++ /dev/null
@@ -1,17 +0,0 @@
----
-template: example.html
-title: Simple Mapbox vector tiles example
-shortdesc: Example of a simple Mapbox vector tiles map.
-docs: >
- A simple vector tiles map. **Note**: Make sure to get your own Mapbox API key when using this example. No map will be visible when the API key has expired.
-tags: "simple, mapbox, vector, tiles"
-resources:
- - resources/mapbox-streets-v6-style.js
-cloak:
- pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg: Your Mapbox access token from http://mapbox.com/ here
----
-
diff --git a/examples/mapbox-vector-tiles-simple.js b/examples/mapbox-vector-tiles-simple.js
deleted file mode 100644
index 783e2462af..0000000000
--- a/examples/mapbox-vector-tiles-simple.js
+++ /dev/null
@@ -1,42 +0,0 @@
-goog.require('ol.Attribution');
-goog.require('ol.Map');
-goog.require('ol.View');
-goog.require('ol.format.MVT');
-goog.require('ol.layer.VectorTile');
-goog.require('ol.source.VectorTile');
-goog.require('ol.style.Fill');
-goog.require('ol.style.Icon');
-goog.require('ol.style.Stroke');
-goog.require('ol.style.Style');
-goog.require('ol.style.Text');
-
-
-var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
-
-var map = new ol.Map({
- layers: [
- new ol.layer.VectorTile({
- source: new ol.source.VectorTile({
- attributions: [new ol.Attribution({
- html: '© Mapbox ' +
- '© ' +
- 'OpenStreetMap contributors'
- })],
- format: new ol.format.MVT(),
- tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}),
- tilePixelRatio: 16,
- url: 'http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
- '{z}/{x}/{y}.vector.pbf?access_token=' + key
- }),
- style: createMapboxStreetsV6Style()
- })
- ],
- target: 'map',
- view: new ol.View({
- center: [0, 0],
- zoom: 2
- })
-});
-
-// ol.style.Fill, ol.style.Icon, ol.style.Stroke, ol.style.Style and
-// ol.style.Text are required for createMapboxStreetsV6Style()
diff --git a/examples/mapbox-vector-tiles.html b/examples/mapbox-vector-tiles.html
index 12aa0017fc..27a8dc54c2 100644
--- a/examples/mapbox-vector-tiles.html
+++ b/examples/mapbox-vector-tiles.html
@@ -3,7 +3,7 @@ template: example.html
title: Mapbox vector tiles example
shortdesc: Example of a Mapbox vector tiles map.
docs: >
- A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwith on mobile devices. **Note**: No map will be visible when the access token has expired.
+ A simple vector tiles map. **Note**: Make sure to get your own Mapbox API key when using this example. No map will be visible when the API key has expired.
tags: "simple, mapbox, vector, tiles"
resources:
- resources/mapbox-streets-v6-style.js
diff --git a/examples/mapbox-vector-tiles.js b/examples/mapbox-vector-tiles.js
index aaa0736fcc..783e2462af 100644
--- a/examples/mapbox-vector-tiles.js
+++ b/examples/mapbox-vector-tiles.js
@@ -3,47 +3,19 @@ goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.format.MVT');
goog.require('ol.layer.VectorTile');
-goog.require('ol.proj');
goog.require('ol.source.VectorTile');
goog.require('ol.style.Fill');
goog.require('ol.style.Icon');
goog.require('ol.style.Stroke');
goog.require('ol.style.Style');
goog.require('ol.style.Text');
-goog.require('ol.tilegrid.TileGrid');
var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg';
-// For how many zoom levels do we want to use the same vector tiles?
-// 1 means "use tiles from all zoom levels". 2 means "use the same tiles for 2
-// subsequent zoom levels".
-var reuseZoomLevels = 2;
-
-// Offset of loaded tiles from web mercator zoom level 0.
-// 0 means "At map zoom level 0, use tiles from zoom level 0". 1 means "At map
-// zoom level 0, use tiles from zoom level 1".
-var zoomOffset = 1;
-
-// Calculation of tile urls
-var resolutions = [];
-for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) {
- resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels));
-}
-function tileUrlFunction(tileCoord) {
- return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
- '{z}/{x}/{y}.vector.pbf?access_token=' + key)
- .replace('{z}', String(tileCoord[0] * reuseZoomLevels + zoomOffset))
- .replace('{x}', String(tileCoord[1]))
- .replace('{y}', String(-tileCoord[2] - 1))
- .replace('{a-d}', 'abcd'.substr(
- ((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1));
-}
-
var map = new ol.Map({
layers: [
new ol.layer.VectorTile({
- preload: Infinity,
source: new ol.source.VectorTile({
attributions: [new ol.Attribution({
html: '© Mapbox ' +
@@ -51,12 +23,10 @@ var map = new ol.Map({
'OpenStreetMap contributors'
})],
format: new ol.format.MVT(),
- tileGrid: new ol.tilegrid.TileGrid({
- extent: ol.proj.get('EPSG:3857').getExtent(),
- resolutions: resolutions
- }),
+ tileGrid: ol.tilegrid.createXYZ({maxZoom: 22}),
tilePixelRatio: 16,
- tileUrlFunction: tileUrlFunction
+ url: 'http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' +
+ '{z}/{x}/{y}.vector.pbf?access_token=' + key
}),
style: createMapboxStreetsV6Style()
})
@@ -64,7 +34,6 @@ var map = new ol.Map({
target: 'map',
view: new ol.View({
center: [0, 0],
- minZoom: 1,
zoom: 2
})
});
diff --git a/externs/olx.js b/externs/olx.js
index 51936fde18..36d2bd800e 100644
--- a/externs/olx.js
+++ b/externs/olx.js
@@ -3651,12 +3651,13 @@ olx.layer.VectorOptions.prototype.visible;
/**
- * @typedef {{map: (ol.Map|undefined),
+ * @typedef {{extent: (ol.Extent|undefined),
+ * map: (ol.Map|undefined),
* minResolution: (number|undefined),
* maxResolution: (number|undefined),
* opacity: (number|undefined),
* renderBuffer: (number|undefined),
- * renderOrder: (function(ol.Feature, ol.Feature):number|null|undefined),
+ * renderOrder: (function(ol.Feature, ol.Feature):number|undefined),
* source: (ol.source.VectorTile|undefined),
* style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined),
* updateWhileAnimating: (boolean|undefined),
@@ -3680,9 +3681,8 @@ olx.layer.VectorTileOptions.prototype.renderBuffer;
/**
* Render order. Function to be used when sorting features before rendering. By
- * default features are drawn in the order that they are created. Use `null` to
- * avoid the sort, but get an undefined draw order.
- * @type {function(ol.Feature, ol.Feature):number|null|undefined}
+ * default features are drawn in the order that they are created.
+ * @type {function(ol.Feature, ol.Feature):number|undefined}
* @api
*/
olx.layer.VectorTileOptions.prototype.renderOrder;
@@ -3703,7 +3703,7 @@ olx.layer.VectorTileOptions.prototype.map;
* The bounding extent for layer rendering. The layer will not be rendered
* outside of this extent.
* @type {ol.Extent|undefined}
- * @api
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.extent;
@@ -3711,7 +3711,7 @@ olx.layer.VectorTileOptions.prototype.extent;
/**
* The minimum resolution (inclusive) at which this layer will be visible.
* @type {number|undefined}
- * @api
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.minResolution;
@@ -3719,7 +3719,7 @@ olx.layer.VectorTileOptions.prototype.minResolution;
/**
* The maximum resolution (exclusive) below which this layer will be visible.
* @type {number|undefined}
- * @api
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.maxResolution;
@@ -3727,15 +3727,15 @@ olx.layer.VectorTileOptions.prototype.maxResolution;
/**
* Opacity. 0-1. Default is `1`.
* @type {number|undefined}
- * @api
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.opacity;
/**
* Source.
- * @type {ol.source.VectorTile}
- * @api
+ * @type {ol.source.VectorTile|undefined}
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.source;
@@ -3744,7 +3744,7 @@ olx.layer.VectorTileOptions.prototype.source;
* Layer style. See {@link ol.style} for default style which will be used if
* this is not defined.
* @type {ol.style.Style|Array.|ol.style.StyleFunction|undefined}
- * @api
+ * @api stable
*/
olx.layer.VectorTileOptions.prototype.style;
@@ -4154,6 +4154,7 @@ olx.source.VectorTileOptions.prototype.attributions;
* Feature format for tiles. Used and required by the default
* `tileLoadFunction`.
* @type {ol.format.Feature|undefined}
+ * @api
*/
olx.source.VectorTileOptions.prototype.format;
diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js
index 34ceaa55fc..f3659a8dc7 100644
--- a/src/ol/featureloader.js
+++ b/src/ol/featureloader.js
@@ -102,7 +102,7 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) {
if (source) {
var features = format.readFeatures(source,
{featureProjection: projection});
- if (success.length == 2) {
+ if (ol.ENABLE_VECTOR_TILE && success.length == 2) {
success.call(this, features, format.readProjection(source));
} else {
success.call(this, features);
@@ -171,5 +171,5 @@ ol.featureloader.xhr = function(url, format) {
*/
function(features) {
this.addFeatures(features);
- }, ol.nullFunction);
+ }, /* FIXME handle error */ ol.nullFunction);
};
diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js
index 65d3806aaf..10c3c0e0b7 100644
--- a/src/ol/format/mvtformat.js
+++ b/src/ol/format/mvtformat.js
@@ -107,7 +107,7 @@ ol.format.MVT.prototype.readFeature_ = function(
goog.asserts.assertInstanceof(geometry, ol.geom.Geometry);
values[this.geometryName_] = geometry;
}
- feature.setProperties(rawFeature.properties);
+ feature.setProperties(values);
feature.setGeometryName(this.geometryName_);
return feature;
};
@@ -121,21 +121,9 @@ ol.format.MVT.prototype.readFeature_ = function(
*/
ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
var coords = rawFeature.loadGeometry();
- var end = 0;
var ends = [];
var flatCoordinates = [];
- var line, coord;
- for (var i = 0, ii = coords.length; i < ii; ++i) {
- line = coords[i];
- for (var j = 0, jj = line.length; j < jj; ++j) {
- coord = line[j];
- // Non-tilespace coords can be calculated here when a TileGrid and
- // TileCoord are known.
- flatCoordinates.push(coord.x, coord.y);
- }
- end += 2 * j;
- ends.push(end);
- }
+ ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
var type = rawFeature.type;
/** @type {ol.geom.GeometryType} */
@@ -149,15 +137,14 @@ ol.format.MVT.prototype.readRenderFeature_ = function(rawFeature, layer) {
} else {
geometryType = ol.geom.GeometryType.MULTI_LINE_STRING;
}
- } else {
+ } else if (type === 3) {
geometryType = ol.geom.GeometryType.POLYGON;
}
- var properties = rawFeature.properties;
- properties[this.layerName_] = layer;
+ var values = rawFeature.properties;
+ values[this.layerName_] = layer;
- return new this.featureClass_(
- geometryType, flatCoordinates, ends, rawFeature.properties);
+ return new this.featureClass_(geometryType, flatCoordinates, ends, values);
};
@@ -180,7 +167,7 @@ ol.format.MVT.prototype.readFeatures = function(source, opt_options) {
}
layer = tile.layers[name];
- for (var i = 0, ii = layer.length; i < layer.length; ++i) {
+ for (var i = 0, ii = layer.length; i < ii; ++i) {
if (featureClass === ol.render.Feature) {
feature = this.readRenderFeature_(layer.feature(i), name);
} else {
@@ -214,20 +201,14 @@ ol.format.MVT.prototype.setLayers = function(layers) {
/**
* @private
- * @param {Object} rawFeature Raw Mapbox feature.
- * @return {ol.geom.Geometry} Geometry.
+ * @param {Object} coords Raw feature coordinates.
+ * @param {Array.} flatCoordinates Flat coordinates to be populated by
+ * this function.
+ * @param {Array.} ends Ends to be populated by this function.
*/
-ol.format.MVT.readGeometry_ = function(rawFeature) {
- var type = rawFeature.type;
- if (type === 0) {
- return null;
- }
-
- var coords = rawFeature.loadGeometry();
-
+ol.format.MVT.calculateFlatCoordinates_ = function(
+ coords, flatCoordinates, ends) {
var end = 0;
- var ends = [];
- var flatCoordinates = [];
var line, coord;
for (var i = 0, ii = coords.length; i < ii; ++i) {
line = coords[i];
@@ -240,6 +221,24 @@ ol.format.MVT.readGeometry_ = function(rawFeature) {
end += 2 * j;
ends.push(end);
}
+};
+
+
+/**
+ * @private
+ * @param {Object} rawFeature Raw Mapbox feature.
+ * @return {ol.geom.Geometry} Geometry.
+ */
+ol.format.MVT.readGeometry_ = function(rawFeature) {
+ var type = rawFeature.type;
+ if (type === 0) {
+ return null;
+ }
+
+ var coords = rawFeature.loadGeometry();
+ var ends = [];
+ var flatCoordinates = [];
+ ol.format.MVT.calculateFlatCoordinates_(coords, flatCoordinates, ends);
var geom;
if (type === 1) {
@@ -251,7 +250,7 @@ ol.format.MVT.readGeometry_ = function(rawFeature) {
} else {
geom = new ol.geom.MultiLineString(null);
}
- } else {
+ } else if (type === 3) {
geom = new ol.geom.Polygon(null);
}
diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js
index 46f69d9115..a87cb6b853 100644
--- a/src/ol/geom/geometry.js
+++ b/src/ol/geom/geometry.js
@@ -253,7 +253,8 @@ ol.geom.Geometry.prototype.translate = goog.abstractMethod;
*/
ol.geom.Geometry.prototype.transform = function(source, destination) {
goog.asserts.assert(
- ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS,
+ ol.proj.get(source).getUnits() !== ol.proj.Units.TILE_PIXELS &&
+ ol.proj.get(destination).getUnits() !== ol.proj.Units.TILE_PIXELS,
'cannot transform geometries with TILE_PIXELS units');
this.applyTransform(ol.proj.getTransform(source, destination));
return this;
diff --git a/src/ol/proj/proj.js b/src/ol/proj/proj.js
index 499fb5eb0e..88e95593f8 100644
--- a/src/ol/proj/proj.js
+++ b/src/ol/proj/proj.js
@@ -677,11 +677,8 @@ ol.proj.get = function(projectionLike) {
ol.proj.equivalent = function(projection1, projection2) {
if (projection1 === projection2) {
return true;
- } else if (projection1.getCode() === projection2.getCode() &&
- projection1.getUnits() === projection2.getUnits()) {
- return true;
- } else if (projection1.getUnits() != projection2.getUnits()) {
- return false;
+ } else if (projection1.getCode() === projection2.getCode()) {
+ return projection1.getUnits() === projection2.getUnits();
} else {
var transformFn = ol.proj.getTransformFromProjections(
projection1, projection2);
diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js
index 9221527e11..a31a8c9203 100644
--- a/src/ol/render/canvas/canvasreplay.js
+++ b/src/ol/render/canvas/canvasreplay.js
@@ -1180,6 +1180,10 @@ ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) {
miterLimit: undefined
};
+ /**
+ * @private
+ * @type {boolean}
+ */
this.rightHanded_ = false;
};
diff --git a/src/ol/render/renderfeature.js b/src/ol/render/renderfeature.js
index 7b6001ae8c..17ead76116 100644
--- a/src/ol/render/renderfeature.js
+++ b/src/ol/render/renderfeature.js
@@ -86,7 +86,7 @@ ol.render.Feature.prototype.getEnds = function() {
* @api
*/
ol.render.Feature.prototype.getExtent = function() {
- if (!goog.isDef(this.extent_)) {
+ if (!this.extent_) {
this.extent_ = this.type_ === ol.geom.GeometryType.POINT ?
ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) :
ol.extent.createOrUpdateFromFlatCoordinates(
diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js
index c0d91b903d..03d9bbe8c7 100644
--- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js
@@ -86,7 +86,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
var projection = viewState.projection;
var resolution = viewState.resolution;
var rotation = viewState.rotation;
- var source = this.getLayer().getSource();
+ var layer = this.getLayer();
+ var source = layer.getSource();
goog.asserts.assertInstanceof(source, ol.source.VectorTile,
'Source is an ol.source.VectorTile');
@@ -94,7 +95,6 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
this.dispatchPreComposeEvent(context, frameState, transform);
- var layer = this.getLayer();
var replayContext;
if (layer.hasListener(ol.render.EventType.RENDER)) {
// resize and clear
@@ -160,10 +160,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame =
ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile,
layer, pixelRatio) {
var revision = layer.getRevision();
- var renderOrder = layer.getRenderOrder();
- if (renderOrder === undefined) {
- renderOrder = null;
- }
+ var renderOrder = layer.getRenderOrder() || null;
var replayState = tile.getReplayState();
if (!replayState.dirty && replayState.renderedRevision == revision &&
diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js
index 1ed9338ccf..0f0b70b7f5 100644
--- a/src/ol/source/urltilesource.js
+++ b/src/ol/source/urltilesource.js
@@ -112,7 +112,7 @@ ol.source.UrlTile.prototype.getTileUrlFunction = function() {
/**
- * Return the URLs used for this XYZ source.
+ * Return the URLs used for this source.
* When a tileUrlFunction is used instead of url or urls,
* null will be returned.
* @return {!Array.|null} URLs.
diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js
index 29ab88587c..a533a1c92a 100644
--- a/src/ol/vectortile.js
+++ b/src/ol/vectortile.js
@@ -170,7 +170,7 @@ ol.VectorTile.prototype.setState = function(tileState) {
/**
- * Set the feeature loader for reading this tile's features.
+ * Set the feature loader for reading this tile's features.
* @param {ol.FeatureLoader} loader Feature loader.
* @api
*/
diff --git a/test_rendering/spec/ol/data/tiles/mvt/14-8938-5680.vector.pbf b/test_rendering/spec/ol/data/tiles/mvt/14-8938-5680.vector.pbf
new file mode 100644
index 0000000000..0ed0c1ee24
Binary files /dev/null and b/test_rendering/spec/ol/data/tiles/mvt/14-8938-5680.vector.pbf differ
diff --git a/test_rendering/spec/ol/layer/expected/vectortile-canvas.png b/test_rendering/spec/ol/layer/expected/vectortile-canvas.png
new file mode 100644
index 0000000000..8b6ddf8536
Binary files /dev/null and b/test_rendering/spec/ol/layer/expected/vectortile-canvas.png differ
diff --git a/test_rendering/spec/ol/layer/vectortile.test.js b/test_rendering/spec/ol/layer/vectortile.test.js
new file mode 100644
index 0000000000..44b9eeadba
--- /dev/null
+++ b/test_rendering/spec/ol/layer/vectortile.test.js
@@ -0,0 +1,82 @@
+goog.provide('ol.test.rendering.layer.VectorTile');
+
+describe('ol.rendering.layer.VectorTile', function() {
+
+ var target, map;
+
+ function createMap(renderer) {
+ target = createMapDiv(50, 50);
+
+ map = new ol.Map({
+ target: target,
+ renderer: renderer,
+ view: new ol.View({
+ center: [1825927.7316762917, 6143091.089223046],
+ zoom: 14
+ })
+ });
+ return map;
+ }
+
+ function waitForTiles(source, layerOptions, onTileLoaded) {
+ var tilesLoading = 0;
+ var tileLoaded = 0;
+
+ var update = function() {
+ if (tilesLoading === tileLoaded) {
+ onTileLoaded();
+ }
+ };
+
+ source.on('tileloadstart', function(event) {
+ tilesLoading++;
+ });
+ source.on('tileloadend', function(event) {
+ tileLoaded++;
+ update();
+ });
+ source.on('tileloaderror', function(event) {
+ expect().fail('Tile failed to load');
+ });
+
+ var options = {
+ source: source
+ };
+ goog.object.extend(options, layerOptions);
+ map.addLayer(new ol.layer.VectorTile(options));
+ }
+
+ describe('vector tile layer', function() {
+ var source;
+
+ beforeEach(function() {
+ source = new ol.source.VectorTile({
+ format: new ol.format.MVT(),
+ tileGrid: ol.tilegrid.createXYZ(),
+ tilePixelRatio: 16,
+ url: 'spec/ol/data/tiles/mvt/{z}-{x}-{y}.vector.pbf'
+ });
+ });
+
+ afterEach(function() {
+ disposeMap(map);
+ });
+
+ it('renders correctly with the canvas renderer', function(done) {
+ map = createMap('canvas');
+ waitForTiles(source, {}, function() {
+ expectResemble(map, 'spec/ol/layer/expected/vectortile-canvas.png',
+ IMAGE_TOLERANCE, done);
+ });
+ });
+
+ });
+
+});
+
+goog.require('goog.object');
+goog.require('ol.format.MVT');
+goog.require('ol.Map');
+goog.require('ol.View');
+goog.require('ol.layer.VectorTile');
+goog.require('ol.source.VectorTile');