From 8f4756c99b7cda887495ceb08672346ee082cfc9 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 13 Oct 2015 14:15:30 +0200 Subject: [PATCH 01/27] Introduce tile-pixels units --- src/ol/geom/geometry.js | 5 +++++ src/ol/proj/proj.js | 4 +++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/ol/geom/geometry.js b/src/ol/geom/geometry.js index 70fac46257..46f69d9115 100644 --- a/src/ol/geom/geometry.js +++ b/src/ol/geom/geometry.js @@ -2,10 +2,12 @@ goog.provide('ol.geom.Geometry'); goog.provide('ol.geom.GeometryLayout'); goog.provide('ol.geom.GeometryType'); +goog.require('goog.asserts'); goog.require('goog.functions'); goog.require('ol.Object'); goog.require('ol.extent'); goog.require('ol.proj'); +goog.require('ol.proj.Units'); /** @@ -250,6 +252,9 @@ ol.geom.Geometry.prototype.translate = goog.abstractMethod; * @api stable */ ol.geom.Geometry.prototype.transform = function(source, destination) { + goog.asserts.assert( + ol.proj.get(source).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 cfb7c9faa9..193bc838d3 100644 --- a/src/ol/proj/proj.js +++ b/src/ol/proj/proj.js @@ -23,7 +23,8 @@ ol.proj.ProjectionLike; /** - * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, or `'us-ft'`. + * Projection units: `'degrees'`, `'ft'`, `'m'`, `'pixels'`, `'tile-pixels'` or + * `'us-ft'`. * @enum {string} * @api stable */ @@ -32,6 +33,7 @@ ol.proj.Units = { FEET: 'ft', METERS: 'm', PIXELS: 'pixels', + TILE_PIXELS: 'tile-pixels', USFEET: 'us-ft' }; From 598111b78ecf45667fcb4b929c61f30e49b9cbbe Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 13 Oct 2015 14:18:00 +0200 Subject: [PATCH 02/27] Introduce ol.format.MVT --- externs/olx.js | 34 ++++ package.json | 6 +- src/ol/featureloader.js | 7 +- src/ol/format/featureformat.js | 2 +- src/ol/format/format.js | 1 + src/ol/format/mvtformat.js | 196 ++++++++++++++++++++++ test/spec/ol/data/14-8938-5680.vector.pbf | Bin 0 -> 46937 bytes test/spec/ol/format/mvtformat.test.js | 68 ++++++++ 8 files changed, 311 insertions(+), 3 deletions(-) create mode 100644 src/ol/format/mvtformat.js create mode 100644 test/spec/ol/data/14-8938-5680.vector.pbf create mode 100644 test/spec/ol/format/mvtformat.test.js diff --git a/externs/olx.js b/externs/olx.js index eef86f69d0..57986ca413 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -1685,6 +1685,40 @@ olx.format.EsriJSONOptions; olx.format.EsriJSONOptions.prototype.geometryName; +/** + * @typedef {{geometryName: (string|undefined), + * layers: (Array.|undefined), + * layerName: (string|undefined)}} + * @api + */ +olx.format.MVTOptions; + + +/** + * Geometry name to use when creating features. Default is 'geometry'. + * @type {string|undefined} + * @api + */ +olx.format.MVTOptions.prototype.geometryName; + + +/** + * Name of the feature attribute that holds the layer name. Default is 'layer'. + * @type {string|undefined} + * @api + */ +olx.format.MVTOptions.prototype.layerName; + + +/** + * Layers to read features from. If not provided, features will be read from all + * layers. + * @type {Array.|undefined} + * @api + */ +olx.format.MVTOptions.prototype.layers; + + /** * @typedef {{factor: (number|undefined), * geometryLayout: (ol.geom.GeometryLayout|undefined)}} diff --git a/package.json b/package.json index bf323b85ae..20c1e81495 100644 --- a/package.json +++ b/package.json @@ -38,9 +38,11 @@ "metalsmith": "1.6.0", "metalsmith-templates": "0.7.0", "nomnom": "1.8.0", + "pbf": "1.3.5", "pixelworks": "1.0.0", "rbush": "1.3.5", "temp": "0.8.1", + "vector-tile": "1.1.3", "walk": "2.3.4" }, "devDependencies": { @@ -64,6 +66,8 @@ }, "ext": [ "rbush", - {"module": "pixelworks", "browserify": true} + {"module": "pbf", "browserify": true}, + {"module": "pixelworks", "browserify": true}, + {"module": "vector-tile", "name": "vectortile", "browserify": true} ] } diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index 0450108f19..735ca5b67f 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -61,7 +61,10 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { */ function(extent, resolution, projection) { var xhrIo = new goog.net.XhrIo(); - xhrIo.setResponseType(goog.net.XhrIo.ResponseType.TEXT); + xhrIo.setResponseType( + format.getType() == ol.format.FormatType.ARRAY_BUFFER ? + goog.net.XhrIo.ResponseType.ARRAY_BUFFER : + goog.net.XhrIo.ResponseType.TEXT); goog.events.listen(xhrIo, goog.net.EventType.COMPLETE, /** * @param {Event} event Event. @@ -87,6 +90,8 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { if (!source) { source = ol.xml.parse(xhrIo.getResponseText()); } + } else if (type == ol.format.FormatType.ARRAY_BUFFER) { + source = xhrIo.getResponse(); } else { goog.asserts.fail('unexpected format type'); } diff --git a/src/ol/format/featureformat.js b/src/ol/format/featureformat.js index 59d0c77943..25c10aa79d 100644 --- a/src/ol/format/featureformat.js +++ b/src/ol/format/featureformat.js @@ -95,7 +95,7 @@ ol.format.Feature.prototype.readFeature = goog.abstractMethod; /** * Read all features from a source. * - * @param {Document|Node|Object|string} source Source. + * @param {Document|Node|ArrayBuffer|Object|string} source Source. * @param {olx.format.ReadOptions=} opt_options Read options. * @return {Array.} Features. */ diff --git a/src/ol/format/format.js b/src/ol/format/format.js index e0cca0fdae..3fa3586e3f 100644 --- a/src/ol/format/format.js +++ b/src/ol/format/format.js @@ -5,6 +5,7 @@ goog.provide('ol.format.FormatType'); * @enum {string} */ ol.format.FormatType = { + ARRAY_BUFFER: 'arraybuffer', JSON: 'json', TEXT: 'text', XML: 'xml' diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js new file mode 100644 index 0000000000..97936f1585 --- /dev/null +++ b/src/ol/format/mvtformat.js @@ -0,0 +1,196 @@ +//FIXME Implement projection handling + +goog.provide('ol.format.MVT'); + +goog.require('goog.array'); +goog.require('goog.asserts'); +goog.require('ol.Feature'); +goog.require('ol.ext.pbf'); +goog.require('ol.ext.vectortile'); +goog.require('ol.format.Feature'); +goog.require('ol.format.FormatType'); +goog.require('ol.geom.Geometry'); +goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.LineString'); +goog.require('ol.geom.MultiLineString'); +goog.require('ol.geom.MultiPoint'); +goog.require('ol.geom.Point'); +goog.require('ol.geom.Polygon'); +goog.require('ol.proj'); +goog.require('ol.proj.Projection'); +goog.require('ol.proj.Units'); + + + +/** + * @classdesc + * Feature format for reading data in the Mapbox MVT format. + * + * @constructor + * @extends {ol.format.Feature} + * @param {olx.format.MVTOptions=} opt_options Options. + * @api + */ +ol.format.MVT = function(opt_options) { + + goog.base(this); + + var options = goog.isDef(opt_options) ? opt_options : {}; + + /** + * @type {ol.proj.Projection} + */ + this.defaultDataProjection = new ol.proj.Projection({ + code: 'EPSG:3857', + units: ol.proj.Units.TILE_PIXELS + }); + + /** + * @private + * @type {string} + */ + this.geometryName_ = goog.isDef(options.geometryName) ? + options.geometryName : 'geometry'; + + /** + * @private + * @type {string} + */ + this.layerName_ = goog.isDef(options.layerName) ? options.layerName : 'layer'; + + /** + * @private + * @type {Array.} + */ + this.layers_ = goog.isDef(options.layers) ? options.layers : null; + +}; +goog.inherits(ol.format.MVT, ol.format.Feature); + + +/** + * @inheritDoc + */ +ol.format.MVT.prototype.getType = function() { + return ol.format.FormatType.ARRAY_BUFFER; +}; + + +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {olx.format.ReadOptions=} opt_options Read options. + * @return {ol.Feature} Feature. + */ +ol.format.MVT.prototype.readFeature_ = function(rawFeature, opt_options) { + var feature = new ol.Feature(); + var values = rawFeature.properties; + var geometry = ol.format.Feature.transformWithOptions( + ol.format.MVT.readGeometry_(rawFeature), false, + this.adaptOptions(opt_options)); + if (!goog.isNull(geometry)) { + goog.asserts.assertInstanceof(geometry, ol.geom.Geometry); + values[this.geometryName_] = geometry; + } + feature.setProperties(rawFeature.properties); + feature.setGeometryName(this.geometryName_); + return feature; +}; + + +/** + * @inheritDoc + */ +ol.format.MVT.prototype.readFeatures = function(source, opt_options) { + goog.asserts.assertInstanceof(source, ArrayBuffer); + + var layerName = this.layerName_; + var layers = this.layers_; + + var pbf = new ol.ext.pbf(source); + var tile = new ol.ext.vectortile.VectorTile(pbf); + var features = []; + var layer, feature; + for (var name in tile.layers) { + if (!goog.isNull(layers) && !goog.array.contains(layers, name)) { + continue; + } + layer = tile.layers[name]; + + for (var i = 0, ii = layer.length; i < layer.length; ++i) { + feature = this.readFeature_(layer.feature(i), opt_options); + feature.set(layerName, name); + features.push(feature); + } + } + + return features; +}; + + +/** + * @inheritDoc + */ +ol.format.MVT.prototype.readProjection = function(source) { + return this.defaultDataProjection; +}; + + +/** + * Sets the layers that features will be read from. + * @param {Array.} layers Layers. + * @api + */ +ol.format.MVT.prototype.setLayers = function(layers) { + this.layers_ = layers; +}; + + +/** + * @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 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); + } + + var geom; + if (type === 1) { + geom = coords.length === 1 ? + new ol.geom.Point(null) : new ol.geom.MultiPoint(null); + } else if (type === 2) { + if (coords.length === 1) { + geom = new ol.geom.LineString(null); + } else { + geom = new ol.geom.MultiLineString(null); + } + } else { + geom = new ol.geom.Polygon(null); + } + + geom.setFlatCoordinates(ol.geom.GeometryLayout.XY, flatCoordinates, + ends); + + return geom; +}; diff --git a/test/spec/ol/data/14-8938-5680.vector.pbf b/test/spec/ol/data/14-8938-5680.vector.pbf new file mode 100644 index 0000000000000000000000000000000000000000..0ed0c1ee240ac78a8ae5f6921253d8a8325c47ef GIT binary patch literal 46937 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 Date: Tue, 13 Oct 2015 14:22:02 +0200 Subject: [PATCH 03/27] Create ol.source.UrlTile --- src/ol/source/tiledebugsource.js | 5 +- src/ol/source/tileimagesource.js | 124 +++-------------------- src/ol/source/tileutfgridsource.js | 5 +- src/ol/source/urltilesource.js | 151 +++++++++++++++++++++++++++++ 4 files changed, 170 insertions(+), 115 deletions(-) create mode 100644 src/ol/source/urltilesource.js diff --git a/src/ol/source/tiledebugsource.js b/src/ol/source/tiledebugsource.js index 3bfd917b34..32a0fad16d 100644 --- a/src/ol/source/tiledebugsource.js +++ b/src/ol/source/tiledebugsource.js @@ -45,7 +45,10 @@ goog.inherits(ol.DebugTile_, ol.Tile); /** - * @inheritDoc + * Get the image element for this tile. + * @param {Object=} opt_context Optional context. Only used by the DOM + * renderer. + * @return {HTMLCanvasElement} Image. */ ol.DebugTile_.prototype.getImage = function(opt_context) { var key = opt_context !== undefined ? goog.getUid(opt_context) : -1; diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js index 964e7d7690..f5b1929ccf 100644 --- a/src/ol/source/tileimagesource.js +++ b/src/ol/source/tileimagesource.js @@ -6,15 +6,10 @@ goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('ol.ImageTile'); goog.require('ol.TileCache'); -goog.require('ol.TileCoord'); -goog.require('ol.TileLoadFunctionType'); goog.require('ol.TileState'); -goog.require('ol.TileUrlFunction'); -goog.require('ol.TileUrlFunctionType'); goog.require('ol.proj'); goog.require('ol.reproj.Tile'); -goog.require('ol.source.Tile'); -goog.require('ol.source.TileEvent'); +goog.require('ol.source.UrlTile'); @@ -24,7 +19,7 @@ goog.require('ol.source.TileEvent'); * * @constructor * @fires ol.source.TileEvent - * @extends {ol.source.Tile} + * @extends {ol.source.UrlTile} * @param {olx.source.TileImageOptions} options Image tile options. * @api */ @@ -39,18 +34,13 @@ ol.source.TileImage = function(options) { state: options.state !== undefined ? /** @type {ol.source.State} */ (options.state) : undefined, tileGrid: options.tileGrid, + tileLoadFunction: goog.isDef(options.tileLoadFunction) ? + options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction, tilePixelRatio: options.tilePixelRatio, + tileUrlFunction: options.tileUrlFunction, wrapX: options.wrapX }); - /** - * @protected - * @type {ol.TileUrlFunctionType} - */ - this.tileUrlFunction = options.tileUrlFunction !== undefined ? - options.tileUrlFunction : - ol.TileUrlFunction.nullTileUrlFunction; - /** * @protected * @type {?string} @@ -58,13 +48,6 @@ ol.source.TileImage = function(options) { this.crossOrigin = options.crossOrigin !== undefined ? options.crossOrigin : null; - /** - * @protected - * @type {ol.TileLoadFunctionType} - */ - this.tileLoadFunction = options.tileLoadFunction !== undefined ? - options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction; - /** * @protected * @type {function(new: ol.ImageTile, ol.TileCoord, ol.TileState, string, @@ -97,16 +80,7 @@ ol.source.TileImage = function(options) { */ this.renderReprojectionEdges_ = false; }; -goog.inherits(ol.source.TileImage, ol.source.Tile); - - -/** - * @param {ol.ImageTile} imageTile Image tile. - * @param {string} src Source. - */ -ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { - imageTile.getImage().src = src; -}; +goog.inherits(ol.source.TileImage, ol.source.UrlTile); /** @@ -249,7 +223,7 @@ ol.source.TileImage.prototype.getTileInternal = this.crossOrigin, this.tileLoadFunction); goog.events.listen(tile, goog.events.EventType.CHANGE, - this.handleTileChange_, false, this); + this.handleTileChange, false, this); this.tileCache.set(tileCoordKey, tile); return tile; @@ -257,50 +231,6 @@ ol.source.TileImage.prototype.getTileInternal = }; -/** - * Return the tile load function of the source. - * @return {ol.TileLoadFunctionType} TileLoadFunction - * @api - */ -ol.source.TileImage.prototype.getTileLoadFunction = function() { - return this.tileLoadFunction; -}; - - -/** - * Return the tile URL function of the source. - * @return {ol.TileUrlFunctionType} TileUrlFunction - * @api - */ -ol.source.TileImage.prototype.getTileUrlFunction = function() { - return this.tileUrlFunction; -}; - - -/** - * Handle tile change events. - * @param {goog.events.Event} event Event. - * @private - */ -ol.source.TileImage.prototype.handleTileChange_ = function(event) { - var tile = /** @type {ol.Tile} */ (event.target); - switch (tile.getState()) { - case ol.TileState.LOADING: - this.dispatchEvent( - new ol.source.TileEvent(ol.source.TileEventType.TILELOADSTART, tile)); - break; - case ol.TileState.LOADED: - this.dispatchEvent( - new ol.source.TileEvent(ol.source.TileEventType.TILELOADEND, tile)); - break; - case ol.TileState.ERROR: - this.dispatchEvent( - new ol.source.TileEvent(ol.source.TileEventType.TILELOADERROR, tile)); - break; - } -}; - - /** * Sets whether to render reprojection edges or not (usually for debugging). * @param {boolean} render Render the edges. @@ -346,41 +276,9 @@ ol.source.TileImage.prototype.setTileGridForProjection = /** - * Set the tile load function of the source. - * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. - * @api + * @param {ol.ImageTile} imageTile Image tile. + * @param {string} src Source. */ -ol.source.TileImage.prototype.setTileLoadFunction = function(tileLoadFunction) { - this.tileCache.clear(); - this.tileCacheForProjection = {}; - this.tileLoadFunction = tileLoadFunction; - this.changed(); -}; - - -/** - * Set the tile URL function of the source. - * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. - * @api - */ -ol.source.TileImage.prototype.setTileUrlFunction = function(tileUrlFunction) { - // FIXME It should be possible to be more intelligent and avoid clearing the - // FIXME cache. The tile URL function would need to be incorporated into the - // FIXME cache key somehow. - this.tileCache.clear(); - this.tileCacheForProjection = {}; - this.tileUrlFunction = tileUrlFunction; - this.changed(); -}; - - -/** - * @inheritDoc - */ -ol.source.TileImage.prototype.useTile = function(z, x, y, projection) { - var tileCache = this.getTileCacheForProjection(projection); - var tileCoordKey = this.getKeyZXY(z, x, y); - if (tileCache && tileCache.containsKey(tileCoordKey)) { - tileCache.get(tileCoordKey); - } +ol.source.TileImage.defaultTileLoadFunction = function(imageTile, src) { + imageTile.getImage().src = src; }; diff --git a/src/ol/source/tileutfgridsource.js b/src/ol/source/tileutfgridsource.js index 8862353677..5cc861ab36 100644 --- a/src/ol/source/tileutfgridsource.js +++ b/src/ol/source/tileutfgridsource.js @@ -256,7 +256,10 @@ goog.inherits(ol.source.TileUTFGridTile_, ol.Tile); /** - * @inheritDoc + * Get the image element for this tile. + * @param {Object=} opt_context Optional context. Only used for the DOM + * renderer. + * @return {Image} Image. */ ol.source.TileUTFGridTile_.prototype.getImage = function(opt_context) { return null; diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js new file mode 100644 index 0000000000..ccec07a350 --- /dev/null +++ b/src/ol/source/urltilesource.js @@ -0,0 +1,151 @@ +goog.provide('ol.source.UrlTile'); + +goog.require('goog.events'); +goog.require('ol.TileLoadFunctionType'); +goog.require('ol.TileState'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.TileUrlFunctionType'); +goog.require('ol.source.Tile'); +goog.require('ol.source.TileEvent'); + + +/** + * @typedef {{attributions: (Array.|undefined), + * cacheSize: (number|undefined), + * extent: (ol.Extent|undefined), + * logo: (string|olx.LogoOptions|undefined), + * opaque: (boolean|undefined), + * projection: ol.proj.ProjectionLike, + * state: (ol.source.State|string|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined), + * tileLoadFunction: ol.TileLoadFunctionType, + * tilePixelRatio: (number|undefined), + * tileUrlFunction: (ol.TileUrlFunctionType|undefined), + * wrapX: (boolean|undefined)}} + */ +ol.source.UrlTileOptions; + + + +/** + * @classdesc + * Base class for sources providing tiles divided into a tile grid over http. + * + * @constructor + * @fires ol.source.TileEvent + * @extends {ol.source.Tile} + * @param {ol.source.UrlTileOptions} options Image tile options. + */ +ol.source.UrlTile = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: goog.isDef(options.state) ? + /** @type {ol.source.State} */ (options.state) : undefined, + tileGrid: options.tileGrid, + tilePixelRatio: options.tilePixelRatio, + wrapX: options.wrapX + }); + + /** + * @protected + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? + options.tileUrlFunction : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @protected + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction = options.tileLoadFunction; + +}; +goog.inherits(ol.source.UrlTile, ol.source.Tile); + + +/** + * Return the tile load function of the source. + * @return {ol.TileLoadFunctionType} TileLoadFunction + * @api + */ +ol.source.UrlTile.prototype.getTileLoadFunction = function() { + return this.tileLoadFunction; +}; + + +/** + * Return the tile URL function of the source. + * @return {ol.TileUrlFunctionType} TileUrlFunction + * @api + */ +ol.source.UrlTile.prototype.getTileUrlFunction = function() { + return this.tileUrlFunction; +}; + + +/** + * Handle tile change events. + * @param {goog.events.Event} event Event. + * @protected + */ +ol.source.UrlTile.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + switch (tile.getState()) { + case ol.TileState.LOADING: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADSTART, tile)); + break; + case ol.TileState.LOADED: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADEND, tile)); + break; + case ol.TileState.ERROR: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADERROR, tile)); + break; + } +}; + + +/** + * Set the tile load function of the source. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + * @api + */ +ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) { + this.tileCache.clear(); + this.tileLoadFunction = tileLoadFunction; + this.changed(); +}; + + +/** + * Set the tile URL function of the source. + * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. + * @api + */ +ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction) { + // FIXME It should be possible to be more intelligent and avoid clearing the + // FIXME cache. The tile URL function would need to be incorporated into the + // FIXME cache key somehow. + this.tileCache.clear(); + this.tileUrlFunction = tileUrlFunction; + this.changed(); +}; + + +/** + * @inheritDoc + */ +ol.source.UrlTile.prototype.useTile = function(z, x, y) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + this.tileCache.get(tileCoordKey); + } +}; From 7d3fc3ccc7a6272ed4b24976d4401aa6d94db34d Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 13 Oct 2015 14:29:14 +0200 Subject: [PATCH 04/27] Add VectorTile layer, source and tile --- externs/olx.js | 263 ++++++++++++++++++++++++++++++ src/ol/featureloader.js | 37 ++++- src/ol/layer/vectortilelayer.js | 99 +++++++++++ src/ol/source/vectortilesource.js | 107 ++++++++++++ src/ol/tileloadfunction.js | 6 +- src/ol/vectortile.js | 173 ++++++++++++++++++++ 6 files changed, 677 insertions(+), 8 deletions(-) create mode 100644 src/ol/layer/vectortilelayer.js create mode 100644 src/ol/source/vectortilesource.js create mode 100644 src/ol/vectortile.js diff --git a/externs/olx.js b/externs/olx.js index 57986ca413..fa5febf9b1 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -3633,6 +3633,133 @@ olx.layer.VectorOptions.prototype.updateWhileInteracting; olx.layer.VectorOptions.prototype.visible; +/** + * @typedef {{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), + * source: (ol.source.VectorTile|undefined), + * style: (ol.style.Style|Array.|ol.style.StyleFunction|undefined), + * updateWhileAnimating: (boolean|undefined), + * updateWhileInteracting: (boolean|undefined), + * visible: (boolean|undefined)}} + * @api + */ +olx.layer.VectorTileOptions; + + +/** + * The buffer around the viewport extent used by the renderer when getting + * features from the vector source for the rendering or hit-detection. + * Recommended value: the size of the largest symbol, line width or label. + * Default is 100 pixels. + * @type {number|undefined} + * @api + */ +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} + * @api + */ +olx.layer.VectorTileOptions.prototype.renderOrder; + + +/** + * Sets the layer as overlay on a map. The map will not manage this layer in its + * layers collection, and the layer will be rendered on top. This is useful for + * temporary layers. The standard way to add a layer to a map and have it + * managed by the map is to use {@link ol.Map#addLayer}. + * @type {ol.Map|undefined} + * @api + */ +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 + */ +olx.layer.VectorTileOptions.prototype.extent; + + +/** + * The minimum resolution (inclusive) at which this layer will be visible. + * @type {number|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.minResolution; + + +/** + * The maximum resolution (exclusive) below which this layer will be visible. + * @type {number|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.maxResolution; + + +/** + * Opacity. 0-1. Default is `1`. + * @type {number|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.opacity; + + +/** + * Source. + * @type {ol.source.VectorTile} + * @api + */ +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 + */ +olx.layer.VectorTileOptions.prototype.style; + + +/** + * When set to `true`, feature batches will be recreated during animations. + * This means that no vectors will be shown clipped, but the setting will have a + * performance impact for large amounts of vector data. When set to `false`, + * batches will be recreated when no animation is active. Default is `false`. + * @type {boolean|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.updateWhileAnimating; + + +/** + * When set to `true`, feature batches will be recreated during interactions. + * See also `updateWhileAnimating`. Default is `false`. + * @type {boolean|undefined} + * @api + */ +olx.layer.VectorTileOptions.prototype.updateWhileInteracting; + + +/** + * Visibility. Default is `true` (visible). + * @type {boolean|undefined} + * @api stable + */ +olx.layer.VectorTileOptions.prototype.visible; + + /** * Namespace. * @type {Object} @@ -3955,6 +4082,142 @@ olx.source.TileImageOptions.prototype.tileUrlFunction; olx.source.TileImageOptions.prototype.wrapX; +/** + * @typedef {{attributions: (Array.|undefined), + * logo: (string|olx.LogoOptions|undefined), + * opaque: (boolean|undefined), + * projection: ol.proj.ProjectionLike, + * state: (ol.source.State|string|undefined), + * format: (ol.format.Feature|undefined), + * rightHandedPolygons: (boolean|undefined), + * tileClass: (function(new: ol.VectorTile, ol.TileCoord, + * ol.TileState, string, ol.format.Feature, + * ol.TileLoadFunctionType)|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined), + * tileLoadFunction: (ol.TileLoadFunctionType|undefined), + * tilePixelRatio: (number|undefined), + * tileUrlFunction: (ol.TileUrlFunctionType|undefined), + * wrapX: (boolean|undefined)}} + * @api + */ +olx.source.VectorTileOptions; + + +/** +/** + * Attributions. + * @type {Array.|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.attributions; + + +/** + * Logo. + * @type {string|olx.LogoOptions|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.logo; + + +/** + * Whether the layer is opaque. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.opaque; + + +/** + * Projection. + * @type {ol.proj.ProjectionLike} + * @api + */ +olx.source.VectorTileOptions.prototype.projection; + + +/** + * Assume that all polygons provided by this source follow the right-hand rule + * (counter-clockwise for exterior and clockwise for interior rings). If `true`, + * renderers will skip the check for the ring orientation. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.rightHandedPolygons; + + +/** + * Source state. + * @type {ol.source.State|string|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.state; + + +/** + * Feature format for tiles. Used and required by the default + * `tileLoadFunction`. + * @type {ol.format.Feature|undefined} + */ +olx.source.VectorTileOptions.prototype.format; + + +/** + * Class used to instantiate image tiles. Default is {@link ol.VectorTile}. + * @type {function(new: ol.VectorTile, ol.TileCoord, + * ol.TileState, string, ol.format.Feature, + * ol.TileLoadFunctionType)|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.tileClass; + + +/** + * Tile grid. + * @type {ol.tilegrid.TileGrid|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.tileGrid; + + +/** + * Optional function to load a tile given a URL. + * @type {ol.TileLoadFunctionType|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.tileLoadFunction; + + +/** + * The pixel ratio used by the tile service. For example, if the tile + * service advertizes 256px by 256px tiles but actually sends 512px + * by 512px tiles (for retina/hidpi devices) then `tilePixelRatio` + * should be set to `2`. Default is `1`. + * @type {number|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.tilePixelRatio; + + +/** + * Optional function to get tile URL given a tile coordinate and the projection. + * @type {ol.TileUrlFunctionType|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.tileUrlFunction; + + +/** + * Whether to wrap the world horizontally. The default, `undefined`, is to + * request out-of-bounds tiles from the server. When set to `false`, only one + * world will be rendered. When set to `true`, tiles will be requested for one + * world only, but they will be wrapped horizontally to render multiple worlds. + * @type {boolean|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.wrapX; + + /** * @typedef {{attributions: (Array.|undefined), * format: (ol.format.Feature|undefined), diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index 735ca5b67f..d53f5ddf2b 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -46,9 +46,9 @@ ol.FeatureUrlFunction; /** * @param {string|ol.FeatureUrlFunction} url Feature URL service. * @param {ol.format.Feature} format Feature format. - * @param {function(this:ol.source.Vector, Array.)} success - * Function called with the loaded features. Called with the vector - * source as `this`. + * @param {function(this:ol.source.Vector, Array., ol.proj.Projection)|function(this:ol.source.Vector, Array.)} success + * Function called with the loaded features and optionally with the data + * projection. Called with the vector source as `this`. * @return {ol.FeatureLoader} The feature loader. */ ol.featureloader.loadFeaturesXhr = function(url, format, success) { @@ -57,7 +57,7 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { * @param {ol.Extent} extent Extent. * @param {number} resolution Resolution. * @param {ol.proj.Projection} projection Projection. - * @this {ol.source.Vector} + * @this {ol.source.Vector|ol.VectorTile} */ function(extent, resolution, projection) { var xhrIo = new goog.net.XhrIo(); @@ -98,7 +98,11 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { if (source) { var features = format.readFeatures(source, {featureProjection: projection}); - success.call(this, features); + if (success.length == 2) { + success.call(this, features, format.readProjection(source)); + } else { + success.call(this, features); + } } else { goog.asserts.fail('undefined or null source'); } @@ -117,6 +121,29 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { }; +/** + * Create an XHR feature loader for a `url` and `format`. The feature loader + * loads features (with XHR), parses the features, and adds them to the + * vector tile. + * @param {string|ol.FeatureUrlFunction} url Feature URL service. + * @param {ol.format.Feature} format Feature format. + * @return {ol.FeatureLoader} The feature loader. + * @api + */ +ol.featureloader.tile = function(url, format) { + return ol.featureloader.loadFeaturesXhr(url, format, + /** + * @param {Array.} features The loaded features. + * @param {ol.proj.Projection} projection Data projection. + * @this {ol.VectorTile} + */ + function(features, projection) { + this.setProjection(projection); + this.setFeatures(features); + }); +}; + + /** * Create an XHR feature loader for a `url` and `format`. The feature loader * loads features (with XHR), parses the features, and adds them to the diff --git a/src/ol/layer/vectortilelayer.js b/src/ol/layer/vectortilelayer.js new file mode 100644 index 0000000000..a3330d414f --- /dev/null +++ b/src/ol/layer/vectortilelayer.js @@ -0,0 +1,99 @@ +goog.provide('ol.layer.VectorTile'); + +goog.require('goog.object'); +goog.require('ol.layer.Vector'); + + +/** + * @enum {string} + */ +ol.layer.VectorTileProperty = { + PRELOAD: 'preload', + USE_INTERIM_TILES_ON_ERROR: 'useInterimTilesOnError' +}; + + + +/** + * @classdesc + * Vector tile data that is rendered client-side. + * Note that any property set in the options is set as a {@link ol.Object} + * property on the layer object; for example, setting `title: 'My Title'` in the + * options means that `title` is observable, and has get/set accessors. + * + * @constructor + * @extends {ol.layer.Vector} + * @param {olx.layer.VectorTileOptions=} opt_options Options. + * @api + */ +ol.layer.VectorTile = function(opt_options) { + var options = goog.isDef(opt_options) ? opt_options : {}; + + var baseOptions = goog.object.clone(options); + + delete baseOptions.preload; + delete baseOptions.useInterimTilesOnError; + goog.base(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); + + this.setPreload(goog.isDef(options.preload) ? options.preload : 0); + this.setUseInterimTilesOnError(goog.isDef(options.useInterimTilesOnError) ? + options.useInterimTilesOnError : true); + +}; +goog.inherits(ol.layer.VectorTile, ol.layer.Vector); + + +/** + * Return the level as number to which we will preload tiles up to. + * @return {number} The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getPreload = function() { + return /** @type {number} */ (this.get(ol.layer.VectorTileProperty.PRELOAD)); +}; + + +/** + * Return the associated {@link ol.source.VectorTile source} of the layer. + * @function + * @return {ol.source.VectorTile} Source. + * @api + */ +ol.layer.VectorTile.prototype.getSource; + + +/** + * Whether we use interim tiles on error. + * @return {boolean} Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.getUseInterimTilesOnError = function() { + return /** @type {boolean} */ ( + this.get(ol.layer.VectorTileProperty.USE_INTERIM_TILES_ON_ERROR)); +}; + + +/** + * Set the level as number to which we will preload tiles up to. + * @param {number} preload The level to preload tiles up to. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setPreload = function(preload) { + this.set(ol.layer.TileProperty.PRELOAD, preload); +}; + + +/** + * Set whether we use interim tiles on error. + * @param {boolean} useInterimTilesOnError Use interim tiles on error. + * @observable + * @api + */ +ol.layer.VectorTile.prototype.setUseInterimTilesOnError = + function(useInterimTilesOnError) { + this.set( + ol.layer.TileProperty.USE_INTERIM_TILES_ON_ERROR, useInterimTilesOnError); +}; diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js new file mode 100644 index 0000000000..7fb8524400 --- /dev/null +++ b/src/ol/source/vectortilesource.js @@ -0,0 +1,107 @@ +goog.provide('ol.source.VectorTile'); + +goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('ol.TileState'); +goog.require('ol.VectorTile'); +goog.require('ol.featureloader'); +goog.require('ol.source.UrlTile'); + + + +/** + * @classdesc + * Base class for sources providing images divided into a tile grid. + * + * @constructor + * @fires ol.source.TileEvent + * @extends {ol.source.UrlTile} + * @param {olx.source.VectorTileOptions} options Vector tile options. + * @api + */ +ol.source.VectorTile = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + logo: options.logo, + opaque: options.opaque, + projection: options.projection, + state: goog.isDef(options.state) ? + /** @type {ol.source.State} */ (options.state) : undefined, + tileGrid: options.tileGrid, + tileLoadFunction: goog.isDef(options.tileLoadFunction) ? + options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction, + tileUrlFunction: options.tileUrlFunction, + tilePixelRatio: options.tilePixelRatio, + wrapX: options.wrapX + }); + + this.assumeRightHandedPolygons_ = + goog.isDef(options.assumeRightHandedPolygons) ? + options.assumeRightHandedPolygons : false; + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = goog.isDef(options.format) ? options.format : null; + + /** + * @protected + * @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string, + * ol.format.Feature, ol.TileLoadFunctionType)} + */ + this.tileClass = goog.isDef(options.tileClass) ? + options.tileClass : ol.VectorTile; + +}; +goog.inherits(ol.source.VectorTile, ol.source.UrlTile); + + +/** + * @return {boolean} Assume right handed polygons. + */ +ol.source.VectorTile.prototype.getRightHandedPolygons = function() { + return this.assumeRightHandedPolygons_; +}; + + +/** + * @inheritDoc + */ +ol.source.VectorTile.prototype.getTile = + function(z, x, y, pixelRatio, projection) { + var tileCoordKey = this.getKeyZXY(z, x, y); + if (this.tileCache.containsKey(tileCoordKey)) { + return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey)); + } else { + goog.asserts.assert(projection, 'argument projection is truthy'); + var tileCoord = [z, x, y]; + var urlTileCoord = this.getTileCoordForTileUrlFunction( + tileCoord, projection); + var tileUrl = goog.isNull(urlTileCoord) ? undefined : + this.tileUrlFunction(urlTileCoord, pixelRatio, projection); + var tile = new this.tileClass( + tileCoord, + goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY, + goog.isDef(tileUrl) ? tileUrl : '', + this.format_, + this.tileLoadFunction); + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange, false, this); + + this.tileCache.set(tileCoordKey, tile); + return tile; + } +}; + + +/** + * @param {ol.VectorTile} vectorTile Vector tile. + * @param {string} url URL. + */ +ol.source.VectorTile.defaultTileLoadFunction = function(vectorTile, url) { + vectorTile.setLoader(ol.featureloader.tile(url, vectorTile.getFormat())); +}; diff --git a/src/ol/tileloadfunction.js b/src/ol/tileloadfunction.js index e459a73eb8..37f3e0d8f5 100644 --- a/src/ol/tileloadfunction.js +++ b/src/ol/tileloadfunction.js @@ -3,10 +3,10 @@ goog.provide('ol.TileVectorLoadFunctionType'); /** - * A function that takes an {@link ol.ImageTile} for the image tile and a - * `{string}` for the src as arguments. + * A function that takes an {@link ol.Tile} for the tile and a + * `{string}` for the url as arguments. * - * @typedef {function(ol.ImageTile, string)} + * @typedef {function(ol.Tile, string)} * @api */ ol.TileLoadFunctionType; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js new file mode 100644 index 0000000000..551fa70788 --- /dev/null +++ b/src/ol/vectortile.js @@ -0,0 +1,173 @@ +goog.provide('ol.VectorTile'); + +goog.require('ol.Tile'); +goog.require('ol.TileCoord'); +goog.require('ol.TileLoadFunctionType'); +goog.require('ol.TileState'); + + +/** + * @typedef {{dirty: boolean, + * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number), + * renderedRevision: number, + * renderedResolution: number, + * replayGroup: ol.render.IReplayGroup}} + */ +ol.TileReplayState; + + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {ol.TileState} state State. + * @param {string} src Data source url. + * @param {ol.format.Feature} format Feature format. + * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. + */ +ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { + + goog.base(this, tileCoord, state); + + /** + * @private + * @type {ol.format.Feature} + */ + this.format_ = format; + + /** + * @private + * @type {Array.} + */ + this.features_ = null; + + /** + * @private + * @type {ol.FeatureLoader} + */ + this.loader_; + + /** + * @private + * @type {ol.proj.Projection} + */ + this.projection_ = null; + + /** + * @private + * @type {ol.TileReplayState} + */ + this.replayState_ = { + dirty: true, + renderedRenderOrder: null, + renderedRevision: -1, + renderedResolution: NaN, + replayGroup: null + }; + + /** + * @private + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction_ = tileLoadFunction; + + /** + * @private + * @type {string} + */ + this.url_ = src; + +}; +goog.inherits(ol.VectorTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.disposeInternal = function() { + goog.base(this, 'disposeInternal'); +}; + + +/** + * Get the feature format assigned for reading this tile's features. + * @return {ol.format.Feature} Feature format. + * @api + */ +ol.VectorTile.prototype.getFormat = function() { + return this.format_; +}; + + +/** + * @return {Array.} Features. + */ +ol.VectorTile.prototype.getFeatures = function() { + return this.features_; +}; + + +/** + * @return {ol.TileReplayState} + */ +ol.VectorTile.prototype.getReplayState = function() { + return this.replayState_; +}; + + +/** + * @inheritDoc + */ +ol.VectorTile.prototype.getKey = function() { + return this.url_; +}; + + +/** + * @return {ol.proj.Projection} Projection. + */ +ol.VectorTile.prototype.getProjection = function() { + return this.projection_; +}; + + +/** + * Load the tile. + */ +ol.VectorTile.prototype.load = function() { + if (this.state == ol.TileState.IDLE) { + this.state = ol.TileState.LOADING; + this.changed(); + this.tileLoadFunction_(this, this.url_); + this.loader_(null, NaN, null); + } +}; + + +/** + * @param {Array.} features Features. + */ +ol.VectorTile.prototype.setFeatures = function(features) { + this.features_ = features; + this.state = ol.TileState.LOADED; + this.changed(); +}; + + +/** + * @param {ol.proj.Projection} projection Projection. + */ +ol.VectorTile.prototype.setProjection = function(projection) { + this.projection_ = projection; +}; + + +/** + * Set the feeature loader for reading this tile's features. + * @param {ol.FeatureLoader} loader Feature loader. + * @api + */ +ol.VectorTile.prototype.setLoader = function(loader) { + this.loader_ = loader; +}; From 4b942bc4f642faa80336d11d08ee81bd2ccad4cd Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 13 Oct 2015 14:34:26 +0200 Subject: [PATCH 05/27] Add VectorTile renderer --- src/ol/ol.js | 8 + src/ol/render/canvas/canvasreplay.js | 32 +- src/ol/render/vector.js | 3 +- src/ol/renderer/canvas/canvasmaprenderer.js | 4 + .../canvas/canvasvectortilelayerrenderer.js | 445 ++++++++++++++++++ 5 files changed, 489 insertions(+), 3 deletions(-) create mode 100644 src/ol/renderer/canvas/canvasvectortilelayerrenderer.js 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; +}; From c0c54437821ed45783089eacf2daa39315d357c8 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 13 Oct 2015 14:35:02 +0200 Subject: [PATCH 06/27] Add example showing the use of Mapbox vector tiles --- examples/mapbox-vector-tiles.css | 3 + examples/mapbox-vector-tiles.html | 15 + examples/mapbox-vector-tiles.js | 66 ++++ examples/resources/mapbox-streets-v6-style.js | 308 ++++++++++++++++++ externs/example.js | 7 + 5 files changed, 399 insertions(+) create mode 100644 examples/mapbox-vector-tiles.css create mode 100644 examples/mapbox-vector-tiles.html create mode 100644 examples/mapbox-vector-tiles.js create mode 100644 examples/resources/mapbox-streets-v6-style.js diff --git a/examples/mapbox-vector-tiles.css b/examples/mapbox-vector-tiles.css new file mode 100644 index 0000000000..33e90f7301 --- /dev/null +++ b/examples/mapbox-vector-tiles.css @@ -0,0 +1,3 @@ +.map { + background: #f8f4f0; +} diff --git a/examples/mapbox-vector-tiles.html b/examples/mapbox-vector-tiles.html new file mode 100644 index 0000000000..ec0744b5f7 --- /dev/null +++ b/examples/mapbox-vector-tiles.html @@ -0,0 +1,15 @@ +--- +template: example.html +title: Mapbox vector tiles example +shortdesc: Example of a Mapbox vector tiles map. +docs: > + A simple vector tiles map. +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 new file mode 100644 index 0000000000..ad1a5d8816 --- /dev/null +++ b/examples/mapbox-vector-tiles.js @@ -0,0 +1,66 @@ +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'); + + +// Mapbox access token - request your own at http://mabobox.com +var accessToken = + 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; + +// For how many zoom levels do we want to use the same vector tile? +var reuseZoomLevels = 2; +// Offset from web mercator zoom level 0 +var zOffset = 1; + +var resolutions = []; +for (var z = zOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) { + resolutions.push(156543.03392804097 / Math.pow(2, z * reuseZoomLevels)); +} + +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' + })], + rightHandedPolygons: true, + format: new ol.format.MVT(), + tileGrid: new ol.tilegrid.TileGrid({ + extent: ol.proj.get('EPSG:3857').getExtent(), + resolutions: resolutions + }), + tilePixelRatio: 16, + tileUrlFunction: function(tileCoord) { + return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + + '{z}/{x}/{y}.vector.pbf?access_token=' + accessToken) + .replace('{z}', String(tileCoord[0] * reuseZoomLevels + zOffset)) + .replace('{x}', String(tileCoord[1])) + .replace('{y}', String(-tileCoord[2] - 1)) + .replace('{a-d}', 'abcd'.substr( + ((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1)); + } + }), + style: createMapboxStreetsV6Style() + }) + ], + target: 'map', + view: new ol.View({ + center: [1823849, 6143760], + minZoom: 1, + zoom: 3 + }) +}); diff --git a/examples/resources/mapbox-streets-v6-style.js b/examples/resources/mapbox-streets-v6-style.js new file mode 100644 index 0000000000..c7f6c4ad2f --- /dev/null +++ b/examples/resources/mapbox-streets-v6-style.js @@ -0,0 +1,308 @@ +function createMapboxStreetsV6Style() { + var fill = new ol.style.Fill({color: ''}); + var stroke = new ol.style.Stroke({color: '', width: 1}); + var polygon = new ol.style.Style({fill: fill}); + var strokedPolygon = new ol.style.Style({fill: fill, stroke: stroke}); + var line = new ol.style.Style({stroke: stroke}); + var text = new ol.style.Style({text: new ol.style.Text({ + text: '', fill: fill, stroke: stroke + })}); + var iconCache = {}; + function getIcon(iconName) { + var icon = iconCache[iconName]; + if (!icon) { + icon = new ol.style.Style({image: new ol.style.Icon({ + src: '//raw.githubusercontent.com/mapbox/maki/mb-pages/renders/' + + iconName + '-12.png' + })}); + iconCache[iconName] = icon; + } + return icon; + } + var styles = []; + return function(feature, resolution) { + var length = 0; + var layer = feature.get('layer'); + var cls = feature.get('class'); + var type = feature.get('type'); + var scalerank = feature.get('scalerank'); + var labelrank = feature.get('labelrank'); + var adminLevel = feature.get('admin_level'); + var maritime = feature.get('maritime'); + var disputed = feature.get('disputed'); + var maki = feature.get('maki'); + var geom = feature.getGeometry().getType(); + if (layer == 'landuse' && cls == 'park') { + fill.setColor('#d8e8c8'); + styles[length++] = polygon; + } else if (layer == 'landuse' && cls == 'cemetery') { + fill.setColor('#e0e4dd'); + styles[length++] = polygon; + } else if (layer == 'landuse' && cls == 'hospital') { + fill.setColor('#fde'); + styles[length++] = polygon; + } else if (layer == 'landuse' && cls == 'school') { + fill.setColor('#f0e8f8'); + styles[length++] = polygon; + } else if (layer == 'landuse' && cls == 'wood') { + fill.setColor('rgb(233,238,223)'); + styles[length++] = polygon; + } else if (layer == 'waterway' && + cls != 'river' && cls != 'stream' && cls != 'canal') { + stroke.setColor('#a0c8f0'); + stroke.setWidth(1.3); + styles[length++] = line; + } else if (layer == 'waterway' && cls == 'river') { + stroke.setColor('#a0c8f0'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'waterway' && (cls == 'stream' || + cls == 'canal')) { + stroke.setColor('#a0c8f0'); + stroke.setWidth(1.3); + styles[length++] = line; + } else if (layer == 'water') { + fill.setColor('#a0c8f0'); + styles[length++] = polygon; + } else if (layer == 'aeroway' && geom == 'Polygon') { + fill.setColor('rgb(242,239,235)'); + styles[length++] = polygon; + } else if (layer == 'aeroway' && geom == 'LineString' && + resolution <= 76.43702828517625) { + stroke.setColor('#f0ede9'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'building') { + fill.setColor('#f2eae2'); + stroke.setColor('#dfdbd7'); + stroke.setWidth(1); + styles[length++] = strokedPolygon; + } else if (layer == 'tunnel' && cls == 'motorway_link') { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && cls == 'service') { + stroke.setColor('#cfcdca'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && + (cls == 'street' || cls == 'street_limited')) { + stroke.setColor('#cfcdca'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && cls == 'main' && + resolution <= 1222.99245256282) { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && cls == 'motorway') { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && cls == 'path') { + stroke.setColor('#cba'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'tunnel' && cls == 'major_rail') { + stroke.setColor('#bbb'); + stroke.setWidth(1.4); + styles[length++] = line; + } else if (layer == 'road' && cls == 'motorway_link') { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'road' && (cls == 'street' || + cls == 'street_limited') && geom == 'LineString') { + stroke.setColor('#cfcdca'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'road' && cls == 'main' && + resolution <= 1222.99245256282) { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'road' && cls == 'motorway' && + resolution <= 4891.96981025128) { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'road' && cls == 'path') { + stroke.setColor('#cba'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'road' && cls == 'major_rail') { + stroke.setColor('#bbb'); + stroke.setWidth(1.4); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'motorway_link') { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'motorway') { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'service') { + stroke.setColor('#cfcdca'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && + (cls == 'street' || cls == 'street_limited')) { + stroke.setColor('#cfcdca'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'main' && + resolution <= 1222.99245256282) { + stroke.setColor('#e9ac77'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'path') { + stroke.setColor('#cba'); + stroke.setWidth(1.2); + styles[length++] = line; + } else if (layer == 'bridge' && cls == 'major_rail') { + stroke.setColor('#bbb'); + stroke.setWidth(1.4); + styles[length++] = line; + } else if (layer == 'admin' && adminLevel >= 3 && maritime === 0) { + stroke.setColor('#9e9cab'); + stroke.setWidth(1); + styles[length++] = line; + } else if (layer == 'admin' && adminLevel == 2 && + disputed === 0 && maritime === 0) { + stroke.setColor('#9e9cab'); + stroke.setWidth(1); + styles[length++] = line; + } else if (layer == 'admin' && adminLevel == 2 && + disputed === 1 && maritime === 0) { + stroke.setColor('#9e9cab'); + stroke.setWidth(1); + styles[length++] = line; + } else if (layer == 'admin' && adminLevel >= 3 && maritime === 1) { + stroke.setColor('#a0c8f0'); + stroke.setWidth(1); + styles[length++] = line; + } else if (layer == 'admin' && adminLevel == 2 && maritime === 1) { + stroke.setColor('#a0c8f0'); + stroke.setWidth(1); + styles[length++] = line; + } else if (layer == 'country_label' && scalerank === 1) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('bold 11px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#334'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(2); + styles[length++] = text; + } else if (layer == 'country_label' && scalerank === 2 && + resolution <= 19567.87924100512) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('bold 10px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#334'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(2); + styles[length++] = text; + } else if (layer == 'country_label' && scalerank === 3 && + resolution <= 9783.93962050256) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('bold 9px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#334'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(2); + styles[length++] = text; + } else if (layer == 'country_label' && scalerank === 4 && + resolution <= 4891.96981025128) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('bold 8px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#334'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(2); + styles[length++] = text; + } else if (layer == 'marine_label' && labelrank === 1 && + geom == 'Point') { + text.getText().setText(feature.get('name_en')); + text.getText().setFont( + 'italic 11px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#74aee9'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(0.75); + styles[length++] = text; + } else if (layer == 'marine_label' && labelrank === 2 && + geom == 'Point') { + text.getText().setText(feature.get('name_en')); + text.getText().setFont( + 'italic 11px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#74aee9'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(0.75); + styles[length++] = text; + } else if (layer == 'marine_label' && labelrank === 3 && + geom == 'Point') { + text.getText().setText(feature.get('name_en')); + text.getText().setFont( + 'italic 10px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#74aee9'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(0.75); + styles[length++] = text; + } else if (layer == 'marine_label' && labelrank === 4 && + geom == 'Point') { + text.getText().setText(feature.get('name_en')); + text.getText().setFont( + 'italic 9px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#74aee9'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(0.75); + styles[length++] = text; + } else if (layer == 'place_label' && type == 'city' && + resolution <= 1222.99245256282) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('11px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#333'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(1.2); + styles[length++] = text; + } else if (layer == 'place_label' && type == 'town' && + resolution <= 305.748113140705) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('9px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#333'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(1.2); + styles[length++] = text; + } else if (layer == 'place_label' && type == 'village' && + resolution <= 38.21851414258813) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('8px "Open Sans", "Arial Unicode MS"'); + fill.setColor('#333'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(1.2); + styles[length++] = text; + } else if (layer == 'place_label' && + resolution <= 19.109257071294063 && (type == 'hamlet' || + type == 'suburb' || type == 'neighbourhood')) { + text.getText().setText(feature.get('name_en')); + text.getText().setFont('bold 9px "Arial Narrow"'); + fill.setColor('#633'); + stroke.setColor('rgba(255,255,255,0.8)'); + stroke.setWidth(1.2); + styles[length++] = text; + } else if (layer == 'poi_label' && resolution <= 19.109257071294063 && + scalerank == 1 && maki !== 'marker') { + styles[length++] = getIcon(maki); + } else if (layer == 'poi_label' && resolution <= 9.554628535647032 && + scalerank == 2 && maki !== 'marker') { + styles[length++] = getIcon(maki); + } else if (layer == 'poi_label' && resolution <= 4.777314267823516 && + scalerank == 3 && maki !== 'marker') { + styles[length++] = getIcon(maki); + } else if (layer == 'poi_label' && resolution <= 2.388657133911758 && + scalerank == 4 && maki !== 'marker') { + styles[length++] = getIcon(maki); + } else if (layer == 'poi_label' && resolution <= 1.194328566955879 && + scalerank >= 5 && maki !== 'marker') { + styles[length++] = getIcon(maki); + } + styles.length = length; + return styles; + }; +} diff --git a/externs/example.js b/externs/example.js index 5f114e6387..5fcec4903a 100644 --- a/externs/example.js +++ b/externs/example.js @@ -9,3 +9,10 @@ var common; * @return {string} Renderer type. */ common.getRendererFromQueryString = function() {}; + + +/** + * @return {function((ol.Feature|ol.render.Feature), number): + * Array.} + */ +var createMapboxStreetsV6Style = function() {}; From dbedbc19ee74a73724e2734189d2994b77439f6d Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 11 Sep 2015 18:31:47 +0200 Subject: [PATCH 07/27] Create replay group for the tile's native resolution only --- .../canvas/canvasvectortilelayerrenderer.js | 49 +++++++------------ src/ol/vectortile.js | 5 +- 2 files changed, 20 insertions(+), 34 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 169641285b..76987cf96c 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -7,6 +7,7 @@ goog.require('goog.object'); goog.require('goog.vec.Mat4'); goog.require('ol.TileRange'); goog.require('ol.TileState'); +goog.require('ol.VectorTile'); goog.require('ol.ViewHint'); goog.require('ol.dom'); goog.require('ol.extent'); @@ -155,12 +156,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = /** * @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) { + layer, pixelRatio) { var revision = layer.getRevision(); var renderOrder = layer.getRenderOrder(); if (!goog.isDef(renderOrder)) { @@ -168,33 +167,30 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } var replayState = tile.getReplayState(); - if (!replayState.dirty && - replayState.renderedResolution == resolution && - replayState.renderedRevision == revision && + if (replayState.renderedRevision == revision && replayState.renderedRenderOrder == renderOrder) { - return false; + return; } // 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 tileCoord = tile.getTileCoord(); + var resolution = tileGrid.getTileCoordResolution(tileCoord); var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; var extent = pixelSpace ? - [0, 0].concat(source.getTilePixelSize( - tileGrid.getZForResolution(resolution), pixelRatio, + [0, 0].concat(source.getTilePixelSize(tileCoord[0], pixelRatio, tile.getProjection())) : - tileGrid.getTileCoordExtent(tile.getTileCoord()); + tileGrid.getTileCoordExtent(tileCoord); 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()); + tileResolution, layer.getRenderBuffer(), source.getRightHandedPolygons()); /** * @param {ol.Feature} feature Feature. @@ -212,8 +208,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, if (goog.isDefAndNotNull(styles)) { var dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup); - replayState.dirty = replayState.dirty || dirty; - this.dirty_ = this.dirty_ || replayState.dirty; + this.dirty_ = this.dirty_ || dirty; } } @@ -226,12 +221,9 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, replayGroup.finish(); - replayState.renderedResolution = resolution; replayState.renderedRevision = revision; replayState.renderedRenderOrder = renderOrder; replayState.replayGroup = replayGroup; - - return true; }; @@ -248,13 +240,14 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = var features = {}; var replayables = this.renderedTiles_; + var source = layer.getSource(); + var tileGrid = source.getTileGrid(); var found, tileSpaceCoordinate; - var i, ii, origin, replayGroup, source; + var i, ii, origin, replayGroup; 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); @@ -265,9 +258,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = origin = ol.extent.getTopLeft( source.getTileGrid().getTileCoordExtent(tileCoord)); tilePixelRatio = source.getTilePixelRatio(); - tileResolution = resolution / tilePixelRatio; - tileSize = ol.size.toSize( - source.getTileGrid().getTileSize(tileCoord[0])); + tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; + tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); tileSpaceCoordinate = [ (coordinate[0] - origin[0]) / tileResolution, (origin[1] - coordinate[1]) / tileResolution @@ -358,7 +350,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = this.scheduleExpireCache(frameState, source); /** - * @type {Object.>} + * @type {Object.>} */ var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; @@ -374,6 +366,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = 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 || @@ -407,16 +401,11 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = tile = tilesToDraw[tileCoordKey]; if (tile.getState() == ol.TileState.LOADED) { replayables.push(tile); + this.createReplayGroup(tile, layer, pixelRatio); } } } - 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; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index 551fa70788..b89b68b527 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -7,10 +7,9 @@ goog.require('ol.TileState'); /** - * @typedef {{dirty: boolean, + * @typedef {{ * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number), * renderedRevision: number, - * renderedResolution: number, * replayGroup: ol.render.IReplayGroup}} */ ol.TileReplayState; @@ -59,10 +58,8 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { * @type {ol.TileReplayState} */ this.replayState_ = { - dirty: true, renderedRenderOrder: null, renderedRevision: -1, - renderedResolution: NaN, replayGroup: null }; From 63629e1ee2bc906c87ad7f9e24b2e201d609e707 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sat, 12 Sep 2015 20:02:14 +0900 Subject: [PATCH 08/27] Add tile error handling --- src/ol/featureloader.js | 19 ++++++++++++++----- src/ol/vectortile.js | 15 +++++++++++---- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index d53f5ddf2b..cf7a450959 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -46,12 +46,15 @@ ol.FeatureUrlFunction; /** * @param {string|ol.FeatureUrlFunction} url Feature URL service. * @param {ol.format.Feature} format Feature format. - * @param {function(this:ol.source.Vector, Array., ol.proj.Projection)|function(this:ol.source.Vector, Array.)} success + * @param {function(this:ol.VectorTile, Array., ol.proj.Projection)|function(this:ol.source.Vector, Array.)} success * Function called with the loaded features and optionally with the data - * projection. Called with the vector source as `this`. + * projection. Called with the vector tile or source as `this`. + * @param {function(this:ol.source.Vector)} failure + * Function called when loading failed. Called with the vector tile or + * source as `this`. * @return {ol.FeatureLoader} The feature loader. */ -ol.featureloader.loadFeaturesXhr = function(url, format, success) { +ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) { return ( /** * @param {ol.Extent} extent Extent. @@ -107,7 +110,7 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success) { goog.asserts.fail('undefined or null source'); } } else { - // FIXME + failure.call(this); } goog.dispose(xhrIo); }, false, this); @@ -140,6 +143,12 @@ ol.featureloader.tile = function(url, format) { function(features, projection) { this.setProjection(projection); this.setFeatures(features); + }, + /** + * @this {ol.VectorTile} + */ + function() { + this.setState(ol.TileState.ERROR); }); }; @@ -161,5 +170,5 @@ ol.featureloader.xhr = function(url, format) { */ function(features) { this.addFeatures(features); - }); + }, goog.nullFunction); }; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index b89b68b527..63810788e9 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -134,8 +134,7 @@ ol.VectorTile.prototype.getProjection = function() { */ ol.VectorTile.prototype.load = function() { if (this.state == ol.TileState.IDLE) { - this.state = ol.TileState.LOADING; - this.changed(); + this.setState(ol.TileState.LOADING); this.tileLoadFunction_(this, this.url_); this.loader_(null, NaN, null); } @@ -147,8 +146,7 @@ ol.VectorTile.prototype.load = function() { */ ol.VectorTile.prototype.setFeatures = function(features) { this.features_ = features; - this.state = ol.TileState.LOADED; - this.changed(); + this.setState(ol.TileState.LOADED); }; @@ -160,6 +158,15 @@ ol.VectorTile.prototype.setProjection = function(projection) { }; +/** + * @param {ol.TileState} tileState Tile state. + */ +ol.VectorTile.prototype.setState = function(tileState) { + this.state = tileState; + this.changed(); +}; + + /** * Set the feeature loader for reading this tile's features. * @param {ol.FeatureLoader} loader Feature loader. From 6e2f82c39701d3c05c6bd54e20864269e3aed555 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 13 Sep 2015 22:14:46 +0900 Subject: [PATCH 09/27] Introduce ol.render.Feature --- externs/olx.js | 19 ++- src/ol/featureloader.js | 3 +- src/ol/format/mvtformat.js | 75 ++++++++- src/ol/interaction/selectinteraction.js | 12 +- src/ol/map.js | 8 +- src/ol/render/canvas/canvasimmediate.js | 27 +-- src/ol/render/canvas/canvasreplay.js | 26 +-- src/ol/render/renderfeature.js | 156 ++++++++++++++++++ src/ol/render/vector.js | 52 +++--- src/ol/render/vectorcontext.js | 27 +-- src/ol/render/webgl/webglimmediate.js | 4 +- .../canvas/canvasimagelayerrenderer.js | 2 +- .../canvas/canvasvectorlayerrenderer.js | 2 +- .../canvas/canvasvectortilelayerrenderer.js | 8 +- src/ol/renderer/dom/domimagelayerrenderer.js | 2 +- src/ol/renderer/dom/domvectorlayerrenderer.js | 2 +- src/ol/renderer/layerrenderer.js | 4 +- src/ol/renderer/maprenderer.js | 6 +- .../renderer/webgl/webglimagelayerrenderer.js | 2 +- src/ol/source/imagevectorsource.js | 2 +- src/ol/source/source.js | 3 +- src/ol/source/tilesource.js | 3 +- src/ol/source/tilevectorsource.js | 3 +- src/ol/source/urltilesource.js | 2 +- src/ol/source/vectortilesource.js | 13 +- src/ol/style/style.js | 13 +- test/spec/ol/format/mvtformat.test.js | 12 +- 27 files changed, 380 insertions(+), 108 deletions(-) create mode 100644 src/ol/render/renderfeature.js diff --git a/externs/olx.js b/externs/olx.js index fa5febf9b1..30bf226baa 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -1686,7 +1686,11 @@ olx.format.EsriJSONOptions.prototype.geometryName; /** - * @typedef {{geometryName: (string|undefined), + * @typedef {{featureClass: (function((ol.geom.Geometry|Object.)=)| + * function(ol.geom.GeometryType,Array., + * (Array.|Array.>),Object.)| + * undefined), + * geometryName: (string|undefined), * layers: (Array.|undefined), * layerName: (string|undefined)}} * @api @@ -1694,6 +1698,19 @@ olx.format.EsriJSONOptions.prototype.geometryName; olx.format.MVTOptions; +/** + * Class for features returned by {@link ol.format.MVT#readFeatures}. Set to + * {@link ol.Feature} to get full editing and geometry support at the cost of + * decreased rendering performance. The default is {@link ol.render.Feature}, + * which is optimized for rendering and hit detection. + * @type {undefined|function((ol.geom.Geometry|Object.)=)| + * function(ol.geom.GeometryType,Array., + * (Array.|Array.>),Object.)} + * @api + */ +olx.format.MVTOptions.prototype.featureClass; + + /** * Geometry name to use when creating features. Default is 'geometry'. * @type {string|undefined} diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index cf7a450959..7c6d1c8582 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -7,6 +7,7 @@ goog.require('goog.events'); goog.require('goog.net.EventType'); goog.require('goog.net.XhrIo'); goog.require('goog.net.XhrIo.ResponseType'); +goog.require('ol.TileState'); goog.require('ol.format.FormatType'); goog.require('ol.xml'); @@ -49,7 +50,7 @@ ol.FeatureUrlFunction; * @param {function(this:ol.VectorTile, Array., ol.proj.Projection)|function(this:ol.source.Vector, Array.)} success * Function called with the loaded features and optionally with the data * projection. Called with the vector tile or source as `this`. - * @param {function(this:ol.source.Vector)} failure + * @param {function(this:ol.VectorTile)|function(this:ol.source.Vector)} failure * Function called when loading failed. Called with the vector tile or * source as `this`. * @return {ol.FeatureLoader} The feature loader. diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js index 97936f1585..0b8a07acd2 100644 --- a/src/ol/format/mvtformat.js +++ b/src/ol/format/mvtformat.js @@ -11,6 +11,7 @@ goog.require('ol.format.Feature'); goog.require('ol.format.FormatType'); goog.require('ol.geom.Geometry'); goog.require('ol.geom.GeometryLayout'); +goog.require('ol.geom.GeometryType'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); goog.require('ol.geom.MultiPoint'); @@ -19,6 +20,7 @@ goog.require('ol.geom.Polygon'); goog.require('ol.proj'); goog.require('ol.proj.Projection'); goog.require('ol.proj.Units'); +goog.require('ol.render.Feature'); @@ -45,6 +47,15 @@ ol.format.MVT = function(opt_options) { units: ol.proj.Units.TILE_PIXELS }); + /** + * @private + * @type {function((ol.geom.Geometry|Object.)=)| + * function(ol.geom.GeometryType,Array., + * (Array.|Array.>),Object.)} + */ + this.featureClass_ = goog.isDef(options.featureClass) ? + options.featureClass : ol.render.Feature; + /** * @private * @type {string} @@ -79,12 +90,15 @@ ol.format.MVT.prototype.getType = function() { /** * @private * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. * @param {olx.format.ReadOptions=} opt_options Read options. * @return {ol.Feature} Feature. */ -ol.format.MVT.prototype.readFeature_ = function(rawFeature, opt_options) { - var feature = new ol.Feature(); +ol.format.MVT.prototype.readFeature_ = function( + rawFeature, layer, opt_options) { + var feature = new this.featureClass_(); var values = rawFeature.properties; + values[this.layerName_] = layer; var geometry = ol.format.Feature.transformWithOptions( ol.format.MVT.readGeometry_(rawFeature), false, this.adaptOptions(opt_options)); @@ -98,18 +112,66 @@ ol.format.MVT.prototype.readFeature_ = function(rawFeature, opt_options) { }; +/** + * @private + * @param {Object} rawFeature Raw Mapbox feature. + * @param {string} layer Layer. + * @return {ol.render.Feature} Feature. + */ +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); + } + + var type = rawFeature.type; + /** @type {ol.geom.GeometryType} */ + var geometryType; + if (type === 1) { + geometryType = coords.length === 1 ? + ol.geom.GeometryType.POINT : ol.geom.GeometryType.MULTI_POINT; + } else if (type === 2) { + if (coords.length === 1) { + geometryType = ol.geom.GeometryType.LINE_STRING; + } else { + geometryType = ol.geom.GeometryType.MULTI_LINE_STRING; + } + } else { + geometryType = ol.geom.GeometryType.POLYGON; + } + + var properties = rawFeature.properties; + properties[this.layerName_] = layer; + + return new this.featureClass_( + geometryType, flatCoordinates, ends, rawFeature.properties); +}; + + /** * @inheritDoc */ ol.format.MVT.prototype.readFeatures = function(source, opt_options) { goog.asserts.assertInstanceof(source, ArrayBuffer); - var layerName = this.layerName_; var layers = this.layers_; var pbf = new ol.ext.pbf(source); var tile = new ol.ext.vectortile.VectorTile(pbf); var features = []; + var featureClass = this.featureClass_; var layer, feature; for (var name in tile.layers) { if (!goog.isNull(layers) && !goog.array.contains(layers, name)) { @@ -118,8 +180,11 @@ ol.format.MVT.prototype.readFeatures = function(source, opt_options) { layer = tile.layers[name]; for (var i = 0, ii = layer.length; i < layer.length; ++i) { - feature = this.readFeature_(layer.feature(i), opt_options); - feature.set(layerName, name); + if (featureClass === ol.render.Feature) { + feature = this.readRenderFeature_(layer.feature(i), name); + } else { + feature = this.readFeature_(layer.feature(i), name, opt_options); + } features.push(feature); } } diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index 447c18634e..d527dcc248 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -33,9 +33,11 @@ ol.interaction.SelectEventType = { /** - * A function that takes an {@link ol.Feature} and an {@link ol.layer.Layer} - * and returns `true` if the feature may be selected or `false` otherwise. - * @typedef {function(ol.Feature, ol.layer.Layer): boolean} + * A function that takes an {@link ol.Feature} or {@link ol.render.Feature} and + * an {@link ol.layer.Layer} and returns `true` if the feature may be selected + * or `false` otherwise. + * @typedef {function((ol.Feature|ol.render.Feature), ol.layer.Layer): + * boolean} * @api */ ol.interaction.SelectFilterFunction; @@ -273,7 +275,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { // the pixel. map.forEachFeatureAtPixel(mapBrowserEvent.pixel, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.layer.Layer} layer Layer. */ function(feature, layer) { @@ -308,7 +310,7 @@ ol.interaction.Select.handleEvent = function(mapBrowserEvent) { // Modify the currently selected feature(s). map.forEachFeatureAtPixel(mapBrowserEvent.pixel, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.layer.Layer} layer Layer. */ function(feature, layer) { diff --git a/src/ol/map.js b/src/ol/map.js index 15fda71a25..91f02c0481 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -594,9 +594,11 @@ ol.Map.prototype.disposeInternal = function() { * callback with each intersecting feature. Layers included in the detection can * be configured through `opt_layerFilter`. * @param {ol.Pixel} pixel Pixel. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. The callback will be called with two arguments. The first - * argument is one {@link ol.Feature feature} at the pixel, the second is + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. The callback will be + * called with two arguments. The first argument is one + * {@link ol.Feature feature} or + * {@link ol.render.Feature render feature} at the pixel, the second is * the {@link ol.layer.Layer layer} of the feature. To stop detection, * callback functions can return a truthy value. * @param {S=} opt_this Value to use as `this` when executing `callback`. diff --git a/src/ol/render/canvas/canvasimmediate.js b/src/ol/render/canvas/canvasimmediate.js index 2d90610ffe..710c49b416 100644 --- a/src/ol/render/canvas/canvasimmediate.js +++ b/src/ol/render/canvas/canvasimmediate.js @@ -527,8 +527,8 @@ ol.render.canvas.Immediate.prototype.drawGeometryCollectionGeometry = * Render a Point geometry into the canvas. Rendering is immediate and uses * the current style. * - * @param {ol.geom.Point} pointGeometry Point geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @api */ ol.render.canvas.Immediate.prototype.drawPointGeometry = @@ -548,8 +548,9 @@ ol.render.canvas.Immediate.prototype.drawPointGeometry = * Render a MultiPoint geometry into the canvas. Rendering is immediate and * uses the current style. * - * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @api */ ol.render.canvas.Immediate.prototype.drawMultiPointGeometry = @@ -569,8 +570,9 @@ ol.render.canvas.Immediate.prototype.drawMultiPointGeometry = * Render a LineString into the canvas. Rendering is immediate and uses * the current style. * - * @param {ol.geom.LineString} lineStringGeometry Line string geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line + * string geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @api */ ol.render.canvas.Immediate.prototype.drawLineStringGeometry = @@ -598,9 +600,9 @@ ol.render.canvas.Immediate.prototype.drawLineStringGeometry = * Render a MultiLineString geometry into the canvas. Rendering is immediate * and uses the current style. * - * @param {ol.geom.MultiLineString} multiLineStringGeometry + * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry * MultiLineString geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @api */ ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry = @@ -635,8 +637,9 @@ ol.render.canvas.Immediate.prototype.drawMultiLineStringGeometry = * Render a Polygon geometry into the canvas. Rendering is immediate and uses * the current style. * - * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @api */ ol.render.canvas.Immediate.prototype.drawPolygonGeometry = @@ -1002,8 +1005,8 @@ ol.render.canvas.Immediate.prototype.setTextStyle = function(textStyle) { * @const * @private * @type {Object.} + * function(this: ol.render.canvas.Immediate, + * (ol.geom.Geometry|ol.render.Feature), Object)>} */ ol.render.canvas.Immediate.GEOMETRY_RENDERERS_ = { 'Point': ol.render.canvas.Immediate.prototype.drawPointGeometry, diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 0e2d7189a9..7b772043c1 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -202,8 +202,8 @@ ol.render.canvas.Replay.prototype.appendFlatCoordinates = /** * @protected - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { this.beginGeometryInstruction1_ = @@ -224,7 +224,8 @@ ol.render.canvas.Replay.prototype.beginGeometry = function(geometry, feature) { * @param {Object.} skippedFeaturesHash Ids of features * to skip. * @param {Array.<*>} instructions Instructions array. - * @param {function(ol.Feature): T|undefined} featureCallback Feature callback. + * @param {function((ol.Feature|ol.render.Feature)): T|undefined} + * featureCallback Feature callback. * @param {ol.Extent=} opt_hitExtent Only check features that intersect this * extent. * @return {T|undefined} Callback result. @@ -256,7 +257,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var feature, fill, stroke, text, x, y; switch (type) { case ol.render.canvas.Instruction.BEGIN_GEOMETRY: - feature = /** @type {ol.Feature} */ (instruction[1]); + feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); var featureUid = goog.getUid(feature).toString(); if (skippedFeaturesHash[featureUid] !== undefined || !feature.getGeometry()) { @@ -409,7 +410,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; case ol.render.canvas.Instruction.END_GEOMETRY: if (featureCallback !== undefined) { - feature = /** @type {ol.Feature} */ (instruction[1]); + feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); var result = featureCallback(feature); if (result) { return result; @@ -517,7 +518,8 @@ ol.render.canvas.Replay.prototype.replay = function( * @param {number} viewRotation View rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. - * @param {function(ol.Feature): T=} opt_featureCallback Feature callback. + * @param {function((ol.Feature|ol.render.Feature)): T=} opt_featureCallback + * Feature callback. * @param {ol.Extent=} opt_hitExtent Only check features that intersect this * extent. * @return {T|undefined} Callback result. @@ -564,8 +566,8 @@ ol.render.canvas.Replay.prototype.reverseHitDetectionInstructions_ = /** - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.canvas.Replay.prototype.endGeometry = function(geometry, feature) { goog.asserts.assert(this.beginGeometryInstruction1_, @@ -1889,7 +1891,8 @@ ol.render.canvas.ReplayGroup.prototype.finish = function() { * @param {number} rotation Rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. - * @param {function(ol.Feature): T} callback Feature callback. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. * @return {T|undefined} Callback result. * @template T */ @@ -1917,7 +1920,7 @@ ol.render.canvas.ReplayGroup.prototype.forEachFeatureAtCoordinate = function( return this.replayHitDetection_(context, transform, rotation, skippedFeaturesHash, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { @@ -2027,7 +2030,8 @@ ol.render.canvas.ReplayGroup.prototype.replay = function( * @param {number} viewRotation View rotation. * @param {Object.} skippedFeaturesHash Ids of features * to skip. - * @param {function(ol.Feature): T} featureCallback Feature callback. + * @param {function((ol.Feature|ol.render.Feature)): T} featureCallback + * Feature callback. * @param {ol.Extent=} opt_hitExtent Only check features that intersect this * extent. * @return {T|undefined} Callback result. diff --git a/src/ol/render/renderfeature.js b/src/ol/render/renderfeature.js new file mode 100644 index 0000000000..21bc706cd9 --- /dev/null +++ b/src/ol/render/renderfeature.js @@ -0,0 +1,156 @@ +goog.provide('ol.render.Feature'); + + +goog.require('goog.asserts'); +goog.require('goog.functions'); +goog.require('ol.extent'); +goog.require('ol.geom.GeometryType'); + + + +/** + * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like + * structure, optimized for rendering and styling. Geometry acces through the + * API is limited to getting the type and extent of the geometry. + * + * @constructor + * @param {ol.geom.GeometryType} type Geometry type. + * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.|Array.>} ends Ends or Endss. + * @param {Object.} properties Properties. + */ +ol.render.Feature = function(type, flatCoordinates, ends, properties) { + + /** + * @private + * @type {ol.Extent|undefined} + */ + this.extent_; + + goog.asserts.assert(type === ol.geom.GeometryType.POINT || + type === ol.geom.GeometryType.MULTI_POINT || + type === ol.geom.GeometryType.LINE_STRING || + type === ol.geom.GeometryType.MULTI_LINE_STRING || + type === ol.geom.GeometryType.POLYGON, + 'Need a Point, MultiPoint, LineString, MultiLineString or Polygon type'); + + /** + * @private + * @type {ol.geom.GeometryType} + */ + this.type_ = type; + + /** + * @private + * @type {Array.} + */ + this.flatCoordinates_ = flatCoordinates; + + /** + * @private + * @type {Array.|Array.>} + */ + this.ends_ = ends; + + /** + * @private + * @type {Object.} + */ + this.properties_ = properties; + +}; + + +/** + * Get a feature property by its key. + * @param {string} key Key + * @return {*} Value for the requested key. + * @api + */ +ol.render.Feature.prototype.get = function(key) { + return this.properties_[key]; +}; + + +/** + * @return {Array.|Array.>} Ends or endss. + */ +ol.render.Feature.prototype.getEnds = function() { + return this.ends_; +}; + + +/** + * Get the extent of this feature's geometry. + * @return {ol.Extent} Extent. + * @api + */ +ol.render.Feature.prototype.getExtent = function() { + if (!goog.isDef(this.extent_)) { + this.extent_ = this.type_ === ol.geom.GeometryType.POINT ? + ol.extent.createOrUpdateFromCoordinate(this.flatCoordinates_) : + ol.extent.createOrUpdateFromFlatCoordinates( + this.flatCoordinates_, 0, this.flatCoordinates_.length, 2); + + } + return this.extent_; +}; + + +/** + * @return {Array.} Flat coordinates. + */ +ol.render.Feature.prototype.getFlatCoordinates = + ol.render.Feature.prototype.getOrientedFlatCoordinates = function() { + return this.flatCoordinates_; +}; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + * @api + */ +ol.render.Feature.prototype.getGeometry = function() { + return this; +}; + + +/** + * Get the feature properties. + * @return {Object.} Feature properties. + * @api + */ +ol.render.Feature.prototype.getProperties = function() { + return this.properties_; +}; + + +/** + * Get the feature for working with its geometry. + * @return {ol.render.Feature} Feature. + */ +ol.render.Feature.prototype.getSimplifiedGeometry = + ol.render.Feature.prototype.getGeometry; + + +/** + * @return {number} Stride. + */ +ol.render.Feature.prototype.getStride = goog.functions.constant(2); + + +/** + * @return {undefined} + */ +ol.render.Feature.prototype.getStyleFunction = goog.nullFunction; + + +/** + * Get the type of this feature's geometry. + * @return {ol.geom.GeometryType} Geometry type. + * @api + */ +ol.render.Feature.prototype.getType = function() { + return this.type_; +}; diff --git a/src/ol/render/vector.js b/src/ol/render/vector.js index 5c91a013d9..7543740713 100644 --- a/src/ol/render/vector.js +++ b/src/ol/render/vector.js @@ -2,6 +2,7 @@ goog.provide('ol.renderer.vector'); goog.require('goog.asserts'); goog.require('ol.geom.Circle'); +goog.require('ol.geom.Geometry'); goog.require('ol.geom.GeometryCollection'); goog.require('ol.geom.LineString'); goog.require('ol.geom.MultiLineString'); @@ -9,14 +10,15 @@ goog.require('ol.geom.MultiPoint'); goog.require('ol.geom.MultiPolygon'); goog.require('ol.geom.Point'); goog.require('ol.geom.Polygon'); +goog.require('ol.render.Feature'); goog.require('ol.render.IReplayGroup'); goog.require('ol.style.ImageState'); goog.require('ol.style.Style'); /** - * @param {ol.Feature} feature1 Feature 1. - * @param {ol.Feature} feature2 Feature 2. + * @param {ol.Feature|ol.render.Feature} feature1 Feature 1. + * @param {ol.Feature|ol.render.Feature} feature2 Feature 2. * @return {number} Order. */ ol.renderer.vector.defaultOrder = function(feature1, feature2) { @@ -76,7 +78,7 @@ ol.renderer.vector.renderCircleGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.style.Style} style Style. * @param {number} squaredTolerance Squared tolerance. * @param {function(this: T, goog.events.Event)} listener Listener function. @@ -113,7 +115,7 @@ ol.renderer.vector.renderFeature = function( /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.style.Style} style Style. * @param {number} squaredTolerance Squared tolerance. * @private @@ -160,13 +162,15 @@ ol.renderer.vector.renderGeometryCollectionGeometry_ = * @param {ol.render.IReplayGroup} replayGroup Replay group. * @param {ol.geom.Geometry} geometry Geometry. * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString, - 'geometry should be an ol.geom.LineString'); + if (geometry instanceof ol.geom.Geometry) { + goog.asserts.assertInstanceof(geometry, ol.geom.LineString, + 'geometry should be an ol.geom.LineString'); + } var strokeStyle = style.getStroke(); if (strokeStyle) { var lineStringReplay = replayGroup.getReplay( @@ -188,13 +192,15 @@ ol.renderer.vector.renderLineStringGeometry_ = * @param {ol.render.IReplayGroup} replayGroup Replay group. * @param {ol.geom.Geometry} geometry Geometry. * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, - 'geometry should be an ol.geom.MultiLineString'); + if (geometry instanceof ol.geom.Geometry) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, + 'geometry should be an ol.geom.MultiLineString'); + } var strokeStyle = style.getStroke(); if (strokeStyle) { var lineStringReplay = replayGroup.getReplay( @@ -249,13 +255,15 @@ ol.renderer.vector.renderMultiPolygonGeometry_ = * @param {ol.render.IReplayGroup} replayGroup Replay group. * @param {ol.geom.Geometry} geometry Geometry. * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point, - 'geometry should be an ol.geom.Point'); + if (geometry instanceof ol.geom.Geometry) { + goog.asserts.assertInstanceof(geometry, ol.geom.Point, + 'geometry should be an ol.geom.Point'); + } var imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { @@ -281,13 +289,15 @@ ol.renderer.vector.renderPointGeometry_ = * @param {ol.render.IReplayGroup} replayGroup Replay group. * @param {ol.geom.Geometry} geometry Geometry. * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, - 'geometry should be an ol.goem.MultiPoint'); + if (geometry instanceof ol.geom.Geometry) { + goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, + 'geometry should be an ol.goem.MultiPoint'); + } var imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { @@ -312,15 +322,17 @@ ol.renderer.vector.renderMultiPointGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, - 'geometry should be an ol.geom.Polygon'); + if (geometry instanceof ol.geom.Geometry) { + goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, + 'geometry should be an ol.geom.Polygon or ol.render.Feature'); + } var fillStyle = style.getFill(); var strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) { diff --git a/src/ol/render/vectorcontext.js b/src/ol/render/vectorcontext.js index 5345a592a0..f643c56d5a 100644 --- a/src/ol/render/vectorcontext.js +++ b/src/ol/render/vectorcontext.js @@ -44,25 +44,27 @@ ol.render.VectorContext.prototype.drawGeometryCollectionGeometry = /** - * @param {ol.geom.LineString} lineStringGeometry Line string geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.LineString|ol.render.Feature} lineStringGeometry Line + * string geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawLineStringGeometry = goog.abstractMethod; /** - * @param {ol.geom.MultiLineString} multiLineStringGeometry + * @param {ol.geom.MultiLineString|ol.render.Feature} multiLineStringGeometry * MultiLineString geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawMultiLineStringGeometry = goog.abstractMethod; /** - * @param {ol.geom.MultiPoint} multiPointGeometry MultiPoint geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.MultiPoint|ol.render.Feature} multiPointGeometry MultiPoint + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawMultiPointGeometry = goog.abstractMethod; @@ -76,15 +78,16 @@ ol.render.VectorContext.prototype.drawMultiPolygonGeometry = /** - * @param {ol.geom.Point} pointGeometry Point geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Point|ol.render.Feature} pointGeometry Point geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawPointGeometry = goog.abstractMethod; /** - * @param {ol.geom.Polygon} polygonGeometry Polygon geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Polygon|ol.render.Feature} polygonGeometry Polygon + * geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawPolygonGeometry = goog.abstractMethod; @@ -94,8 +97,8 @@ ol.render.VectorContext.prototype.drawPolygonGeometry = goog.abstractMethod; * @param {number} offset Offset. * @param {number} end End. * @param {number} stride Stride. - * @param {ol.geom.Geometry} geometry Geometry. - * @param {ol.Feature} feature Feature. + * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.Feature|ol.render.Feature} feature Feature. */ ol.render.VectorContext.prototype.drawText = goog.abstractMethod; diff --git a/src/ol/render/webgl/webglimmediate.js b/src/ol/render/webgl/webglimmediate.js index a0f45b0af0..1981e67ef2 100644 --- a/src/ol/render/webgl/webglimmediate.js +++ b/src/ol/render/webgl/webglimmediate.js @@ -294,8 +294,8 @@ ol.render.webgl.Immediate.prototype.setTextStyle = function(textStyle) { * @const * @private * @type {Object.} + * function(this: ol.render.webgl.Immediate, + * (ol.geom.Geometry|ol.render.Feature), Object)>} */ ol.render.webgl.Immediate.GEOMETRY_RENDERERS_ = { 'Point': ol.render.webgl.Immediate.prototype.drawPointGeometry, diff --git a/src/ol/renderer/canvas/canvasimagelayerrenderer.js b/src/ol/renderer/canvas/canvasimagelayerrenderer.js index 22fae30287..9346fbc8f2 100644 --- a/src/ol/renderer/canvas/canvasimagelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasimagelayerrenderer.js @@ -65,7 +65,7 @@ ol.renderer.canvas.ImageLayer.prototype.forEachFeatureAtCoordinate = return source.forEachFeatureAtCoordinate( coordinate, resolution, rotation, skippedFeatureUids, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index d0c96994cc..5784df90af 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -168,7 +168,7 @@ ol.renderer.canvas.VectorLayer.prototype.forEachFeatureAtCoordinate = return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, rotation, layerState.managed ? frameState.skippedFeatureUids : {}, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 76987cf96c..666834497f 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -5,6 +5,7 @@ goog.require('goog.asserts'); goog.require('goog.events'); goog.require('goog.object'); goog.require('goog.vec.Mat4'); +goog.require('ol.Feature'); goog.require('ol.TileRange'); goog.require('ol.TileState'); goog.require('ol.VectorTile'); @@ -193,12 +194,13 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, tileResolution, layer.getRenderBuffer(), source.getRightHandedPolygons()); /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @this {ol.renderer.canvas.VectorTileLayer} */ function renderFeature(feature) { var styles; if (goog.isDef(feature.getStyleFunction())) { + goog.asserts.assertInstanceof(feature, ol.Feature, 'Got an ol.Feature'); styles = feature.getStyleFunction().call(feature, resolution); } else if (goog.isDef(layer.getStyleFunction())) { styles = layer.getStyleFunction()(feature, resolution); @@ -273,7 +275,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = tileSpaceCoordinate, resolution, rotation, layerState.managed ? frameState.skippedFeatureUids : {}, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { @@ -413,7 +415,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {number} squaredTolerance Squared tolerance. * @param {Array.} styles Array of styles * @param {ol.render.canvas.ReplayGroup} replayGroup Replay group. diff --git a/src/ol/renderer/dom/domimagelayerrenderer.js b/src/ol/renderer/dom/domimagelayerrenderer.js index 033de8614b..f60702a394 100644 --- a/src/ol/renderer/dom/domimagelayerrenderer.js +++ b/src/ol/renderer/dom/domimagelayerrenderer.js @@ -55,7 +55,7 @@ ol.renderer.dom.ImageLayer.prototype.forEachFeatureAtCoordinate = return source.forEachFeatureAtCoordinate( coordinate, resolution, rotation, skippedFeatureUids, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index 3239f82c72..3a221b9376 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -190,7 +190,7 @@ ol.renderer.dom.VectorLayer.prototype.forEachFeatureAtCoordinate = return this.replayGroup_.forEachFeatureAtCoordinate(coordinate, resolution, rotation, layerState.managed ? frameState.skippedFeatureUids : {}, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index cd0dba0093..7ab15e54a2 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -42,8 +42,8 @@ goog.inherits(ol.renderer.Layer, ol.Observable); /** * @param {ol.Coordinate} coordinate Coordinate. * @param {olx.FrameState} frameState Frame state. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. + * @param {function(this: S, (ol.Feature|ol.render.Feature), ol.layer.Layer): T} + * callback Feature callback. * @param {S} thisArg Value to use as `this` when executing `callback`. * @return {T|undefined} Callback result. * @template S,T diff --git a/src/ol/renderer/maprenderer.js b/src/ol/renderer/maprenderer.js index 7cd56f3c00..de5a49d80d 100644 --- a/src/ol/renderer/maprenderer.js +++ b/src/ol/renderer/maprenderer.js @@ -114,8 +114,8 @@ ol.renderer.Map.expireIconCache_ = function(map, frameState) { /** * @param {ol.Coordinate} coordinate Coordinate. * @param {olx.FrameState} frameState FrameState. - * @param {function(this: S, ol.Feature, ol.layer.Layer): T} callback Feature - * callback. + * @param {function(this: S, (ol.Feature|ol.render.Feature), + * ol.layer.Layer): T} callback Feature callback. * @param {S} thisArg Value to use as `this` when executing `callback`. * @param {function(this: U, ol.layer.Layer): boolean} layerFilter Layer filter * function, only layers which are visible and for which this function @@ -136,7 +136,7 @@ ol.renderer.Map.prototype.forEachFeatureAtCoordinate = var features = {}; /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function forEachFeatureAtCoordinate(feature) { diff --git a/src/ol/renderer/webgl/webglimagelayerrenderer.js b/src/ol/renderer/webgl/webglimagelayerrenderer.js index 8aad156335..dc8d7cc51d 100644 --- a/src/ol/renderer/webgl/webglimagelayerrenderer.js +++ b/src/ol/renderer/webgl/webglimagelayerrenderer.js @@ -85,7 +85,7 @@ ol.renderer.webgl.ImageLayer.prototype.forEachFeatureAtCoordinate = coordinate, resolution, rotation, skippedFeatureUids, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js index c940996abd..c02a618b50 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -162,7 +162,7 @@ ol.source.ImageVector.prototype.forEachFeatureAtCoordinate = function( return this.replayGroup_.forEachFeatureAtCoordinate( coordinate, resolution, 0, skippedFeatureUids, /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @return {?} Callback result. */ function(feature) { diff --git a/src/ol/source/source.js b/src/ol/source/source.js index 38261a0e12..84f0c6208e 100644 --- a/src/ol/source/source.js +++ b/src/ol/source/source.js @@ -89,7 +89,8 @@ goog.inherits(ol.source.Source, ol.Object); * @param {number} resolution Resolution. * @param {number} rotation Rotation. * @param {Object.} skippedFeatureUids Skipped feature uids. - * @param {function(ol.Feature): T} callback Feature callback. + * @param {function((ol.Feature|ol.render.Feature)): T} callback Feature + * callback. * @return {T|undefined} Callback result. * @template T */ diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index e47afe0d3f..648a77b564 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -19,6 +19,7 @@ goog.require('ol.tilegrid.TileGrid'); /** * @typedef {{attributions: (Array.|undefined), + * cacheSize: (number|undefined), * extent: (ol.Extent|undefined), * logo: (string|olx.LogoOptions|undefined), * opaque: (boolean|undefined), @@ -77,7 +78,7 @@ ol.source.Tile = function(options) { * @protected * @type {ol.TileCache} */ - this.tileCache = new ol.TileCache(); + this.tileCache = new ol.TileCache(options.cacheSize); /** * @protected diff --git a/src/ol/source/tilevectorsource.js b/src/ol/source/tilevectorsource.js index 902dafb021..1ca9c23522 100644 --- a/src/ol/source/tilevectorsource.js +++ b/src/ol/source/tilevectorsource.js @@ -310,7 +310,8 @@ ol.source.TileVector.prototype.loadFeatures = this.tileLoadFunction_(url, goog.bind(tileSuccess, this)); } else { var loader = ol.featureloader.loadFeaturesXhr(url, - /** @type {ol.format.Feature} */ (this.format_), tileSuccess); + /** @type {ol.format.Feature} */ (this.format_), tileSuccess, + goog.nullFunction); loader.call(this, extent, resolution, projection); } } diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js index ccec07a350..f675aca898 100644 --- a/src/ol/source/urltilesource.js +++ b/src/ol/source/urltilesource.js @@ -11,7 +11,6 @@ goog.require('ol.source.TileEvent'); /** * @typedef {{attributions: (Array.|undefined), - * cacheSize: (number|undefined), * extent: (ol.Extent|undefined), * logo: (string|olx.LogoOptions|undefined), * opaque: (boolean|undefined), @@ -40,6 +39,7 @@ ol.source.UrlTile = function(options) { goog.base(this, { attributions: options.attributions, + cacheSize: options.cacheSize, extent: options.extent, logo: options.logo, opaque: options.opaque, diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index 7fb8524400..4b264bcbff 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -24,6 +24,7 @@ ol.source.VectorTile = function(options) { goog.base(this, { attributions: options.attributions, + cacheSize: ol.DEFAULT_TILE_CACHE_HIGH_WATER_MARK / 16, extent: options.extent, logo: options.logo, opaque: options.opaque, @@ -38,10 +39,6 @@ ol.source.VectorTile = function(options) { wrapX: options.wrapX }); - this.assumeRightHandedPolygons_ = - goog.isDef(options.assumeRightHandedPolygons) ? - options.assumeRightHandedPolygons : false; - /** * @private * @type {ol.format.Feature} @@ -60,14 +57,6 @@ ol.source.VectorTile = function(options) { goog.inherits(ol.source.VectorTile, ol.source.UrlTile); -/** - * @return {boolean} Assume right handed polygons. - */ -ol.source.VectorTile.prototype.getRightHandedPolygons = function() { - return this.assumeRightHandedPolygons_; -}; - - /** * @inheritDoc */ diff --git a/src/ol/style/style.js b/src/ol/style/style.js index 3e4c1d46eb..5adb92755e 100644 --- a/src/ol/style/style.js +++ b/src/ol/style/style.js @@ -198,7 +198,8 @@ ol.style.Style.prototype.setZIndex = function(zIndex) { * the view's resolution. The function should return an array of * {@link ol.style.Style}. This way e.g. a vector layer can be styled. * - * @typedef {function(ol.Feature, number): Array.} + * @typedef {function((ol.Feature|ol.render.Feature), number): + * Array.} * @api */ ol.style.StyleFunction; @@ -245,7 +246,7 @@ ol.style.defaultStyle_ = null; /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {number} resolution Resolution. * @return {Array.} Style. */ @@ -354,7 +355,8 @@ ol.style.createDefaultEditingStyles = function() { * A function that takes an {@link ol.Feature} as argument and returns an * {@link ol.geom.Geometry} that will be rendered and styled for the feature. * - * @typedef {function(ol.Feature): (ol.geom.Geometry|undefined)} + * @typedef {function((ol.Feature|ol.render.Feature)): + * (ol.geom.Geometry|ol.render.Feature|undefined)} * @api */ ol.style.GeometryFunction; @@ -362,8 +364,9 @@ ol.style.GeometryFunction; /** * Function that is called with a feature and returns its default geometry. - * @param {ol.Feature} feature Feature to get the geometry for. - * @return {ol.geom.Geometry|undefined} Geometry to render. + * @param {ol.Feature|ol.render.Feature} feature Feature to get the geometry + * for. + * @return {ol.geom.Geometry|ol.render.Feature|undefined} Geometry to render. */ ol.style.defaultGeometryFunction = function(feature) { goog.asserts.assert(feature, 'feature must not be null'); diff --git a/test/spec/ol/format/mvtformat.test.js b/test/spec/ol/format/mvtformat.test.js index 9f77d889ac..2e722746a8 100644 --- a/test/spec/ol/format/mvtformat.test.js +++ b/test/spec/ol/format/mvtformat.test.js @@ -17,6 +17,12 @@ describe('ol.format.MVT', function() { describe('#readFeatures', function() { + it('uses ol.render.Feature as feature class by default', function() { + var format = new ol.format.MVT({layers: ['water']}); + var features = format.readFeatures(data); + expect(features[0]).to.be.a(ol.render.Feature); + }); + it('parses only specified layers', function() { var format = new ol.format.MVT({layers: ['water']}); var features = format.readFeatures(data); @@ -24,7 +30,10 @@ describe('ol.format.MVT', function() { }); it('parses geometries correctly', function() { - var format = new ol.format.MVT({layers: ['poi_label']}); + var format = new ol.format.MVT({ + featureClass: ol.Feature, + layers: ['poi_label'] + }); var pbf = new ol.ext.pbf(data); var tile = new ol.ext.vectortile.VectorTile(pbf); var geometry, rawGeometry; @@ -66,3 +75,4 @@ goog.require('ol.Feature'); goog.require('ol.ext.pbf'); goog.require('ol.ext.vectortile'); goog.require('ol.format.MVT'); +goog.require('ol.render.Feature'); From 9df280a884030faa0798c702c242c41687e7a954 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 24 Sep 2015 14:01:14 +0200 Subject: [PATCH 10/27] Handle dirty tiles properly --- src/ol/renderer/canvas/canvasvectortilelayerrenderer.js | 6 +++++- src/ol/vectortile.js | 2 ++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 666834497f..3d739de5e7 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -168,7 +168,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } var replayState = tile.getReplayState(); - if (replayState.renderedRevision == revision && + if (!replayState.dirty && replayState.renderedRevision == revision && replayState.renderedRenderOrder == renderOrder) { return; } @@ -176,6 +176,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, // 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, @@ -211,6 +212,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, var dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup); this.dirty_ = this.dirty_ || dirty; + replayState.dirty = replayState.dirty || dirty; } } @@ -391,6 +393,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = } } + this.dirty_ = false; + /** @type {Array.} */ var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); goog.array.sort(zs); diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index 63810788e9..29ab88587c 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -8,6 +8,7 @@ goog.require('ol.TileState'); /** * @typedef {{ + * dirty: boolean, * renderedRenderOrder: (null|function(ol.Feature, ol.Feature):number), * renderedRevision: number, * replayGroup: ol.render.IReplayGroup}} @@ -58,6 +59,7 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { * @type {ol.TileReplayState} */ this.replayState_ = { + dirty: false, renderedRenderOrder: null, renderedRevision: -1, replayGroup: null From cd2a2ebbc57c6cc94134e3f6a7ce31f798800696 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 24 Sep 2015 14:37:26 +0200 Subject: [PATCH 11/27] Do not call moveTo/lineTo when coordinates are the same See http://jsperf.com/moveto-lineto-to-same-coordinate/3 --- src/ol/render/canvas/canvasreplay.js | 26 +++++++++++++++++++++++--- 1 file changed, 23 insertions(+), 3 deletions(-) diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 7b772043c1..9428b30044 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -251,6 +251,7 @@ ol.render.canvas.Replay.prototype.replay_ = function( var d = 0; // data index var dd; // end of per-instruction data var localTransform = this.tmpLocalTransform_; + var prevX, prevY, roundX, roundY; while (i < ii) { var instruction = instructions[i]; var type = /** @type {ol.render.canvas.Instruction} */ (instruction[0]); @@ -410,7 +411,8 @@ ol.render.canvas.Replay.prototype.replay_ = function( break; case ol.render.canvas.Instruction.END_GEOMETRY: if (featureCallback !== undefined) { - feature = /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); + feature = + /** @type {ol.Feature|ol.render.Feature} */ (instruction[1]); var result = featureCallback(feature); if (result) { return result; @@ -429,9 +431,25 @@ ol.render.canvas.Replay.prototype.replay_ = function( goog.asserts.assert(goog.isNumber(instruction[2]), '3rd instruction should be a number'); dd = /** @type {number} */ (instruction[2]); - context.moveTo(pixelCoordinates[d], pixelCoordinates[d + 1]); + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (roundX !== prevX || roundY !== prevY) { + context.moveTo(x, y); + prevX = roundX; + prevY = roundY; + } for (d += 2; d < dd; d += 2) { - context.lineTo(pixelCoordinates[d], pixelCoordinates[d + 1]); + x = pixelCoordinates[d]; + y = pixelCoordinates[d + 1]; + roundX = (x + 0.5) | 0; + roundY = (y + 0.5) | 0; + if (roundX !== prevX || roundY !== prevY) { + context.lineTo(x, y); + prevX = roundX; + prevY = roundY; + } } ++i; break; @@ -465,6 +483,8 @@ ol.render.canvas.Replay.prototype.replay_ = function( if (ol.has.CANVAS_LINE_DASH) { context.setLineDash(/** @type {Array.} */ (instruction[6])); } + prevX = NaN; + prevY = NaN; ++i; break; case ol.render.canvas.Instruction.SET_TEXT_STYLE: From bda3a6803c32d1a564f9d73153e6eb399a3f969e Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 24 Sep 2015 14:44:50 +0200 Subject: [PATCH 12/27] Use tiles with lower or same resolution as view --- src/ol/renderer/canvas/canvasvectortilelayerrenderer.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 3d739de5e7..5f4f0c130b 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -345,9 +345,12 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = var pixelRatio = frameState.pixelRatio; var tileGrid = source.getTileGrid(); - var z = tileGrid.getZForResolution(resolution); - var tileRange = tileGrid.getTileRangeForExtentAndResolution( - extent, resolution); + 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()); From 29b64d862863e161279d5882fee43fb1862a903b Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 24 Sep 2015 14:45:37 +0200 Subject: [PATCH 13/27] Improve readability and reduce garbage creation --- .../canvas/canvasvectortilelayerrenderer.js | 30 +++++++++++-------- 1 file changed, 17 insertions(+), 13 deletions(-) diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 5f4f0c130b..ee45b7649d 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -123,7 +123,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.composeFrame = tileSize = tileGrid.getTileSize(currentZ); tilePixelSize = source.getTilePixelSize(currentZ, pixelRatio, projection); tilePixelRatio = tilePixelSize[0] / - ol.size.toSize(tileGrid.getTileSize(currentZ), this.tmpSize_)[0]; + ol.size.toSize(tileSize, this.tmpSize_)[0]; tileResolution = tileGrid.getResolution(currentZ); tilePixelResolution = tileResolution / tilePixelRatio; if (tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS) { @@ -183,16 +183,22 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, 'Source is an ol.source.VectorTile'); var tileGrid = source.getTileGrid(); var tileCoord = tile.getTileCoord(); - var resolution = tileGrid.getTileCoordResolution(tileCoord); var pixelSpace = tile.getProjection().getUnits() == ol.proj.Units.TILE_PIXELS; - var extent = pixelSpace ? - [0, 0].concat(source.getTilePixelSize(tileCoord[0], pixelRatio, - tile.getProjection())) : - tileGrid.getTileCoordExtent(tileCoord); + var extent; + if (pixelSpace) { + var tilePixelSize = source.getTilePixelSize(tileCoord[0], pixelRatio, + tile.getProjection()); + extent = [0, 0, tilePixelSize[0], tilePixelSize[1]]; + } else { + extent = tileGrid.getTileCoordExtent(tileCoord); + } + var resolution = tileGrid.getResolution(tileCoord[0]); var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution; - var replayGroup = new ol.render.canvas.ReplayGroup(pixelSpace ? 0 : - ol.renderer.vector.getTolerance(tileResolution, pixelRatio), extent, + replayState.dirty = false; + var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, tileResolution, layer.getRenderBuffer(), source.getRightHandedPolygons()); + var squaredTolerance = ol.renderer.vector.getSquaredTolerance( + tileResolution, pixelRatio); /** * @param {ol.Feature|ol.render.Feature} feature Feature. @@ -206,8 +212,6 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } 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); @@ -254,13 +258,13 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = tileCoord = tile.getTileCoord(); goog.asserts.assertInstanceof(source, ol.source.VectorTile, 'Source is an ol.source.VectorTile'); - tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord); + tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, + this.tmpExtent_); if (!ol.extent.containsCoordinate(tileExtent, coordinate)) { continue; } if (tile.getProjection().getUnits() === ol.proj.Units.TILE_PIXELS) { - origin = ol.extent.getTopLeft( - source.getTileGrid().getTileCoordExtent(tileCoord)); + origin = ol.extent.getTopLeft(tileExtent); tilePixelRatio = source.getTilePixelRatio(); tileResolution = tileGrid.getResolution(tileCoord[0]) / tilePixelRatio; tileSize = ol.size.toSize(tileGrid.getTileSize(tileCoord[0])); From 0e8e104a2d4163ff9aa7f32823d73b71fe7d514d Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 25 Sep 2015 17:15:01 +0200 Subject: [PATCH 14/27] Handle righthanded polygons in format, not source --- externs/olx.js | 11 ----------- src/ol/format/featureformat.js | 14 ++++++++++++++ src/ol/format/mvtformat.js | 2 ++ .../canvas/canvasvectortilelayerrenderer.js | 3 ++- 4 files changed, 18 insertions(+), 12 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index 30bf226baa..d819468aba 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4106,7 +4106,6 @@ olx.source.TileImageOptions.prototype.wrapX; * projection: ol.proj.ProjectionLike, * state: (ol.source.State|string|undefined), * format: (ol.format.Feature|undefined), - * rightHandedPolygons: (boolean|undefined), * tileClass: (function(new: ol.VectorTile, ol.TileCoord, * ol.TileState, string, ol.format.Feature, * ol.TileLoadFunctionType)|undefined), @@ -4153,16 +4152,6 @@ olx.source.VectorTileOptions.prototype.opaque; olx.source.VectorTileOptions.prototype.projection; -/** - * Assume that all polygons provided by this source follow the right-hand rule - * (counter-clockwise for exterior and clockwise for interior rings). If `true`, - * renderers will skip the check for the ring orientation. - * @type {boolean|undefined} - * @api - */ -olx.source.VectorTileOptions.prototype.rightHandedPolygons; - - /** * Source state. * @type {ol.source.State|string|undefined} diff --git a/src/ol/format/featureformat.js b/src/ol/format/featureformat.js index 25c10aa79d..67f8ed20da 100644 --- a/src/ol/format/featureformat.js +++ b/src/ol/format/featureformat.js @@ -24,6 +24,12 @@ ol.format.Feature = function() { * @type {ol.proj.Projection} */ this.defaultDataProjection = null; + + /** + * @protected + * @type {boolean} + */ + this.rightHandedPolygons = false; }; @@ -53,6 +59,14 @@ ol.format.Feature.prototype.getReadOptions = function(source, opt_options) { }; +/** + * @return {boolean} + */ +ol.format.Feature.prototype.getRightHandedPolygons = function() { + return this.rightHandedPolygons; +}; + + /** * Sets the `defaultDataProjection` on the options, if no `dataProjection` * is set. diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js index 0b8a07acd2..e22535bf83 100644 --- a/src/ol/format/mvtformat.js +++ b/src/ol/format/mvtformat.js @@ -75,6 +75,8 @@ ol.format.MVT = function(opt_options) { */ this.layers_ = goog.isDef(options.layers) ? options.layers : null; + this.rightHandedPolygons = true; + }; goog.inherits(ol.format.MVT, ol.format.Feature); diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index ee45b7649d..a990815b5c 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -196,7 +196,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution; replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, - tileResolution, layer.getRenderBuffer(), source.getRightHandedPolygons()); + tileResolution, layer.getRenderBuffer(), + tile.getFormat().getRightHandedPolygons()); var squaredTolerance = ol.renderer.vector.getSquaredTolerance( tileResolution, pixelRatio); From 8e9b20db51d85e76a68d02fc594c2e7641c12297 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 27 Sep 2015 20:36:07 +0200 Subject: [PATCH 15/27] Additional tests, documentation and example --- examples/mapbox-vector-tiles-simple.css | 3 + examples/mapbox-vector-tiles-simple.html | 15 ++ examples/mapbox-vector-tiles-simple.js | 44 +++++ examples/mapbox-vector-tiles.html | 2 +- examples/mapbox-vector-tiles.js | 41 +++-- src/ol/layer/vectortilelayer.js | 2 +- src/ol/render/renderfeature.js | 2 +- src/ol/source/vectorsource.js | 4 +- src/ol/source/vectortilesource.js | 8 +- test/spec/ol/layer/vectortilelayer.test.js | 37 ++++ test/spec/ol/render/renderfeature.test.js | 90 ++++++++++ .../canvasvectortilelayerrenderer.test.js | 128 ++++++++++++++ test/spec/ol/source/urltilesource.test.js | 162 ++++++++++++++++++ test/spec/ol/source/vectortilesource.test.js | 43 +++++ 14 files changed, 560 insertions(+), 21 deletions(-) create mode 100644 examples/mapbox-vector-tiles-simple.css create mode 100644 examples/mapbox-vector-tiles-simple.html create mode 100644 examples/mapbox-vector-tiles-simple.js create mode 100644 test/spec/ol/layer/vectortilelayer.test.js create mode 100644 test/spec/ol/render/renderfeature.test.js create mode 100644 test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js create mode 100644 test/spec/ol/source/urltilesource.test.js create mode 100644 test/spec/ol/source/vectortilesource.test.js diff --git a/examples/mapbox-vector-tiles-simple.css b/examples/mapbox-vector-tiles-simple.css new file mode 100644 index 0000000000..33e90f7301 --- /dev/null +++ b/examples/mapbox-vector-tiles-simple.css @@ -0,0 +1,3 @@ +.map { + background: #f8f4f0; +} diff --git a/examples/mapbox-vector-tiles-simple.html b/examples/mapbox-vector-tiles-simple.html new file mode 100644 index 0000000000..36da8db103 --- /dev/null +++ b/examples/mapbox-vector-tiles-simple.html @@ -0,0 +1,15 @@ +--- +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 +--- +
+
+
+
+
diff --git a/examples/mapbox-vector-tiles-simple.js b/examples/mapbox-vector-tiles-simple.js new file mode 100644 index 0000000000..3d68525c4d --- /dev/null +++ b/examples/mapbox-vector-tiles-simple.js @@ -0,0 +1,44 @@ +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'); + + +// Mapbox access token - request your own at http://mapbox.com +var accessToken = + '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=' + accessToken + }), + 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 ec0744b5f7..c7cb0b45d3 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 simple vector tiles map. + A vector tiles map which reuses the same tiles for subsequent zoom levels to save bandwith on mobile devices. **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 ad1a5d8816..4506437370 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -13,21 +13,36 @@ goog.require('ol.style.Text'); goog.require('ol.tilegrid.TileGrid'); -// Mapbox access token - request your own at http://mabobox.com +// Mapbox access token - request your own at http://mapbox.com var accessToken = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; -// For how many zoom levels do we want to use the same vector tile? +// 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 from web mercator zoom level 0 -var zOffset = 1; +// 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 = zOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) { +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=' + accessToken) + .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({ +var map = new ol.Map({ layers: [ new ol.layer.VectorTile({ preload: Infinity, @@ -37,22 +52,13 @@ var map = new ol.Map({ '© ' + 'OpenStreetMap contributors' })], - rightHandedPolygons: true, format: new ol.format.MVT(), tileGrid: new ol.tilegrid.TileGrid({ extent: ol.proj.get('EPSG:3857').getExtent(), resolutions: resolutions }), tilePixelRatio: 16, - tileUrlFunction: function(tileCoord) { - return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + - '{z}/{x}/{y}.vector.pbf?access_token=' + accessToken) - .replace('{z}', String(tileCoord[0] * reuseZoomLevels + zOffset)) - .replace('{x}', String(tileCoord[1])) - .replace('{y}', String(-tileCoord[2] - 1)) - .replace('{a-d}', 'abcd'.substr( - ((tileCoord[1] << tileCoord[0]) + tileCoord[2]) % 4, 1)); - } + tileUrlFunction: tileUrlFunction }), style: createMapboxStreetsV6Style() }) @@ -64,3 +70,6 @@ var map = new ol.Map({ zoom: 3 }) }); + +// ol.style.Fill, ol.style.Icon, ol.style.Stroke, ol.style.Style and +// ol.style.Text are required for createMapboxStreetsV6Style() diff --git a/src/ol/layer/vectortilelayer.js b/src/ol/layer/vectortilelayer.js index a3330d414f..31797e1170 100644 --- a/src/ol/layer/vectortilelayer.js +++ b/src/ol/layer/vectortilelayer.js @@ -16,7 +16,7 @@ ol.layer.VectorTileProperty = { /** * @classdesc - * Vector tile data that is rendered client-side. + * Layer for vector tile data that is rendered client-side. * Note that any property set in the options is set as a {@link ol.Object} * property on the layer object; for example, setting `title: 'My Title'` in the * options means that `title` is observable, and has get/set accessors. diff --git a/src/ol/render/renderfeature.js b/src/ol/render/renderfeature.js index 21bc706cd9..4a147fed7d 100644 --- a/src/ol/render/renderfeature.js +++ b/src/ol/render/renderfeature.js @@ -10,7 +10,7 @@ goog.require('ol.geom.GeometryType'); /** * Lightweight, read-only, {@link ol.Feature} and {@link ol.geom.Geometry} like - * structure, optimized for rendering and styling. Geometry acces through the + * structure, optimized for rendering and styling. Geometry access through the * API is limited to getting the type and extent of the geometry. * * @constructor diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index d36696c86f..cb708c0891 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -66,7 +66,9 @@ ol.source.VectorEventType = { /** * @classdesc - * Provides a source of features for vector layers. + * Provides a source of features for vector layers. Vector features provided + * by this source are suitable for editing. See {@link ol.source.VectorTile} for + * vector data that is optimized for rendering. * * @constructor * @extends {ol.source.Source} diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index 4b264bcbff..4e8e2b4476 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -12,7 +12,13 @@ goog.require('ol.source.UrlTile'); /** * @classdesc - * Base class for sources providing images divided into a tile grid. + * Class for layer sources providing vector data divided into a tile grid, to be + * used with {@link ol.layer.VectorTile}. Although this source receives tiles + * with vector features from the server, it is not meant for feature editing. + * Features are optimized for rendering, their geometries are clipped at or near + * tile boundaries and simplified for a view resolution. See + * {@link ol.source.Vector} for vector sources that are suitable for feature + * editing. * * @constructor * @fires ol.source.TileEvent diff --git a/test/spec/ol/layer/vectortilelayer.test.js b/test/spec/ol/layer/vectortilelayer.test.js new file mode 100644 index 0000000000..4ced5c282e --- /dev/null +++ b/test/spec/ol/layer/vectortilelayer.test.js @@ -0,0 +1,37 @@ +goog.provide('ol.test.layer.VectorTile'); + +describe('ol.layer.VectorTile', function() { + + describe('constructor (defaults)', function() { + + var layer; + + beforeEach(function() { + layer = new ol.layer.VectorTile({ + source: new ol.source.VectorTile({}) + }); + }); + + afterEach(function() { + goog.dispose(layer); + }); + + it('creates an instance', function() { + expect(layer).to.be.a(ol.layer.VectorTile); + }); + + it('provides default preload', function() { + expect(layer.getPreload()).to.be(0); + }); + + it('provides default useInterimTilesOnError', function() { + expect(layer.getUseInterimTilesOnError()).to.be(true); + }); + + }); + +}); + +goog.require('goog.dispose'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.source.VectorTile'); diff --git a/test/spec/ol/render/renderfeature.test.js b/test/spec/ol/render/renderfeature.test.js new file mode 100644 index 0000000000..622cfaddc4 --- /dev/null +++ b/test/spec/ol/render/renderfeature.test.js @@ -0,0 +1,90 @@ +goog.provide('ol.test.render.Feature'); + +describe('ol.render.Feature', function() { + + var renderFeature; + var type = 'Point'; + var flatCoordinates = [0, 0]; + var ends = null; + var properties = {foo: 'bar'}; + + describe('Constructor', function() { + it('creates an instance', function() { + renderFeature = + new ol.render.Feature(type, flatCoordinates, ends, properties); + expect(renderFeature).to.be.a(ol.render.Feature); + }); + }); + + describe('#get()', function() { + it('returns a single property', function() { + expect(renderFeature.get('foo')).to.be('bar'); + }); + }); + + describe('#getEnds()', function() { + it('returns the ends it was created with', function() { + expect(renderFeature.getEnds()).to.equal(ends); + }); + }); + + describe('#getExtent()', function() { + it('returns the correct extent for a point', function() { + expect(renderFeature.getExtent()).to.eql([0, 0, 0, 0]); + }); + it('caches the extent', function() { + expect(renderFeature.getExtent()).to.equal(renderFeature.extent_); + }); + it('returns the correct extent for a linestring', function() { + var feature = + new ol.render.Feature('LineString', [-1, -2, 2, 1], null, {}); + expect(feature.getExtent()).to.eql([-1, -2, 2, 1]); + }); + }); + + describe('#getFlatCoordinates()', function() { + it('returns the flat coordinates it was created with', function() { + expect(renderFeature.getFlatCoordinates()).to.equal(flatCoordinates); + }); + }); + + describe('#getGeometry()', function() { + it('returns itself as geometry', function() { + expect(renderFeature.getGeometry()).to.equal(renderFeature); + }); + }); + + describe('#getProperties()', function() { + it('returns the properties it was created with', function() { + expect(renderFeature.getProperties()).to.equal(properties); + }); + }); + + describe('#getSimplifiedGeometry()', function() { + it('returns itself as simplified geometry', function() { + expect(renderFeature.getSimplifiedGeometry()).to.equal(renderFeature); + }); + }); + + describe('#getStride()', function() { + it('returns 2', function() { + expect(renderFeature.getStride()).to.be(2); + }); + }); + + describe('#getStyleFunction()', function() { + it('returns undefined', function() { + expect(renderFeature.getStyleFunction()).to.be(undefined); + }); + }); + + describe('#getType()', function() { + it('returns the type it was created with', function() { + expect(renderFeature.getType()).to.equal(type); + }); + }); + +}); + + +goog.require('ol.render.Feature'); diff --git a/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js b/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js new file mode 100644 index 0000000000..da228a61c0 --- /dev/null +++ b/test/spec/ol/renderer/canvas/canvasvectortilelayerrenderer.test.js @@ -0,0 +1,128 @@ +goog.provide('ol.test.renderer.canvas.VectorTileLayer'); + +describe('ol.renderer.canvas.VectorTileLayer', function() { + + describe('constructor', function() { + + it('creates a new instance', function() { + var layer = new ol.layer.VectorTile({ + source: new ol.source.VectorTile({}) + }); + var renderer = new ol.renderer.canvas.VectorTileLayer(layer); + expect(renderer).to.be.a(ol.renderer.canvas.VectorTileLayer); + }); + + it('gives precedence to feature styles over layer styles', function() { + var target = document.createElement('div'); + target.style.width = '256px'; + target.style.height = '256px'; + document.body.appendChild(target); + var map = new ol.Map({ + view: new ol.View({ + center: [0, 0], + zoom: 0 + }), + target: target + }); + var layerStyle = [new ol.style.Style({ + text: new ol.style.Text({ + text: 'layer' + }) + })]; + var featureStyle = [new ol.style.Style({ + text: new ol.style.Text({ + text: 'feature' + }) + })]; + var feature1 = new ol.Feature(new ol.geom.Point([0, 0])); + var feature2 = new ol.Feature(new ol.geom.Point([0, 0])); + feature2.setStyle(featureStyle); + var TileClass = function() { + ol.VectorTile.apply(this, arguments); + this.setState('loaded'); + this.setFeatures([feature1, feature2]); + this.setProjection(ol.proj.get('EPSG:3857')); + }; + ol.inherits(TileClass, ol.VectorTile); + var source = new ol.source.VectorTile({ + format: new ol.format.MVT(), + tileClass: TileClass, + tileGrid: ol.tilegrid.createXYZ() + }); + var layer = new ol.layer.VectorTile({ + source: source, + style: layerStyle + }); + map.addLayer(layer); + var spy = sinon.spy(map.getRenderer().getLayerRenderer(layer), + 'renderFeature'); + map.renderSync(); + expect(spy.getCall(0).args[2]).to.be(layerStyle); + expect(spy.getCall(1).args[2]).to.be(featureStyle); + document.body.removeChild(target); + }); + + }); + + describe('#forEachFeatureAtCoordinate', function() { + var layer, renderer, replayGroup; + var TileClass = function() { + ol.VectorTile.apply(this, arguments); + this.setState('loaded'); + this.setProjection(ol.proj.get('EPSG:3857')); + this.replayState_.replayGroup = replayGroup; + }; + ol.inherits(TileClass, ol.VectorTile); + + beforeEach(function() { + replayGroup = {}; + layer = new ol.layer.VectorTile({ + source: new ol.source.VectorTile({ + tileClass: TileClass, + tileGrid: ol.tilegrid.createXYZ() + }) + }); + renderer = new ol.renderer.canvas.VectorTileLayer(layer); + replayGroup.forEachFeatureAtCoordinate = function(coordinate, + resolution, rotation, skippedFeaturesUids, callback) { + var feature = new ol.Feature(); + callback(feature); + callback(feature); + }; + }); + + it('calls callback once per feature with a layer as 2nd arg', function() { + var spy = sinon.spy(); + var coordinate = [0, 0]; + var frameState = { + layerStates: {}, + skippedFeatureUids: {}, + viewState: { + resolution: 1, + rotation: 0 + } + }; + frameState.layerStates[goog.getUid(layer)] = {}; + renderer.renderedTiles_ = [new TileClass([0, 0, -1])]; + renderer.forEachFeatureAtCoordinate( + coordinate, frameState, spy, undefined); + expect(spy.callCount).to.be(1); + expect(spy.getCall(0).args[1]).to.equal(layer); + }); + }); + +}); + + +goog.require('ol.Feature'); +goog.require('ol.Map'); +goog.require('ol.VectorTile'); +goog.require('ol.View'); +goog.require('ol.format.MVT'); +goog.require('ol.geom.Point'); +goog.require('ol.layer.VectorTile'); +goog.require('ol.proj'); +goog.require('ol.renderer.canvas.VectorTileLayer'); +goog.require('ol.source.VectorTile'); +goog.require('ol.style.Style'); +goog.require('ol.style.Text'); diff --git a/test/spec/ol/source/urltilesource.test.js b/test/spec/ol/source/urltilesource.test.js new file mode 100644 index 0000000000..8273b5956a --- /dev/null +++ b/test/spec/ol/source/urltilesource.test.js @@ -0,0 +1,162 @@ +goog.provide('ol.test.source.UrlTile'); + + +describe('ol.source.UrlTile', function() { + + describe('tileUrlFunction', function() { + + var tileSource, tileGrid; + + beforeEach(function() { + tileSource = new ol.source.UrlTile({ + projection: 'EPSG:3857', + tileGrid: ol.tilegrid.createXYZ({maxZoom: 6}), + url: '{z}/{x}/{y}', + wrapX: true + }); + tileGrid = tileSource.getTileGrid(); + }); + + it('returns the expected URL', function() { + + var coordinate = [829330.2064098881, 5933916.615134273]; + var tileUrl; + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 0)); + expect(tileUrl).to.eql('0/0/0'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 1)); + expect(tileUrl).to.eql('1/1/0'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 2)); + expect(tileUrl).to.eql('2/2/1'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 3)); + expect(tileUrl).to.eql('3/4/2'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 4)); + expect(tileUrl).to.eql('4/8/5'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 5)); + expect(tileUrl).to.eql('5/16/11'); + + tileUrl = tileSource.tileUrlFunction( + tileGrid.getTileCoordForCoordAndZ(coordinate, 6)); + expect(tileUrl).to.eql('6/33/22'); + + }); + + describe('wrap x', function() { + + it('returns the expected URL', function() { + var projection = tileSource.getProjection(); + var tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, -31, -23], projection)); + expect(tileUrl).to.eql('6/33/22'); + + tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, 33, -23], projection)); + expect(tileUrl).to.eql('6/33/22'); + + tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, 97, -23], projection)); + expect(tileUrl).to.eql('6/33/22'); + }); + + }); + + describe('crop y', function() { + + it('returns the expected URL', function() { + var projection = tileSource.getProjection(); + var tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, 33, 0], projection)); + expect(tileUrl).to.be(undefined); + + tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, 33, -23], projection)); + expect(tileUrl).to.eql('6/33/22'); + + tileUrl = tileSource.tileUrlFunction( + tileSource.getTileCoordForTileUrlFunction( + [6, 33, -65], projection)); + expect(tileUrl).to.be(undefined); + }); + + }); + + }); + + describe('#getUrls', function() { + + var sourceOptions; + var source; + var url = 'http://geo.nls.uk/maps/towns/glasgow1857/{z}/{x}/{-y}.png'; + + beforeEach(function() { + sourceOptions = { + tileGrid: ol.tilegrid.createXYZ({ + extent: ol.proj.get('EPSG:4326').getExtent() + }) + }; + }); + + describe('using a "url" option', function() { + beforeEach(function() { + sourceOptions.url = url; + source = new ol.source.UrlTile(sourceOptions); + }); + + it('returns the XYZ URL', function() { + var urls = source.getUrls(); + expect(urls).to.be.eql([url]); + }); + + }); + + describe('using a "urls" option', function() { + beforeEach(function() { + sourceOptions.urls = ['some_xyz_url1', 'some_xyz_url2']; + source = new ol.source.UrlTile(sourceOptions); + }); + + it('returns the XYZ URLs', function() { + var urls = source.getUrls(); + expect(urls).to.be.eql(['some_xyz_url1', 'some_xyz_url2']); + }); + + }); + + describe('using a "tileUrlFunction"', function() { + beforeEach(function() { + sourceOptions.tileUrlFunction = function() { + return 'some_xyz_url'; + }; + source = new ol.source.UrlTile(sourceOptions); + }); + + it('returns null', function() { + var urls = source.getUrls(); + expect(urls).to.be(null); + }); + + }); + + }); + +}); + +goog.require('ol.TileCoord'); +goog.require('ol.proj'); +goog.require('ol.source.UrlTile'); diff --git a/test/spec/ol/source/vectortilesource.test.js b/test/spec/ol/source/vectortilesource.test.js new file mode 100644 index 0000000000..441c4f382d --- /dev/null +++ b/test/spec/ol/source/vectortilesource.test.js @@ -0,0 +1,43 @@ +goog.provide('ol.test.source.VectorTile'); + + +describe('ol.source.VectorTile', function() { + + var format = new ol.format.MVT(); + var source = new ol.source.VectorTile({ + format: format, + tileGrid: ol.tilegrid.createXYZ(), + url: '{z}/{x}/{y}.pbf' + }); + var tile; + + describe('constructor', function() { + it('sets the format on the instance', function() { + expect(source.format_).to.equal(format); + }); + it('uses ol.VectorTile as default tileClass', function() { + expect(source.tileClass).to.equal(ol.VectorTile); + }); + }); + + describe('#getTile()', function() { + it('creates a tile with the correct tile class', function() { + tile = source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857')); + expect(tile).to.be.a(ol.VectorTile); + }); + it('sets the correct tileCoord on the created tile', function() { + expect(tile.getTileCoord()).to.eql([0, 0, 0]); + }); + it('fetches tile from cache when requested again', function() { + expect(source.getTile(0, 0, 0, 1, ol.proj.get('EPSG:3857'))) + .to.equal(tile); + }); + }); + +}); + + +goog.require('ol.VectorTile'); +goog.require('ol.format.MVT'); +goog.require('ol.proj'); +goog.require('ol.source.VectorTile'); From af69933c64e21556be3c278e25eab10baf112395 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 30 Sep 2015 14:23:18 +0200 Subject: [PATCH 16/27] Enable wrapX by default --- examples/mapbox-vector-tiles.js | 4 ++-- src/ol/source/vectortilesource.js | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/examples/mapbox-vector-tiles.js b/examples/mapbox-vector-tiles.js index 4506437370..97529fd143 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -65,9 +65,9 @@ var map = new ol.Map({ ], target: 'map', view: new ol.View({ - center: [1823849, 6143760], + center: [0, 0], minZoom: 1, - zoom: 3 + zoom: 2 }) }); diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index 4e8e2b4476..3fab70318c 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -42,7 +42,7 @@ ol.source.VectorTile = function(options) { options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction, tileUrlFunction: options.tileUrlFunction, tilePixelRatio: options.tilePixelRatio, - wrapX: options.wrapX + wrapX: options.wrapX === undefined ? true : options.wrapX }); /** From 4784b7f2e232489b574ab10a6301b1825a9f7db1 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Thu, 1 Oct 2015 19:19:37 +0200 Subject: [PATCH 17/27] Handle urls and templates in ol.source.UrlTile This makes url templates available for ol.source.VectorTile. --- externs/olx.js | 65 +++++++++++++++++++++------ src/ol/source/tilearcgisrestsource.js | 48 ++------------------ src/ol/source/tileimagesource.js | 2 + src/ol/source/tilewmssource.js | 53 +++------------------- src/ol/source/urltilesource.js | 64 ++++++++++++++++++++++++-- src/ol/source/vectortilesource.js | 2 + src/ol/source/wmtssource.js | 21 ++------- src/ol/source/xyzsource.js | 54 ++-------------------- 8 files changed, 131 insertions(+), 178 deletions(-) diff --git a/externs/olx.js b/externs/olx.js index d819468aba..fd68460f29 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -3976,6 +3976,8 @@ olx.source.TileUTFGridOptions.prototype.url; * tileLoadFunction: (ol.TileLoadFunctionType|undefined), * tilePixelRatio: (number|undefined), * tileUrlFunction: (ol.TileUrlFunctionType|undefined), + * url: (string|undefined), + * urls: (Array.|undefined), * wrapX: (boolean|undefined)}} * @api */ @@ -4088,6 +4090,24 @@ olx.source.TileImageOptions.prototype.tilePixelRatio; olx.source.TileImageOptions.prototype.tileUrlFunction; +/** + * URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders. + * A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be + * used instead of defining each one separately in the `urls` option. + * @type {string|undefined} + * @api stable + */ +olx.source.TileImageOptions.prototype.url; + + +/** + * An array of URL templates. + * @type {Array.|undefined} + * @api + */ +olx.source.TileImageOptions.prototype.urls; + + /** * Whether to wrap the world horizontally. The default, `undefined`, is to * request out-of-bounds tiles from the server. When set to `false`, only one @@ -4101,11 +4121,11 @@ olx.source.TileImageOptions.prototype.wrapX; /** * @typedef {{attributions: (Array.|undefined), + * format: (ol.format.Feature|undefined), * logo: (string|olx.LogoOptions|undefined), * opaque: (boolean|undefined), * projection: ol.proj.ProjectionLike, * state: (ol.source.State|string|undefined), - * format: (ol.format.Feature|undefined), * tileClass: (function(new: ol.VectorTile, ol.TileCoord, * ol.TileState, string, ol.format.Feature, * ol.TileLoadFunctionType)|undefined), @@ -4113,6 +4133,8 @@ olx.source.TileImageOptions.prototype.wrapX; * tileLoadFunction: (ol.TileLoadFunctionType|undefined), * tilePixelRatio: (number|undefined), * tileUrlFunction: (ol.TileUrlFunctionType|undefined), + * url: (string|undefined), + * urls: (Array.|undefined), * wrapX: (boolean|undefined)}} * @api */ @@ -4128,6 +4150,14 @@ olx.source.VectorTileOptions; olx.source.VectorTileOptions.prototype.attributions; +/** + * Feature format for tiles. Used and required by the default + * `tileLoadFunction`. + * @type {ol.format.Feature|undefined} + */ +olx.source.VectorTileOptions.prototype.format; + + /** * Logo. * @type {string|olx.LogoOptions|undefined} @@ -4160,14 +4190,6 @@ olx.source.VectorTileOptions.prototype.projection; olx.source.VectorTileOptions.prototype.state; -/** - * Feature format for tiles. Used and required by the default - * `tileLoadFunction`. - * @type {ol.format.Feature|undefined} - */ -olx.source.VectorTileOptions.prototype.format; - - /** * Class used to instantiate image tiles. Default is {@link ol.VectorTile}. * @type {function(new: ol.VectorTile, ol.TileCoord, @@ -4214,10 +4236,27 @@ olx.source.VectorTileOptions.prototype.tileUrlFunction; /** - * Whether to wrap the world horizontally. The default, `undefined`, is to - * request out-of-bounds tiles from the server. When set to `false`, only one - * world will be rendered. When set to `true`, tiles will be requested for one - * world only, but they will be wrapped horizontally to render multiple worlds. + * URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders. + * A `{?-?}` template pattern, for example `subdomain{a-f}.domain.com`, may be + * used instead of defining each one separately in the `urls` option. + * @type {string|undefined} + * @api stable + */ +olx.source.VectorTileOptions.prototype.url; + + +/** + * An array of URL templates. + * @type {Array.|undefined} + * @api + */ +olx.source.VectorTileOptions.prototype.urls; + + +/** + * Whether to wrap the world horizontally. When set to `false`, only one world + * will be rendered. When set to `true`, tiles will be wrapped horizontally to + * render multiple worlds. Default is `true`. * @type {boolean|undefined} * @api */ diff --git a/src/ol/source/tilearcgisrestsource.js b/src/ol/source/tilearcgisrestsource.js index 76ba2c336e..bd37a418d6 100644 --- a/src/ol/source/tilearcgisrestsource.js +++ b/src/ol/source/tilearcgisrestsource.js @@ -7,7 +7,6 @@ goog.require('goog.string'); goog.require('goog.uri.utils'); goog.require('ol'); goog.require('ol.TileCoord'); -goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.size'); @@ -45,20 +44,11 @@ ol.source.TileArcGISRest = function(opt_options) { tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, tileUrlFunction: goog.bind(this.tileUrlFunction_, this), + url: options.url, + urls: options.urls, wrapX: options.wrapX !== undefined ? options.wrapX : true }); - var urls = options.urls; - if (urls === undefined && options.url !== undefined) { - urls = ol.TileUrlFunction.expandUrl(options.url); - } - - /** - * @private - * @type {!Array.} - */ - this.urls_ = urls || []; - /** * @private * @type {Object} @@ -100,7 +90,7 @@ ol.source.TileArcGISRest.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, pixelRatio, projection, params) { - var urls = this.urls_; + var urls = this.urls; if (urls.length === 0) { return undefined; } @@ -158,38 +148,6 @@ ol.source.TileArcGISRest.prototype.getTilePixelSize = }; -/** - * Return the URLs used for this ArcGIS source. - * @return {!Array.} URLs. - * @api stable - */ -ol.source.TileArcGISRest.prototype.getUrls = function() { - return this.urls_; -}; - - -/** - * Set the URL to use for requests. - * @param {string|undefined} url URL. - * @api stable - */ -ol.source.TileArcGISRest.prototype.setUrl = function(url) { - var urls = url !== undefined ? ol.TileUrlFunction.expandUrl(url) : null; - this.setUrls(urls); -}; - - -/** - * Set the URLs to use for requests. - * @param {Array.|undefined} urls URLs. - * @api stable - */ -ol.source.TileArcGISRest.prototype.setUrls = function(urls) { - this.urls_ = urls || []; - this.changed(); -}; - - /** * @param {ol.TileCoord} tileCoord Tile coordinate. * @param {number} pixelRatio Pixel ratio. diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js index f5b1929ccf..c4964374ef 100644 --- a/src/ol/source/tileimagesource.js +++ b/src/ol/source/tileimagesource.js @@ -38,6 +38,8 @@ ol.source.TileImage = function(options) { options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction, tilePixelRatio: options.tilePixelRatio, tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, wrapX: options.wrapX }); diff --git a/src/ol/source/tilewmssource.js b/src/ol/source/tilewmssource.js index 265b607a2d..f17caee76a 100644 --- a/src/ol/source/tilewmssource.js +++ b/src/ol/source/tilewmssource.js @@ -11,7 +11,6 @@ goog.require('goog.string'); goog.require('goog.uri.utils'); goog.require('ol'); goog.require('ol.TileCoord'); -goog.require('ol.TileUrlFunction'); goog.require('ol.extent'); goog.require('ol.proj'); goog.require('ol.size'); @@ -49,20 +48,11 @@ ol.source.TileWMS = function(opt_options) { tileGrid: options.tileGrid, tileLoadFunction: options.tileLoadFunction, tileUrlFunction: goog.bind(this.tileUrlFunction_, this), + url: options.url, + urls: options.urls, wrapX: options.wrapX !== undefined ? options.wrapX : true }); - var urls = options.urls; - if (urls === undefined && options.url !== undefined) { - urls = ol.TileUrlFunction.expandUrl(options.url); - } - - /** - * @private - * @type {!Array.} - */ - this.urls_ = urls || []; - /** * @private * @type {number} @@ -221,7 +211,7 @@ ol.source.TileWMS.prototype.getRequestUrl_ = function(tileCoord, tileSize, tileExtent, pixelRatio, projection, params) { - var urls = this.urls_; + var urls = this.urls; if (urls.length === 0) { return undefined; } @@ -301,16 +291,6 @@ ol.source.TileWMS.prototype.getTilePixelSize = }; -/** - * Return the URLs used for this WMS source. - * @return {!Array.} URLs. - * @api stable - */ -ol.source.TileWMS.prototype.getUrls = function() { - return this.urls_; -}; - - /** * @private */ @@ -319,8 +299,8 @@ ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() { var res = []; var j, jj; - for (j = 0, jj = this.urls_.length; j < jj; ++j) { - res[i++] = this.urls_[j]; + for (j = 0, jj = this.urls.length; j < jj; ++j) { + res[i++] = this.urls[j]; } var key; @@ -332,29 +312,6 @@ ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() { }; -/** - * Set the URL to use for requests. - * @param {string|undefined} url URL. - * @api stable - */ -ol.source.TileWMS.prototype.setUrl = function(url) { - var urls = url !== undefined ? ol.TileUrlFunction.expandUrl(url) : null; - this.setUrls(urls); -}; - - -/** - * Set the URLs to use for requests. - * @param {Array.|undefined} urls URLs. - * @api stable - */ -ol.source.TileWMS.prototype.setUrls = function(urls) { - this.urls_ = urls || []; - this.resetCoordKeyPrefix_(); - this.changed(); -}; - - /** * @param {ol.TileCoord} tileCoord Tile coordinate. * @param {number} pixelRatio Pixel ratio. diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js index f675aca898..0df96f58ca 100644 --- a/src/ol/source/urltilesource.js +++ b/src/ol/source/urltilesource.js @@ -5,6 +5,7 @@ goog.require('ol.TileLoadFunctionType'); goog.require('ol.TileState'); goog.require('ol.TileUrlFunction'); goog.require('ol.TileUrlFunctionType'); +goog.require('ol.proj'); goog.require('ol.source.Tile'); goog.require('ol.source.TileEvent'); @@ -20,6 +21,8 @@ goog.require('ol.source.TileEvent'); * tileLoadFunction: ol.TileLoadFunctionType, * tilePixelRatio: (number|undefined), * tileUrlFunction: (ol.TileUrlFunctionType|undefined), + * url: (string|undefined), + * urls: (Array.|undefined), * wrapX: (boolean|undefined)}} */ ol.source.UrlTileOptions; @@ -51,19 +54,38 @@ ol.source.UrlTile = function(options) { wrapX: options.wrapX }); + /** + * @protected + * @type {ol.TileLoadFunctionType} + */ + this.tileLoadFunction = options.tileLoadFunction; + /** * @protected * @type {ol.TileUrlFunctionType} */ - this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? + this.tileUrlFunction = options.tileUrlFunction ? options.tileUrlFunction : ol.TileUrlFunction.nullTileUrlFunction; /** * @protected - * @type {ol.TileLoadFunctionType} + * @type {!Array.|null} */ - this.tileLoadFunction = options.tileLoadFunction; + this.urls = null; + + if (options.urls) { + if (options.tileUrlFunction) { + this.urls = options.urls; + } else { + this.setUrls(options.urls); + } + } else if (options.url) { + this.setUrl(options.url); + } + if (options.tileUrlFunction) { + this.setTileUrlFunction(options.tileUrlFunction); + } }; goog.inherits(ol.source.UrlTile, ol.source.Tile); @@ -89,6 +111,18 @@ ol.source.UrlTile.prototype.getTileUrlFunction = function() { }; +/** + * Return the URLs used for this XYZ source. + * When a tileUrlFunction is used instead of url or urls, + * null will be returned. + * @return {!Array.|null} URLs. + * @api + */ +ol.source.UrlTile.prototype.getUrls = function() { + return this.urls; +}; + + /** * Handle tile change events. * @param {goog.events.Event} event Event. @@ -140,6 +174,30 @@ ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction) { }; +/** + * Set the URL to use for requests. + * @param {string} url URL. + * @api stable + */ +ol.source.UrlTile.prototype.setUrl = function(url) { + this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates( + ol.TileUrlFunction.expandUrl(url), this.tileGrid)); + this.urls = [url]; +}; + + +/** + * Set the URLs to use for requests. + * @param {Array.} urls URLs. + * @api stable + */ +ol.source.UrlTile.prototype.setUrls = function(urls) { + this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates( + urls, this.tileGrid)); + this.urls = urls; +}; + + /** * @inheritDoc */ diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index 3fab70318c..1b4641de3a 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -42,6 +42,8 @@ ol.source.VectorTile = function(options) { options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction, tileUrlFunction: options.tileUrlFunction, tilePixelRatio: options.tilePixelRatio, + url: options.url, + urls: options.urls, wrapX: options.wrapX === undefined ? true : options.wrapX }); diff --git a/src/ol/source/wmtssource.js b/src/ol/source/wmtssource.js index b9fd7c0d8e..14483b02d5 100644 --- a/src/ol/source/wmtssource.js +++ b/src/ol/source/wmtssource.js @@ -87,12 +87,6 @@ ol.source.WMTS = function(options) { urls = ol.TileUrlFunction.expandUrl(options.url); } - /** - * @private - * @type {!Array.} - */ - this.urls_ = urls || []; - // FIXME: should we guess this requestEncoding from options.url(s) // structure? that would mean KVP only if a template is not provided. @@ -175,9 +169,9 @@ ol.source.WMTS = function(options) { }); } - var tileUrlFunction = this.urls_.length > 0 ? + var tileUrlFunction = (urls && urls.length > 0) ? ol.TileUrlFunction.createFromTileUrlFunctions( - this.urls_.map(createFromWMTSTemplate)) : + urls.map(createFromWMTSTemplate)) : ol.TileUrlFunction.nullTileUrlFunction; goog.base(this, { @@ -191,6 +185,7 @@ ol.source.WMTS = function(options) { tileLoadFunction: options.tileLoadFunction, tilePixelRatio: options.tilePixelRatio, tileUrlFunction: tileUrlFunction, + urls: urls, wrapX: options.wrapX !== undefined ? options.wrapX : false }); @@ -268,16 +263,6 @@ ol.source.WMTS.prototype.getStyle = function() { }; -/** - * Return the URLs used for this WMTS source. - * @return {!Array.} URLs. - * @api - */ -ol.source.WMTS.prototype.getUrls = function() { - return this.urls_; -}; - - /** * Return the version of the WMTS source. * @return {string} Version. diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js index 17c1f46ed7..0ca9c13cba 100644 --- a/src/ol/source/xyzsource.js +++ b/src/ol/source/xyzsource.js @@ -1,6 +1,5 @@ goog.provide('ol.source.XYZ'); -goog.require('ol.TileUrlFunction'); goog.require('ol.source.TileImage'); @@ -38,12 +37,6 @@ ol.source.XYZ = function(options) { tileSize: options.tileSize }); - /** - * @private - * @type {!Array.|null} - */ - this.urls_ = null; - goog.base(this, { attributions: options.attributions, crossOrigin: options.crossOrigin, @@ -53,52 +46,11 @@ ol.source.XYZ = function(options) { tileGrid: tileGrid, tileLoadFunction: options.tileLoadFunction, tilePixelRatio: options.tilePixelRatio, - tileUrlFunction: ol.TileUrlFunction.nullTileUrlFunction, + tileUrlFunction: options.tileUrlFunction, + url: options.url, + urls: options.urls, wrapX: options.wrapX !== undefined ? options.wrapX : true }); - if (options.tileUrlFunction !== undefined) { - this.setTileUrlFunction(options.tileUrlFunction); - } else if (options.urls !== undefined) { - this.setUrls(options.urls); - } else if (options.url !== undefined) { - this.setUrl(options.url); - } - }; goog.inherits(ol.source.XYZ, ol.source.TileImage); - - -/** - * Return the URLs used for this XYZ source. - * When a tileUrlFunction is used instead of url or urls, - * null will be returned. - * @return {!Array.|null} URLs. - * @api - */ -ol.source.XYZ.prototype.getUrls = function() { - return this.urls_; -}; - - -/** - * Set the URL to use for requests. - * @param {string} url URL. - * @api stable - */ -ol.source.XYZ.prototype.setUrl = function(url) { - this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates( - ol.TileUrlFunction.expandUrl(url), this.tileGrid)); - this.urls_ = [url]; -}; - - -/** - * Set the URLs to use for requests. - * @param {Array.} urls URLs. - */ -ol.source.XYZ.prototype.setUrls = function(urls) { - this.setTileUrlFunction( - ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid)); - this.urls_ = urls; -}; From 66338a662d774d12f4158ac6851409cfa823c29b Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 5 Oct 2015 22:52:14 +0200 Subject: [PATCH 18/27] Fix comparison of projections with same code but different units --- src/ol/proj/proj.js | 3 ++- test/spec/ol/proj/proj.test.js | 13 +++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) diff --git a/src/ol/proj/proj.js b/src/ol/proj/proj.js index 193bc838d3..499fb5eb0e 100644 --- a/src/ol/proj/proj.js +++ b/src/ol/proj/proj.js @@ -677,7 +677,8 @@ ol.proj.get = function(projectionLike) { ol.proj.equivalent = function(projection1, projection2) { if (projection1 === projection2) { return true; - } else if (projection1.getCode() === projection2.getCode()) { + } else if (projection1.getCode() === projection2.getCode() && + projection1.getUnits() === projection2.getUnits()) { return true; } else if (projection1.getUnits() != projection2.getUnits()) { return false; diff --git a/test/spec/ol/proj/proj.test.js b/test/spec/ol/proj/proj.test.js index a7dfe204ee..d792b4b07a 100644 --- a/test/spec/ol/proj/proj.test.js +++ b/test/spec/ol/proj/proj.test.js @@ -56,6 +56,19 @@ describe('ol.proj', function() { 'EPSG:4326' ]); }); + + it('requires code and units to be equal for projection evquivalence', + function() { + var proj1 = new ol.proj.Projection({ + code: 'EPSG:3857', + units: 'm' + }); + var proj2 = new ol.proj.Projection({ + code: 'EPSG:3857', + units: 'tile-pixels' + }); + expect(ol.proj.equivalent(proj1, proj2)).to.not.be.ok(); + }); }); describe('identify transform', function() { From 8daff341d02c472f4e470625c5e09b4b21368977 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Mon, 12 Oct 2015 22:40:01 +0200 Subject: [PATCH 19/27] Update after ol.interaction.Select changes --- src/ol/interaction/selectinteraction.js | 6 +++--- .../ol/interaction/modifyinteraction.test.js | 16 +++++++++------- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/src/ol/interaction/selectinteraction.js b/src/ol/interaction/selectinteraction.js index d527dcc248..82cb617c20 100644 --- a/src/ol/interaction/selectinteraction.js +++ b/src/ol/interaction/selectinteraction.js @@ -212,7 +212,7 @@ goog.inherits(ol.interaction.Select, ol.interaction.Interaction); /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @param {ol.layer.Layer} layer Layer. * @private */ @@ -236,7 +236,7 @@ ol.interaction.Select.prototype.getFeatures = function() { /** * Returns the associated {@link ol.layer.Vector vectorlayer} of * the (last) selected feature. - * @param {ol.Feature} feature Feature + * @param {ol.Feature|ol.render.Feature} feature Feature * @return {ol.layer.Vector} Layer. * @api */ @@ -414,7 +414,7 @@ ol.interaction.Select.prototype.removeFeature_ = function(evt) { /** - * @param {ol.Feature} feature Feature. + * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.interaction.Select.prototype.removeFeatureLayerAssociation_ = diff --git a/test/spec/ol/interaction/modifyinteraction.test.js b/test/spec/ol/interaction/modifyinteraction.test.js index e1692e4be2..511eee9ec5 100644 --- a/test/spec/ol/interaction/modifyinteraction.test.js +++ b/test/spec/ol/interaction/modifyinteraction.test.js @@ -150,19 +150,21 @@ describe('ol.interaction.Modify', function() { it('works when clicking on a shared vertex', function() { features.push(features[0].clone()); + var first = features[0]; + var firstRevision = first.getGeometry().getRevision(); + var second = features[1]; + var secondRevision = second.getGeometry().getRevision(); + var modify = new ol.interaction.Modify({ features: new ol.Collection(features) }); map.addInteraction(modify); - var first = features[0]; - var second = features[1]; - events = trackEvents(first, modify); - expect(first.getGeometry().getRevision()).to.equal(1); + expect(first.getGeometry().getRevision()).to.equal(firstRevision); expect(first.getGeometry().getCoordinates()[0]).to.have.length(5); - expect(second.getGeometry().getRevision()).to.equal(2); + expect(second.getGeometry().getRevision()).to.equal(secondRevision); expect(second.getGeometry().getCoordinates()[0]).to.have.length(5); simulateEvent('pointerdown', 10, -20, false, 0); @@ -170,9 +172,9 @@ describe('ol.interaction.Modify', function() { simulateEvent('click', 10, -20, false, 0); simulateEvent('singleclick', 10, -20, false, 0); - expect(first.getGeometry().getRevision()).to.equal(2); + expect(first.getGeometry().getRevision()).to.equal(firstRevision + 1); expect(first.getGeometry().getCoordinates()[0]).to.have.length(4); - expect(second.getGeometry().getRevision()).to.equal(3); + expect(second.getGeometry().getRevision()).to.equal(secondRevision + 1); expect(second.getGeometry().getCoordinates()[0]).to.have.length(4); validateEvents(events, features); From b99954e93ae7ca891764a59ff0b8abdf3dcc1c24 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 14 Oct 2015 15:46:58 +0200 Subject: [PATCH 20/27] Cloak Mapbox access tokens --- examples/mapbox-vector-tiles-simple.html | 2 ++ examples/mapbox-vector-tiles-simple.js | 6 ++---- examples/mapbox-vector-tiles.html | 4 +++- examples/mapbox-vector-tiles.js | 6 ++---- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/examples/mapbox-vector-tiles-simple.html b/examples/mapbox-vector-tiles-simple.html index 36da8db103..9a9d32764a 100644 --- a/examples/mapbox-vector-tiles-simple.html +++ b/examples/mapbox-vector-tiles-simple.html @@ -7,6 +7,8 @@ docs: > 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 index 3d68525c4d..783e2462af 100644 --- a/examples/mapbox-vector-tiles-simple.js +++ b/examples/mapbox-vector-tiles-simple.js @@ -11,9 +11,7 @@ goog.require('ol.style.Style'); goog.require('ol.style.Text'); -// Mapbox access token - request your own at http://mapbox.com -var accessToken = - 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; +var key = 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; var map = new ol.Map({ layers: [ @@ -28,7 +26,7 @@ var map = new ol.Map({ 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=' + accessToken + '{z}/{x}/{y}.vector.pbf?access_token=' + key }), style: createMapboxStreetsV6Style() }) diff --git a/examples/mapbox-vector-tiles.html b/examples/mapbox-vector-tiles.html index c7cb0b45d3..12aa0017fc 100644 --- a/examples/mapbox-vector-tiles.html +++ b/examples/mapbox-vector-tiles.html @@ -3,10 +3,12 @@ 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**: Make sure to get your own Mapbox API key when using this example. No map will be visible when the API key has expired. + 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: "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.js b/examples/mapbox-vector-tiles.js index 97529fd143..aaa0736fcc 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -13,9 +13,7 @@ goog.require('ol.style.Text'); goog.require('ol.tilegrid.TileGrid'); -// Mapbox access token - request your own at http://mapbox.com -var accessToken = - 'pk.eyJ1IjoiYWhvY2V2YXIiLCJhIjoiRk1kMWZaSSJ9.E5BkluenyWQMsBLsuByrmg'; +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 @@ -34,7 +32,7 @@ for (var z = zoomOffset / reuseZoomLevels; z <= 22 / reuseZoomLevels; ++z) { } function tileUrlFunction(tileCoord) { return ('http://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + - '{z}/{x}/{y}.vector.pbf?access_token=' + accessToken) + '{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)) From 656023e56933e7d06e49f82b390f3307b01f43ed Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 16 Oct 2015 23:36:23 +0200 Subject: [PATCH 21/27] Use new createLoadedTileFinder signature The signature of this method was changed with the introduction of raster reprojection. --- src/ol/renderer/canvas/canvasvectortilelayerrenderer.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index a990815b5c..6a24b12467 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -367,7 +367,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = var tilesToDrawByZ = {}; tilesToDrawByZ[z] = {}; - var findLoadedTiles = this.createLoadedTileFinder(source, tilesToDrawByZ); + var findLoadedTiles = this.createLoadedTileFinder(source, projection, + tilesToDrawByZ); var useInterimTilesOnError = layer.getUseInterimTilesOnError(); From 80fa26ddd878158db61bbc452473dc7dbf75c75e Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sat, 17 Oct 2015 23:25:14 +0200 Subject: [PATCH 22/27] Reduce the use of goog.* --- src/ol/featureloader.js | 2 +- src/ol/format/mvtformat.js | 15 +++++----- src/ol/layer/vectortilelayer.js | 6 ++-- src/ol/render/canvas/canvasreplay.js | 2 +- src/ol/render/renderfeature.js | 2 +- .../canvas/canvasvectortilelayerrenderer.js | 29 +++++++++---------- src/ol/source/tileimagesource.js | 2 +- src/ol/source/urltilesource.js | 2 +- src/ol/source/vectortilesource.js | 17 +++++------ 9 files changed, 36 insertions(+), 41 deletions(-) diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index 7c6d1c8582..34ceaa55fc 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -171,5 +171,5 @@ ol.featureloader.xhr = function(url, format) { */ function(features) { this.addFeatures(features); - }, goog.nullFunction); + }, ol.nullFunction); }; diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js index e22535bf83..65d3806aaf 100644 --- a/src/ol/format/mvtformat.js +++ b/src/ol/format/mvtformat.js @@ -2,7 +2,6 @@ goog.provide('ol.format.MVT'); -goog.require('goog.array'); goog.require('goog.asserts'); goog.require('ol.Feature'); goog.require('ol.ext.pbf'); @@ -37,7 +36,7 @@ ol.format.MVT = function(opt_options) { goog.base(this); - var options = goog.isDef(opt_options) ? opt_options : {}; + var options = opt_options ? opt_options : {}; /** * @type {ol.proj.Projection} @@ -53,27 +52,27 @@ ol.format.MVT = function(opt_options) { * function(ol.geom.GeometryType,Array., * (Array.|Array.>),Object.)} */ - this.featureClass_ = goog.isDef(options.featureClass) ? + this.featureClass_ = options.featureClass ? options.featureClass : ol.render.Feature; /** * @private * @type {string} */ - this.geometryName_ = goog.isDef(options.geometryName) ? + this.geometryName_ = options.geometryName ? options.geometryName : 'geometry'; /** * @private * @type {string} */ - this.layerName_ = goog.isDef(options.layerName) ? options.layerName : 'layer'; + this.layerName_ = options.layerName ? options.layerName : 'layer'; /** * @private * @type {Array.} */ - this.layers_ = goog.isDef(options.layers) ? options.layers : null; + this.layers_ = options.layers ? options.layers : null; this.rightHandedPolygons = true; @@ -104,7 +103,7 @@ ol.format.MVT.prototype.readFeature_ = function( var geometry = ol.format.Feature.transformWithOptions( ol.format.MVT.readGeometry_(rawFeature), false, this.adaptOptions(opt_options)); - if (!goog.isNull(geometry)) { + if (geometry) { goog.asserts.assertInstanceof(geometry, ol.geom.Geometry); values[this.geometryName_] = geometry; } @@ -176,7 +175,7 @@ ol.format.MVT.prototype.readFeatures = function(source, opt_options) { var featureClass = this.featureClass_; var layer, feature; for (var name in tile.layers) { - if (!goog.isNull(layers) && !goog.array.contains(layers, name)) { + if (layers && layers.indexOf(name) == -1) { continue; } layer = tile.layers[name]; diff --git a/src/ol/layer/vectortilelayer.js b/src/ol/layer/vectortilelayer.js index 31797e1170..fb158e6133 100644 --- a/src/ol/layer/vectortilelayer.js +++ b/src/ol/layer/vectortilelayer.js @@ -27,7 +27,7 @@ ol.layer.VectorTileProperty = { * @api */ ol.layer.VectorTile = function(opt_options) { - var options = goog.isDef(opt_options) ? opt_options : {}; + var options = opt_options ? opt_options : {}; var baseOptions = goog.object.clone(options); @@ -35,8 +35,8 @@ ol.layer.VectorTile = function(opt_options) { delete baseOptions.useInterimTilesOnError; goog.base(this, /** @type {olx.layer.VectorOptions} */ (baseOptions)); - this.setPreload(goog.isDef(options.preload) ? options.preload : 0); - this.setUseInterimTilesOnError(goog.isDef(options.useInterimTilesOnError) ? + this.setPreload(options.preload ? options.preload : 0); + this.setUseInterimTilesOnError(options.useInterimTilesOnError ? options.useInterimTilesOnError : true); }; diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index 9428b30044..9221527e11 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1884,7 +1884,7 @@ ol.render.canvas.ReplayGroup = function( * @private * @type {boolean} */ - this.rightHandedPolygons_ = goog.isDef(opt_rightHandedPolygons) ? + this.rightHandedPolygons_ = opt_rightHandedPolygons ? opt_rightHandedPolygons : false; }; diff --git a/src/ol/render/renderfeature.js b/src/ol/render/renderfeature.js index 4a147fed7d..7b6001ae8c 100644 --- a/src/ol/render/renderfeature.js +++ b/src/ol/render/renderfeature.js @@ -143,7 +143,7 @@ ol.render.Feature.prototype.getStride = goog.functions.constant(2); /** * @return {undefined} */ -ol.render.Feature.prototype.getStyleFunction = goog.nullFunction; +ol.render.Feature.prototype.getStyleFunction = ol.nullFunction; /** diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 6a24b12467..cdbc9d76bf 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -1,9 +1,7 @@ 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.Feature'); goog.require('ol.TileRange'); @@ -163,7 +161,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, layer, pixelRatio) { var revision = layer.getRevision(); var renderOrder = layer.getRenderOrder(); - if (!goog.isDef(renderOrder)) { + if (renderOrder === undefined) { renderOrder = null; } @@ -207,13 +205,13 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, */ function renderFeature(feature) { var styles; - if (goog.isDef(feature.getStyleFunction())) { + if (feature.getStyleFunction()) { goog.asserts.assertInstanceof(feature, ol.Feature, 'Got an ol.Feature'); styles = feature.getStyleFunction().call(feature, resolution); - } else if (goog.isDef(layer.getStyleFunction())) { + } else if (layer.getStyleFunction()) { styles = layer.getStyleFunction()(feature, resolution); } - if (goog.isDefAndNotNull(styles)) { + if (styles) { var dirty = this.renderFeature(feature, squaredTolerance, styles, replayGroup); this.dirty_ = this.dirty_ || dirty; @@ -222,11 +220,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, } var features = tile.getFeatures(); - if (!goog.isNull(renderOrder) && - renderOrder !== replayState.renderedRenderOrder) { - goog.array.sort(features, renderOrder); + if (renderOrder && renderOrder !== replayState.renderedRenderOrder) { + features.sort(renderOrder); } - goog.array.forEach(features, renderFeature, this); + features.forEach(renderFeature, this); replayGroup.finish(); @@ -286,7 +283,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = * @return {?} Callback result. */ function(feature) { - goog.asserts.assert(goog.isDef(feature), 'received a feature'); + goog.asserts.assert(feature, 'received a feature'); var key = goog.getUid(feature).toString(); if (!(key in features)) { features[key] = true; @@ -336,7 +333,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = } var extent = frameState.extent; - if (goog.isDef(layerState.extent)) { + if (layerState.extent) { extent = ol.extent.getIntersection(extent, layerState.extent); } if (ol.extent.isEmpty(extent)) { @@ -394,7 +391,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = if (!fullyLoaded) { childTileRange = tileGrid.getTileCoordChildTileRange( tile.tileCoord, tmpTileRange, tmpExtent); - if (!goog.isNull(childTileRange)) { + if (childTileRange) { findLoadedTiles(z + 1, childTileRange); } } @@ -405,8 +402,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = this.dirty_ = false; /** @type {Array.} */ - var zs = goog.array.map(goog.object.getKeys(tilesToDrawByZ), Number); - goog.array.sort(zs); + var zs = Object.keys(tilesToDrawByZ).map(Number); + zs.sort(); var replayables = []; var i, ii, currentZ, tileCoordKey, tilesToDraw; for (i = 0, ii = zs.length; i < ii; ++i) { @@ -436,7 +433,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.prepareFrame = */ ol.renderer.canvas.VectorTileLayer.prototype.renderFeature = function(feature, squaredTolerance, styles, replayGroup) { - if (!goog.isDefAndNotNull(styles)) { + if (!styles) { return false; } var i, ii, loading = false; diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js index c4964374ef..6622a0b762 100644 --- a/src/ol/source/tileimagesource.js +++ b/src/ol/source/tileimagesource.js @@ -34,7 +34,7 @@ ol.source.TileImage = function(options) { state: options.state !== undefined ? /** @type {ol.source.State} */ (options.state) : undefined, tileGrid: options.tileGrid, - tileLoadFunction: goog.isDef(options.tileLoadFunction) ? + tileLoadFunction: options.tileLoadFunction ? options.tileLoadFunction : ol.source.TileImage.defaultTileLoadFunction, tilePixelRatio: options.tilePixelRatio, tileUrlFunction: options.tileUrlFunction, diff --git a/src/ol/source/urltilesource.js b/src/ol/source/urltilesource.js index 0df96f58ca..1ed9338ccf 100644 --- a/src/ol/source/urltilesource.js +++ b/src/ol/source/urltilesource.js @@ -47,7 +47,7 @@ ol.source.UrlTile = function(options) { logo: options.logo, opaque: options.opaque, projection: options.projection, - state: goog.isDef(options.state) ? + state: options.state ? /** @type {ol.source.State} */ (options.state) : undefined, tileGrid: options.tileGrid, tilePixelRatio: options.tilePixelRatio, diff --git a/src/ol/source/vectortilesource.js b/src/ol/source/vectortilesource.js index 1b4641de3a..5fb8ae4435 100644 --- a/src/ol/source/vectortilesource.js +++ b/src/ol/source/vectortilesource.js @@ -35,10 +35,10 @@ ol.source.VectorTile = function(options) { logo: options.logo, opaque: options.opaque, projection: options.projection, - state: goog.isDef(options.state) ? + state: options.state ? /** @type {ol.source.State} */ (options.state) : undefined, tileGrid: options.tileGrid, - tileLoadFunction: goog.isDef(options.tileLoadFunction) ? + tileLoadFunction: options.tileLoadFunction ? options.tileLoadFunction : ol.source.VectorTile.defaultTileLoadFunction, tileUrlFunction: options.tileUrlFunction, tilePixelRatio: options.tilePixelRatio, @@ -51,15 +51,14 @@ ol.source.VectorTile = function(options) { * @private * @type {ol.format.Feature} */ - this.format_ = goog.isDef(options.format) ? options.format : null; + this.format_ = options.format ? options.format : null; /** * @protected * @type {function(new: ol.VectorTile, ol.TileCoord, ol.TileState, string, * ol.format.Feature, ol.TileLoadFunctionType)} */ - this.tileClass = goog.isDef(options.tileClass) ? - options.tileClass : ol.VectorTile; + this.tileClass = options.tileClass ? options.tileClass : ol.VectorTile; }; goog.inherits(ol.source.VectorTile, ol.source.UrlTile); @@ -78,12 +77,12 @@ ol.source.VectorTile.prototype.getTile = var tileCoord = [z, x, y]; var urlTileCoord = this.getTileCoordForTileUrlFunction( tileCoord, projection); - var tileUrl = goog.isNull(urlTileCoord) ? undefined : - this.tileUrlFunction(urlTileCoord, pixelRatio, projection); + var tileUrl = urlTileCoord ? + this.tileUrlFunction(urlTileCoord, pixelRatio, projection) : undefined; var tile = new this.tileClass( tileCoord, - goog.isDef(tileUrl) ? ol.TileState.IDLE : ol.TileState.EMPTY, - goog.isDef(tileUrl) ? tileUrl : '', + tileUrl !== undefined ? ol.TileState.IDLE : ol.TileState.EMPTY, + tileUrl !== undefined ? tileUrl : '', this.format_, this.tileLoadFunction); goog.events.listen(tile, goog.events.EventType.CHANGE, From 4be89715edd836628caefd8e82039b6122dcd221 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 23 Oct 2015 12:53:03 +0200 Subject: [PATCH 23/27] Add credit/attribution for the example style function --- examples/resources/mapbox-streets-v6-style.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/examples/resources/mapbox-streets-v6-style.js b/examples/resources/mapbox-streets-v6-style.js index c7f6c4ad2f..af123f84b9 100644 --- a/examples/resources/mapbox-streets-v6-style.js +++ b/examples/resources/mapbox-streets-v6-style.js @@ -1,3 +1,6 @@ +// Styles for the mapbox-streets-v6 vector tile data set. Loosely based on +// http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6.json + function createMapboxStreetsV6Style() { var fill = new ol.style.Fill({color: ''}); var stroke = new ol.style.Stroke({color: '', width: 1}); From 2b2ac47b1f8b7d102eb42bc2d7a47dc41c2c2bab Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 27 Oct 2015 18:45:03 +0100 Subject: [PATCH 24/27] Remove ol.source.TileVector --- changelog/upgrade-notes.md | 4 + examples/tile-vector.css | 4 - examples/tile-vector.html | 23 -- examples/tile-vector.js | 168 --------- externs/olx.js | 92 ----- .../canvas/canvasvectorlayerrenderer.js | 5 +- .../canvas/canvasvectortilelayerrenderer.js | 4 +- src/ol/renderer/dom/domvectorlayerrenderer.js | 5 +- .../webgl/webglvectorlayerrenderer.js | 5 +- src/ol/source/imagevectorsource.js | 2 +- src/ol/source/tilevectorsource.js | 354 ------------------ src/ol/source/vectorsource.js | 14 - test/spec/ol/source/tilevectorsource.test.js | 105 ------ test/spec/ol/source/vectorsource.test.js | 11 + 14 files changed, 24 insertions(+), 772 deletions(-) delete mode 100644 examples/tile-vector.css delete mode 100644 examples/tile-vector.html delete mode 100644 examples/tile-vector.js delete mode 100644 src/ol/source/tilevectorsource.js delete mode 100644 test/spec/ol/source/tilevectorsource.test.js diff --git a/changelog/upgrade-notes.md b/changelog/upgrade-notes.md index 88f1ad372a..bd86f3b6d6 100644 --- a/changelog/upgrade-notes.md +++ b/changelog/upgrade-notes.md @@ -42,6 +42,10 @@ but with additional css: } ``` +#### Removal of `ol.source.TileVector` + +With the introduction of true vector tile support, `ol.source.TileVector` becomes obsolete. Change your code to use `ol.layer.VectorTile` and `ol.source.VectorTile` instead of `ol.layer.Vector` and `ol.source.TileVector`. + ### v3.10.0 #### `ol.layer.Layer` changes diff --git a/examples/tile-vector.css b/examples/tile-vector.css deleted file mode 100644 index e038ad375e..0000000000 --- a/examples/tile-vector.css +++ /dev/null @@ -1,4 +0,0 @@ - #map { - max-width: 600px; - margin: 0 auto; - } \ No newline at end of file diff --git a/examples/tile-vector.html b/examples/tile-vector.html deleted file mode 100644 index 3fa660c722..0000000000 --- a/examples/tile-vector.html +++ /dev/null @@ -1,23 +0,0 @@ ---- -template: example.html -title: Tile vector example -shortdesc: Example of vector tiles from openstreetmap.us. -docs: > - Example of vector tiles from openstreetmap.us. -tags: "custom, control" ---- -
-
-
-
-
-
- Warning Map is becoming unresponsive with too many layers. -
-
- Layers - - - - -
diff --git a/examples/tile-vector.js b/examples/tile-vector.js deleted file mode 100644 index 40644545fd..0000000000 --- a/examples/tile-vector.js +++ /dev/null @@ -1,168 +0,0 @@ -goog.require('ol.Map'); -goog.require('ol.View'); -goog.require('ol.format.TopoJSON'); -goog.require('ol.layer.Vector'); -goog.require('ol.proj'); -goog.require('ol.source.TileVector'); -goog.require('ol.style.Fill'); -goog.require('ol.style.Stroke'); -goog.require('ol.style.Style'); - -var waterLayer = new ol.layer.Vector({ - source: new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - projection: 'EPSG:3857', - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - url: 'http://{a-c}.tile.openstreetmap.us/' + - 'vectiles-water-areas/{z}/{x}/{y}.topojson' - }), - style: new ol.style.Style({ - fill: new ol.style.Fill({ - color: '#9db9e8' - }) - }) -}); - -var roadStyleCache = {}; -var roadLayer = new ol.layer.Vector({ - source: new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - projection: 'EPSG:3857', - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - url: 'http://{a-c}.tile.openstreetmap.us/' + - 'vectiles-highroad/{z}/{x}/{y}.topojson' - }), - style: function(feature, resolution) { - var kind = feature.get('kind'); - var railway = feature.get('railway'); - var sort_key = feature.get('sort_key'); - var styleKey = kind + '/' + railway + '/' + sort_key; - var styleArray = roadStyleCache[styleKey]; - if (!styleArray) { - var color, width; - if (railway) { - color = '#7de'; - width = 1; - } else { - color = { - 'major_road': '#776', - 'minor_road': '#ccb', - 'highway': '#f39' - }[kind]; - width = kind == 'highway' ? 1.5 : 1; - } - styleArray = [new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: color, - width: width - }), - zIndex: sort_key - })]; - roadStyleCache[styleKey] = styleArray; - } - return styleArray; - } -}); - -var buildingStyle = [ - new ol.style.Style({ - fill: new ol.style.Fill({ - color: '#666', - opacity: 0.4 - }), - stroke: new ol.style.Stroke({ - color: '#444', - width: 1 - }) - }) -]; -var buildingLayer = new ol.layer.Vector({ - source: new ol.source.TileVector({ - format: new ol.format.TopoJSON({ - defaultProjection: 'EPSG:4326' - }), - projection: 'EPSG:3857', - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - url: 'http://{a-c}.tile.openstreetmap.us/' + - 'vectiles-buildings/{z}/{x}/{y}.topojson' - }), - visible: false, - style: function(f, resolution) { - return (resolution < 10) ? buildingStyle : []; - } -}); - -var landuseStyleCache = {}; -var landuseLayer = new ol.layer.Vector({ - source: new ol.source.TileVector({ - format: new ol.format.TopoJSON({ - defaultProjection: 'EPSG:4326' - }), - projection: 'EPSG:3857', - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - url: 'http://{a-c}.tile.openstreetmap.us/' + - 'vectiles-land-usages/{z}/{x}/{y}.topojson' - }), - visible: false, - style: function(feature, resolution) { - var kind = feature.get('kind'); - var styleKey = kind; - var styleArray = landuseStyleCache[styleKey]; - if (!styleArray) { - var color, width; - color = { - 'parking': '#ddd', - 'industrial': '#aaa', - 'urban area': '#aaa', - 'park': '#76C759', - 'school': '#DA10E7', - 'garden': '#76C759', - 'pitch': '#D58F8D', - 'scrub': '#3E7D28', - 'residential': '#4C9ED9' - }[kind]; - width = kind == 'highway' ? 1.5 : 1; - styleArray = [new ol.style.Style({ - stroke: new ol.style.Stroke({ - color: color, - width: width - }), - fill: new ol.style.Fill({ - color: color, - opacity: 0.5 - }) - })]; - landuseStyleCache[styleKey] = styleArray; - } - return styleArray; - } -}); - -var map = new ol.Map({ - layers: [landuseLayer, buildingLayer, waterLayer, roadLayer], - renderer: 'canvas', - target: document.getElementById('map'), - view: new ol.View({ - center: ol.proj.fromLonLat([-74.0064, 40.7142]), - maxZoom: 19, - zoom: 15 - }) -}); - -$('input[type=checkbox]').on('change', function() { - var layer = { - landuse: landuseLayer, - buildings: buildingLayer, - water: waterLayer, - roads: roadLayer - }[$(this).attr('id')]; - layer.setVisible(!layer.getVisible()); -}); diff --git a/externs/olx.js b/externs/olx.js index fd68460f29..51936fde18 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4263,98 +4263,6 @@ olx.source.VectorTileOptions.prototype.urls; olx.source.VectorTileOptions.prototype.wrapX; -/** - * @typedef {{attributions: (Array.|undefined), - * format: (ol.format.Feature|undefined), - * logo: (string|olx.LogoOptions|undefined), - * tileGrid: ol.tilegrid.TileGrid, - * tileUrlFunction: (ol.TileUrlFunctionType|undefined), - * tileLoadFunction: (ol.TileVectorLoadFunctionType|undefined), - * url: (string|undefined), - * urls: (Array.|undefined), - * wrapX: (boolean|undefined)}} - * @api - */ -olx.source.TileVectorOptions; - - -/** - * Attributions. - * @type {Array.|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.attributions; - - -/** - * Format. Required unless tileLoadFunction is used. - * @type {ol.format.Feature|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.format; - - -/** - * Logo. - * @type {string|olx.LogoOptions|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.logo; - - -/** - * Tile grid. - * @type {ol.tilegrid.TileGrid} - * @api - */ -olx.source.TileVectorOptions.prototype.tileGrid; - - -/** - * Optional function to get tile URL given a tile coordinate and the projection. - * Required if url or urls are not provided. - * @type {ol.TileUrlFunctionType|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.tileUrlFunction; - - -/** - * Optional function to override the default loading and format parsing behaviour. - * If this option is used format is ignored and the provided function will be - * responsible for data retrieval and transformation into features. - * @type {ol.TileVectorLoadFunctionType|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.tileLoadFunction; - - -/** - * URL template. Must include `{x}`, `{y}` or `{-y}`, and `{z}` placeholders. - * @type {string|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.url; - - -/** - * An array of URL templates. - * @type {Array.|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.urls; - - -/** - * Wrap the world horizontally. Default is `true`. For vector editing across the - * -180° and 180° meridians to work properly, this should be set to `false`. The - * resulting geometry coordinates will then exceed the world bounds. - * @type {boolean|undefined} - * @api - */ -olx.source.TileVectorOptions.prototype.wrapX; - - /** * @typedef {{url: (string|undefined), * displayDpi: (number|undefined), diff --git a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js index 5784df90af..d4652c81ba 100644 --- a/src/ol/renderer/canvas/canvasvectorlayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectorlayerrenderer.js @@ -293,7 +293,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = if (vectorLayerRenderOrder) { /** @type {Array.} */ var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + vectorSource.forEachFeatureInExtent(extent, /** * @param {ol.Feature} feature Feature. */ @@ -303,8 +303,7 @@ ol.renderer.canvas.VectorLayer.prototype.prepareFrame = goog.array.sort(features, vectorLayerRenderOrder); features.forEach(renderFeature, this); } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); } replayGroup.finish(); diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index cdbc9d76bf..c0d91b903d 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -247,6 +247,8 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = var replayables = this.renderedTiles_; var source = layer.getSource(); + goog.asserts.assertInstanceof(source, ol.source.VectorTile, + 'Source is an ol.source.VectorTile'); var tileGrid = source.getTileGrid(); var found, tileSpaceCoordinate; var i, ii, origin, replayGroup; @@ -254,8 +256,6 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = for (i = 0, ii = replayables.length; i < ii; ++i) { tile = replayables[i]; tileCoord = tile.getTileCoord(); - goog.asserts.assertInstanceof(source, ol.source.VectorTile, - 'Source is an ol.source.VectorTile'); tileExtent = source.getTileGrid().getTileCoordExtent(tileCoord, this.tmpExtent_); if (!ol.extent.containsCoordinate(tileExtent, coordinate)) { diff --git a/src/ol/renderer/dom/domvectorlayerrenderer.js b/src/ol/renderer/dom/domvectorlayerrenderer.js index 3a221b9376..e44c5f5ad6 100644 --- a/src/ol/renderer/dom/domvectorlayerrenderer.js +++ b/src/ol/renderer/dom/domvectorlayerrenderer.js @@ -301,7 +301,7 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame = if (vectorLayerRenderOrder) { /** @type {Array.} */ var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + vectorSource.forEachFeatureInExtent(extent, /** * @param {ol.Feature} feature Feature. */ @@ -311,8 +311,7 @@ ol.renderer.dom.VectorLayer.prototype.prepareFrame = goog.array.sort(features, vectorLayerRenderOrder); features.forEach(renderFeature, this); } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); } replayGroup.finish(); diff --git a/src/ol/renderer/webgl/webglvectorlayerrenderer.js b/src/ol/renderer/webgl/webglvectorlayerrenderer.js index 211a9f37ed..ae84186a9e 100644 --- a/src/ol/renderer/webgl/webglvectorlayerrenderer.js +++ b/src/ol/renderer/webgl/webglvectorlayerrenderer.js @@ -269,7 +269,7 @@ ol.renderer.webgl.VectorLayer.prototype.prepareFrame = if (vectorLayerRenderOrder) { /** @type {Array.} */ var features = []; - vectorSource.forEachFeatureInExtentAtResolution(extent, resolution, + vectorSource.forEachFeatureInExtent(extent, /** * @param {ol.Feature} feature Feature. */ @@ -279,8 +279,7 @@ ol.renderer.webgl.VectorLayer.prototype.prepareFrame = goog.array.sort(features, vectorLayerRenderOrder); features.forEach(renderFeature, this); } else { - vectorSource.forEachFeatureInExtentAtResolution( - extent, resolution, renderFeature, this); + vectorSource.forEachFeatureInExtent(extent, renderFeature, this); } replayGroup.finish(context); diff --git a/src/ol/source/imagevectorsource.js b/src/ol/source/imagevectorsource.js index c02a618b50..ccfddf1ffd 100644 --- a/src/ol/source/imagevectorsource.js +++ b/src/ol/source/imagevectorsource.js @@ -116,7 +116,7 @@ ol.source.ImageVector.prototype.canvasFunctionInternal_ = this.source_.loadFeatures(extent, resolution, projection); var loading = false; - this.source_.forEachFeatureInExtentAtResolution(extent, resolution, + this.source_.forEachFeatureInExtent(extent, /** * @param {ol.Feature} feature Feature. */ diff --git a/src/ol/source/tilevectorsource.js b/src/ol/source/tilevectorsource.js deleted file mode 100644 index 1ca9c23522..0000000000 --- a/src/ol/source/tilevectorsource.js +++ /dev/null @@ -1,354 +0,0 @@ -goog.provide('ol.source.TileVector'); - -goog.require('goog.array'); -goog.require('goog.asserts'); -goog.require('goog.object'); -goog.require('ol.TileUrlFunction'); -goog.require('ol.featureloader'); -goog.require('ol.source.State'); -goog.require('ol.source.Vector'); -goog.require('ol.tilecoord'); -goog.require('ol.tilegrid.TileGrid'); - - - -/** - * @classdesc - * A vector source in one of the supported formats, where the data is divided - * into tiles in a fixed grid pattern. - * - * @constructor - * @extends {ol.source.Vector} - * @param {olx.source.TileVectorOptions} options Options. - * @api - */ -ol.source.TileVector = function(options) { - - goog.base(this, { - attributions: options.attributions, - logo: options.logo, - projection: undefined, - state: ol.source.State.READY, - wrapX: options.wrapX - }); - - /** - * @private - * @type {ol.format.Feature|undefined} - */ - this.format_ = options.format !== undefined ? options.format : null; - - /** - * @private - * @type {ol.tilegrid.TileGrid} - */ - this.tileGrid_ = options.tileGrid; - - /** - * @private - * @type {ol.TileUrlFunctionType} - */ - this.tileUrlFunction_ = ol.TileUrlFunction.nullTileUrlFunction; - - /** - * @private - * @type {?ol.TileVectorLoadFunctionType} - */ - this.tileLoadFunction_ = options.tileLoadFunction !== undefined ? - options.tileLoadFunction : null; - - goog.asserts.assert(this.format_ || this.tileLoadFunction_, - 'Either format or tileLoadFunction are required'); - - /** - * @private - * @type {Object.>} - */ - this.tiles_ = {}; - - if (options.tileUrlFunction !== undefined) { - this.setTileUrlFunction(options.tileUrlFunction); - } else if (options.urls !== undefined) { - this.setUrls(options.urls); - } else if (options.url !== undefined) { - this.setUrl(options.url); - } - -}; -goog.inherits(ol.source.TileVector, ol.source.Vector); - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.addFeature = goog.abstractMethod; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.addFeatures = goog.abstractMethod; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.clear = function() { - goog.object.clear(this.tiles_); -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.forEachFeature = goog.abstractMethod; - - -/** - * Iterate through all features whose geometries contain the provided - * coordinate at the provided resolution, calling the callback with each - * feature. If the callback returns a "truthy" value, iteration will stop and - * the function will return the same value. - * - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} resolution Resolution. - * @param {function(this: T, ol.Feature): S} callback Called with each feature - * whose goemetry contains the provided coordinate. - * @param {T=} opt_this The object to use as `this` in the callback. - * @return {S|undefined} The return value from the last call to the callback. - * @template T,S - */ -ol.source.TileVector.prototype.forEachFeatureAtCoordinateAndResolution = - function(coordinate, resolution, callback, opt_this) { - - var tileGrid = this.tileGrid_; - var tiles = this.tiles_; - var tileCoord = tileGrid.getTileCoordForCoordAndResolution(coordinate, - resolution); - - var tileKey = this.getTileKeyZXY_(tileCoord[0], tileCoord[1], tileCoord[2]); - var features = tiles[tileKey]; - if (features !== undefined) { - var i, ii; - for (i = 0, ii = features.length; i < ii; ++i) { - var feature = features[i]; - var geometry = feature.getGeometry(); - goog.asserts.assert(geometry, 'feature geometry is defined and not null'); - if (geometry.containsCoordinate(coordinate)) { - var result = callback.call(opt_this, feature); - if (result) { - return result; - } - } - } - } - return undefined; -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.forEachFeatureInExtent = goog.abstractMethod; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.forEachFeatureInExtentAtResolution = - function(extent, resolution, f, opt_this) { - var tileGrid = this.tileGrid_; - var tiles = this.tiles_; - var z = tileGrid.getZForResolution(resolution); - var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - var x, y; - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - var tileKey = this.getTileKeyZXY_(z, x, y); - var features = tiles[tileKey]; - if (features !== undefined) { - var i, ii; - for (i = 0, ii = features.length; i < ii; ++i) { - var result = f.call(opt_this, features[i]); - if (result) { - return result; - } - } - } - } - } - return undefined; -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.getClosestFeatureToCoordinate = - goog.abstractMethod; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.getExtent = goog.abstractMethod; - - -/** - * Return the features of the TileVector source. - * @inheritDoc - * @api - */ -ol.source.TileVector.prototype.getFeatures = function() { - var tiles = this.tiles_; - var features = []; - var tileKey; - for (tileKey in tiles) { - goog.array.extend(features, tiles[tileKey]); - } - return features; -}; - - -/** - * Get all features whose geometry intersects the provided coordinate for the - * provided resolution. - * @param {ol.Coordinate} coordinate Coordinate. - * @param {number} resolution Resolution. - * @return {Array.} Features. - * @api - */ -ol.source.TileVector.prototype.getFeaturesAtCoordinateAndResolution = - function(coordinate, resolution) { - var features = []; - this.forEachFeatureAtCoordinateAndResolution(coordinate, resolution, - /** - * @param {ol.Feature} feature Feature. - */ - function(feature) { - features.push(feature); - }); - return features; -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.getFeaturesInExtent = goog.abstractMethod; - - -/** - * Handles x-axis wrapping and returns a tile coordinate transformed from the - * internal tile scheme to the tile grid's tile scheme. When the tile coordinate - * is outside the resolution and extent range of the tile grid, `null` will be - * returned. - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {ol.proj.Projection} projection Projection. - * @return {ol.TileCoord} Tile coordinate to be passed to the tileUrlFunction or - * null if no tile URL should be created for the passed `tileCoord`. - */ -ol.source.TileVector.prototype.getTileCoordForTileUrlFunction = - function(tileCoord, projection) { - var tileGrid = this.tileGrid_; - goog.asserts.assert(tileGrid, 'tile grid needed'); - if (this.getWrapX() && projection.isGlobal()) { - tileCoord = ol.tilecoord.wrapX(tileCoord, tileGrid, projection); - } - return ol.tilecoord.withinExtentAndZ(tileCoord, tileGrid) ? - tileCoord : null; -}; - - -/** - * @param {number} z Z. - * @param {number} x X. - * @param {number} y Y. - * @private - * @return {string} Tile key. - */ -ol.source.TileVector.prototype.getTileKeyZXY_ = function(z, x, y) { - return z + '/' + x + '/' + y; -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.loadFeatures = - function(extent, resolution, projection) { - var tileGrid = this.tileGrid_; - var tileUrlFunction = this.tileUrlFunction_; - var tiles = this.tiles_; - var z = tileGrid.getZForResolution(resolution); - var tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z); - var tileCoord = [z, 0, 0]; - var x, y; - /** - * @param {string} tileKey Tile key. - * @param {Array.} features Features. - * @this {ol.source.TileVector} - */ - function success(tileKey, features) { - tiles[tileKey] = features; - this.changed(); - } - for (x = tileRange.minX; x <= tileRange.maxX; ++x) { - for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - var tileKey = this.getTileKeyZXY_(z, x, y); - if (!(tileKey in tiles)) { - tileCoord[1] = x; - tileCoord[2] = y; - var urlTileCoord = this.getTileCoordForTileUrlFunction( - tileCoord, projection); - var url = !urlTileCoord ? undefined : - tileUrlFunction(urlTileCoord, 1, projection); - if (url !== undefined) { - tiles[tileKey] = []; - var tileSuccess = goog.partial(success, tileKey); - if (this.tileLoadFunction_) { - this.tileLoadFunction_(url, goog.bind(tileSuccess, this)); - } else { - var loader = ol.featureloader.loadFeaturesXhr(url, - /** @type {ol.format.Feature} */ (this.format_), tileSuccess, - goog.nullFunction); - loader.call(this, extent, resolution, projection); - } - } - } - } - } -}; - - -/** - * @inheritDoc - */ -ol.source.TileVector.prototype.removeFeature = goog.abstractMethod; - - -/** - * @param {ol.TileUrlFunctionType} tileUrlFunction Tile URL function. - */ -ol.source.TileVector.prototype.setTileUrlFunction = function(tileUrlFunction) { - this.tileUrlFunction_ = tileUrlFunction; - this.changed(); -}; - - -/** - * @param {string} url URL. - */ -ol.source.TileVector.prototype.setUrl = function(url) { - this.setTileUrlFunction(ol.TileUrlFunction.createFromTemplates( - ol.TileUrlFunction.expandUrl(url), this.tileGrid_)); -}; - - -/** - * @param {Array.} urls URLs. - */ -ol.source.TileVector.prototype.setUrls = function(urls) { - this.setTileUrlFunction( - ol.TileUrlFunction.createFromTemplates(urls, this.tileGrid_)); -}; diff --git a/src/ol/source/vectorsource.js b/src/ol/source/vectorsource.js index cb708c0891..f17d5ebd74 100644 --- a/src/ol/source/vectorsource.js +++ b/src/ol/source/vectorsource.js @@ -493,20 +493,6 @@ ol.source.Vector.prototype.forEachFeatureInExtent = }; -/** - * @param {ol.Extent} extent Extent. - * @param {number} resolution Resolution. - * @param {function(this: T, ol.Feature): S} f Callback. - * @param {T=} opt_this The object to use as `this` in `f`. - * @return {S|undefined} - * @template T,S - */ -ol.source.Vector.prototype.forEachFeatureInExtentAtResolution = - function(extent, resolution, f, opt_this) { - return this.forEachFeatureInExtent(extent, f, opt_this); -}; - - /** * Iterate through all features whose geometry intersects the provided extent, * calling the callback with each feature. If the callback returns a "truthy" diff --git a/test/spec/ol/source/tilevectorsource.test.js b/test/spec/ol/source/tilevectorsource.test.js deleted file mode 100644 index a2329642ee..0000000000 --- a/test/spec/ol/source/tilevectorsource.test.js +++ /dev/null @@ -1,105 +0,0 @@ -goog.provide('ol.test.source.TileVector'); - - -describe('ol.source.TileVector', function() { - - describe('#loadFeatures()', function() { - - it('calls tileUrlFunction with correct tile coords', function() { - var tileCoords = []; - var source = new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - tileUrlFunction: function(tileCoord) { - tileCoords.push(tileCoord.slice()); - return null; - } - }); - var projection = ol.proj.get('EPSG:3857'); - source.loadFeatures( - [-8238854, 4969777, -8237854, 4970777], 4.8, projection); - expect(tileCoords[0]).to.eql([15, 9647, -12321]); - expect(tileCoords[1]).to.eql([15, 9647, -12320]); - expect(tileCoords[2]).to.eql([15, 9648, -12321]); - expect(tileCoords[3]).to.eql([15, 9648, -12320]); - }); - - }); - - describe('#getTileCoordForTileUrlFunction()', function() { - - it('returns the expected tile coordinate - {wrapX: true}', function() { - var tileSource = new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - wrapX: true - }); - var projection = ol.proj.get('EPSG:3857'); - - var tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, -31, -23], projection); - expect(tileCoord).to.eql([6, 33, -23]); - - tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, 33, -23], projection); - expect(tileCoord).to.eql([6, 33, -23]); - - tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, 97, -23], projection); - expect(tileCoord).to.eql([6, 33, -23]); - }); - - it('returns the expected tile coordinate - {wrapX: false}', function() { - var tileSource = new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - wrapX: false - }); - var projection = ol.proj.get('EPSG:3857'); - - var tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, -31, -23], projection); - expect(tileCoord).to.eql(null); - - tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, 33, -23], projection); - expect(tileCoord).to.eql([6, 33, -23]); - - tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, 97, -23], projection); - expect(tileCoord).to.eql(null); - }); - - it('works with wrapX and custom projection without extent', function() { - var tileSource = new ol.source.TileVector({ - format: new ol.format.TopoJSON(), - tileGrid: ol.tilegrid.createXYZ({ - maxZoom: 19 - }), - wrapX: true - }); - var projection = new ol.proj.Projection({ - code: 'foo', - global: true, - units: 'm' - }); - - var tileCoord = tileSource.getTileCoordForTileUrlFunction( - [6, -31, -23], projection); - expect(tileCoord).to.eql([6, 33, -23]); - }); - }); - -}); - - -goog.require('ol.format.TopoJSON'); -goog.require('ol.proj'); -goog.require('ol.proj.Projection'); -goog.require('ol.source.TileVector'); diff --git a/test/spec/ol/source/vectorsource.test.js b/test/spec/ol/source/vectorsource.test.js index d3f7a5382a..2d0ca9cca8 100644 --- a/test/spec/ol/source/vectorsource.test.js +++ b/test/spec/ol/source/vectorsource.test.js @@ -62,6 +62,17 @@ describe('ol.source.Vector', function() { expect(listener).to.be.called(); }); + it('adds same id features only once', function() { + var source = new ol.source.Vector(); + var feature1 = new ol.Feature(); + feature1.setId('1'); + var feature2 = new ol.Feature(); + feature2.setId('1'); + source.addFeature(feature1); + source.addFeature(feature2); + expect(source.getFeatures().length).to.be(1); + }); + }); }); From 5832943773fa61d7e1a180513f36861ec962fe56 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Tue, 27 Oct 2015 21:04:38 +0100 Subject: [PATCH 25/27] Make changes suggested during the review --- ...e.css => mapbox-vector-tiles-advanced.css} | 0 examples/mapbox-vector-tiles-advanced.html | 17 ++++ examples/mapbox-vector-tiles-advanced.js | 73 ++++++++++++++++ examples/mapbox-vector-tiles-simple.html | 17 ---- examples/mapbox-vector-tiles-simple.js | 42 --------- examples/mapbox-vector-tiles.html | 2 +- examples/mapbox-vector-tiles.js | 37 +------- externs/olx.js | 25 +++--- src/ol/featureloader.js | 4 +- src/ol/format/mvtformat.js | 65 +++++++------- src/ol/geom/geometry.js | 3 +- src/ol/proj/proj.js | 7 +- src/ol/render/canvas/canvasreplay.js | 4 + src/ol/render/renderfeature.js | 2 +- .../canvas/canvasvectortilelayerrenderer.js | 9 +- src/ol/source/urltilesource.js | 2 +- src/ol/vectortile.js | 2 +- .../ol/data/tiles/mvt/14-8938-5680.vector.pbf | Bin 0 -> 46937 bytes .../ol/layer/expected/vectortile-canvas.png | Bin 0 -> 5650 bytes .../spec/ol/layer/vectortile.test.js | 82 ++++++++++++++++++ 20 files changed, 237 insertions(+), 156 deletions(-) rename examples/{mapbox-vector-tiles-simple.css => mapbox-vector-tiles-advanced.css} (100%) create mode 100644 examples/mapbox-vector-tiles-advanced.html create mode 100644 examples/mapbox-vector-tiles-advanced.js delete mode 100644 examples/mapbox-vector-tiles-simple.html delete mode 100644 examples/mapbox-vector-tiles-simple.js create mode 100644 test_rendering/spec/ol/data/tiles/mvt/14-8938-5680.vector.pbf create mode 100644 test_rendering/spec/ol/layer/expected/vectortile-canvas.png create mode 100644 test_rendering/spec/ol/layer/vectortile.test.js 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 0000000000000000000000000000000000000000..0ed0c1ee240ac78a8ae5f6921253d8a8325c47ef GIT binary patch literal 46937 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 YAU}Y2Zw^?3JL?5lU9z>PHNW-n|6FV;R{#J2 literal 0 HcmV?d00001 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'); From 5d264d2bf082dbfc760ad56f656c20715a4fed55 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 28 Oct 2015 09:05:47 +0100 Subject: [PATCH 26/27] Remove right-handed polygon handling in renderer Since ol.render.Feature assumes right-handed polygons anyway, this extra optimization is not needed. --- src/ol/format/featureformat.js | 13 ------- src/ol/format/mvtformat.js | 2 -- src/ol/render/canvas/canvasreplay.js | 36 ++----------------- src/ol/render/renderfeature.js | 3 +- .../canvas/canvasvectortilelayerrenderer.js | 3 +- 5 files changed, 5 insertions(+), 52 deletions(-) diff --git a/src/ol/format/featureformat.js b/src/ol/format/featureformat.js index 67f8ed20da..419f3376e8 100644 --- a/src/ol/format/featureformat.js +++ b/src/ol/format/featureformat.js @@ -25,11 +25,6 @@ ol.format.Feature = function() { */ this.defaultDataProjection = null; - /** - * @protected - * @type {boolean} - */ - this.rightHandedPolygons = false; }; @@ -59,14 +54,6 @@ ol.format.Feature.prototype.getReadOptions = function(source, opt_options) { }; -/** - * @return {boolean} - */ -ol.format.Feature.prototype.getRightHandedPolygons = function() { - return this.rightHandedPolygons; -}; - - /** * Sets the `defaultDataProjection` on the options, if no `dataProjection` * is set. diff --git a/src/ol/format/mvtformat.js b/src/ol/format/mvtformat.js index 10c3c0e0b7..f625c0ed2b 100644 --- a/src/ol/format/mvtformat.js +++ b/src/ol/format/mvtformat.js @@ -74,8 +74,6 @@ ol.format.MVT = function(opt_options) { */ this.layers_ = options.layers ? options.layers : null; - this.rightHandedPolygons = true; - }; goog.inherits(ol.format.MVT, ol.format.Feature); diff --git a/src/ol/render/canvas/canvasreplay.js b/src/ol/render/canvas/canvasreplay.js index a31a8c9203..e8ed38e661 100644 --- a/src/ol/render/canvas/canvasreplay.js +++ b/src/ol/render/canvas/canvasreplay.js @@ -1180,12 +1180,6 @@ ol.render.canvas.PolygonReplay = function(tolerance, maxExtent, resolution) { miterLimit: undefined }; - /** - * @private - * @type {boolean} - */ - this.rightHanded_ = false; - }; goog.inherits(ol.render.canvas.PolygonReplay, ol.render.canvas.Replay); @@ -1318,9 +1312,7 @@ ol.render.canvas.PolygonReplay.prototype.drawPolygonGeometry = state.miterLimit, state.lineDash]); } var ends = polygonGeometry.getEnds(); - var flatCoordinates = this.rightHanded_ ? - polygonGeometry.getFlatCoordinates() : - polygonGeometry.getOrientedFlatCoordinates(); + var flatCoordinates = polygonGeometry.getOrientedFlatCoordinates(); var stride = polygonGeometry.getStride(); this.drawFlatCoordinatess_(flatCoordinates, 0, ends, stride); this.endGeometry(polygonGeometry, feature); @@ -1405,16 +1397,6 @@ 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 */ @@ -1833,13 +1815,10 @@ 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, - opt_rightHandedPolygons) { + tolerance, maxExtent, resolution, opt_renderBuffer) { /** * @private @@ -1884,13 +1863,6 @@ ol.render.canvas.ReplayGroup = function( */ this.hitDetectionTransform_ = goog.vec.Mat4.createNumber(); - /** - * @private - * @type {boolean} - */ - this.rightHandedPolygons_ = opt_rightHandedPolygons ? - opt_rightHandedPolygons : false; - }; @@ -1979,10 +1951,6 @@ 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/renderfeature.js b/src/ol/render/renderfeature.js index 17ead76116..00ba0eb451 100644 --- a/src/ol/render/renderfeature.js +++ b/src/ol/render/renderfeature.js @@ -15,7 +15,8 @@ goog.require('ol.geom.GeometryType'); * * @constructor * @param {ol.geom.GeometryType} type Geometry type. - * @param {Array.} flatCoordinates Flat coordinates. + * @param {Array.} flatCoordinates Flat coordinates. These always need + * to be right-handed for polygons. * @param {Array.|Array.>} ends Ends or Endss. * @param {Object.} properties Properties. */ diff --git a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js index 03d9bbe8c7..820ebd4ce7 100644 --- a/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvasvectortilelayerrenderer.js @@ -191,8 +191,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup = function(tile, var tileResolution = pixelSpace ? source.getTilePixelRatio() : resolution; replayState.dirty = false; var replayGroup = new ol.render.canvas.ReplayGroup(0, extent, - tileResolution, layer.getRenderBuffer(), - tile.getFormat().getRightHandedPolygons()); + tileResolution, layer.getRenderBuffer()); var squaredTolerance = ol.renderer.vector.getSquaredTolerance( tileResolution, pixelRatio); From 80b78b8b5a703dec9e4317fd7320464d0f7e6a8f Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 28 Oct 2015 09:23:11 +0100 Subject: [PATCH 27/27] Avoid instanceof dependant assertions --- src/ol/render/vector.js | 51 +++++++---------------------------------- 1 file changed, 8 insertions(+), 43 deletions(-) diff --git a/src/ol/render/vector.js b/src/ol/render/vector.js index 7543740713..504ed3d583 100644 --- a/src/ol/render/vector.js +++ b/src/ol/render/vector.js @@ -1,15 +1,6 @@ goog.provide('ol.renderer.vector'); goog.require('goog.asserts'); -goog.require('ol.geom.Circle'); -goog.require('ol.geom.Geometry'); -goog.require('ol.geom.GeometryCollection'); -goog.require('ol.geom.LineString'); -goog.require('ol.geom.MultiLineString'); -goog.require('ol.geom.MultiPoint'); -goog.require('ol.geom.MultiPolygon'); -goog.require('ol.geom.Point'); -goog.require('ol.geom.Polygon'); goog.require('ol.render.Feature'); goog.require('ol.render.IReplayGroup'); goog.require('ol.style.ImageState'); @@ -49,15 +40,13 @@ ol.renderer.vector.getTolerance = function(resolution, pixelRatio) { /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.Circle} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature} feature Feature. * @private */ ol.renderer.vector.renderCircleGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.Circle, - 'geometry should be an ol.geom.Circle'); var fillStyle = style.getFill(); var strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) { @@ -137,15 +126,13 @@ ol.renderer.vector.renderFeature_ = function( /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.GeometryCollection} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature} feature Feature. * @private */ ol.renderer.vector.renderGeometryCollectionGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.GeometryCollection, - 'geometry should be an ol.geom.GeometryCollection'); var geometries = geometry.getGeometriesArray(); var i, ii; for (i = 0, ii = geometries.length; i < ii; ++i) { @@ -160,17 +147,13 @@ ol.renderer.vector.renderGeometryCollectionGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.LineString|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderLineStringGeometry_ = function(replayGroup, geometry, style, feature) { - if (geometry instanceof ol.geom.Geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.LineString, - 'geometry should be an ol.geom.LineString'); - } var strokeStyle = style.getStroke(); if (strokeStyle) { var lineStringReplay = replayGroup.getReplay( @@ -190,17 +173,13 @@ ol.renderer.vector.renderLineStringGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.MultiLineString|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderMultiLineStringGeometry_ = function(replayGroup, geometry, style, feature) { - if (geometry instanceof ol.geom.Geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiLineString, - 'geometry should be an ol.geom.MultiLineString'); - } var strokeStyle = style.getStroke(); if (strokeStyle) { var lineStringReplay = replayGroup.getReplay( @@ -222,15 +201,13 @@ ol.renderer.vector.renderMultiLineStringGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.MultiPolygon} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature} feature Feature. * @private */ ol.renderer.vector.renderMultiPolygonGeometry_ = function(replayGroup, geometry, style, feature) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPolygon, - 'geometry should be an ol.geom.MultiPolygon'); var fillStyle = style.getFill(); var strokeStyle = style.getStroke(); if (strokeStyle || fillStyle) { @@ -253,17 +230,13 @@ ol.renderer.vector.renderMultiPolygonGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.Point|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderPointGeometry_ = function(replayGroup, geometry, style, feature) { - if (geometry instanceof ol.geom.Geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.Point, - 'geometry should be an ol.geom.Point'); - } var imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { @@ -287,17 +260,13 @@ ol.renderer.vector.renderPointGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry} geometry Geometry. + * @param {ol.geom.MultiPoint|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderMultiPointGeometry_ = function(replayGroup, geometry, style, feature) { - if (geometry instanceof ol.geom.Geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.MultiPoint, - 'geometry should be an ol.goem.MultiPoint'); - } var imageStyle = style.getImage(); if (imageStyle) { if (imageStyle.getImageState() != ol.style.ImageState.LOADED) { @@ -322,17 +291,13 @@ ol.renderer.vector.renderMultiPointGeometry_ = /** * @param {ol.render.IReplayGroup} replayGroup Replay group. - * @param {ol.geom.Geometry|ol.render.Feature} geometry Geometry. + * @param {ol.geom.Polygon|ol.render.Feature} geometry Geometry. * @param {ol.style.Style} style Style. * @param {ol.Feature|ol.render.Feature} feature Feature. * @private */ ol.renderer.vector.renderPolygonGeometry_ = function(replayGroup, geometry, style, feature) { - if (geometry instanceof ol.geom.Geometry) { - goog.asserts.assertInstanceof(geometry, ol.geom.Polygon, - 'geometry should be an ol.geom.Polygon or ol.render.Feature'); - } var fillStyle = style.getFill(); var strokeStyle = style.getStroke(); if (fillStyle || strokeStyle) {