From b3be7e7ba99afdb255bb09d3df2e48a7637cc408 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Wed, 2 Aug 2017 17:45:55 +0200 Subject: [PATCH] Get tilePixelRatio from MVT tiles --- examples/mapbox-vector-tiles-advanced.js | 1 - examples/mapbox-vector-tiles.js | 1 - externs/olx.js | 12 ++++-- src/ol/featureloader.js | 4 +- src/ol/format/feature.js | 9 ++++ src/ol/format/mvt.js | 22 +++++++++- src/ol/renderer/canvas/vectortilelayer.js | 41 +++++++++++++------ src/ol/vectorimagetile.js | 2 +- src/ol/vectortile.js | 31 +++++++++++++- test/spec/ol/format/mvt.test.js | 7 ++++ .../renderer/canvas/vectortilelayer.test.js | 8 ++++ 11 files changed, 113 insertions(+), 25 deletions(-) diff --git a/examples/mapbox-vector-tiles-advanced.js b/examples/mapbox-vector-tiles-advanced.js index 3fa9e6f3fe..5bebe91531 100644 --- a/examples/mapbox-vector-tiles-advanced.js +++ b/examples/mapbox-vector-tiles-advanced.js @@ -44,7 +44,6 @@ var map = new ol.Map({ resolutions: resolutions, tileSize: 512 }), - tilePixelRatio: 8, tileUrlFunction: tileUrlFunction }), style: createMapboxStreetsV6Style() diff --git a/examples/mapbox-vector-tiles.js b/examples/mapbox-vector-tiles.js index 32e6354ea8..752ee245ab 100644 --- a/examples/mapbox-vector-tiles.js +++ b/examples/mapbox-vector-tiles.js @@ -24,7 +24,6 @@ var map = new ol.Map({ 'OpenStreetMap contributors', format: new ol.format.MVT(), tileGrid: ol.tilegrid.createXYZ({tileSize: 512, maxZoom: 22}), - tilePixelRatio: 8, url: 'https://{a-d}.tiles.mapbox.com/v4/mapbox.mapbox-streets-v6/' + '{z}/{x}/{y}.vector.pbf?access_token=' + key }), diff --git a/externs/olx.js b/externs/olx.js index e07514a3ea..98e545336f 100644 --- a/externs/olx.js +++ b/externs/olx.js @@ -4994,6 +4994,8 @@ olx.source.VectorTileOptions.prototype.tileGrid; * var format = tile.getFormat(); * tile.setFeatures(format.readFeatures(data)); * tile.setProjection(format.readProjection(data)); + * // uncomment the line below for ol.format.MVT only + * //tile.setExtent(format.getLastExtent()); * }; * }); * ``` @@ -5004,10 +5006,12 @@ 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`. + * The pixel ratio used by the tile service. For example, if the tile service + * advertizes 512px by 512px tiles but actually sends tiles with coordinates in + * the range of 0..4096 pixels, then `tilePixelRatio` should be set to `8`. + * When {@link ol.format.MVT} is used to parse the features, this setting will + * be overridden by the coordinate range advertized in the tile. + * Default is `1`. * @type {number|undefined} * @api */ diff --git a/src/ol/featureloader.js b/src/ol/featureloader.js index 2441e4bc6e..a85b8ab2e8 100644 --- a/src/ol/featureloader.js +++ b/src/ol/featureloader.js @@ -8,7 +8,7 @@ goog.require('ol.xml'); /** * @param {string|ol.FeatureUrlFunction} url Feature URL service. * @param {ol.format.Feature} format Feature format. - * @param {function(this:ol.VectorTile, Array., ol.proj.Projection)|function(this:ol.source.Vector, Array.)} success + * @param {function(this:ol.VectorTile, Array., ol.proj.Projection, ol.Extent)|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.VectorTile)|function(this:ol.source.Vector)} failure @@ -56,7 +56,7 @@ ol.featureloader.loadFeaturesXhr = function(url, format, success, failure) { if (source) { success.call(this, format.readFeatures(source, {featureProjection: projection}), - format.readProjection(source)); + format.readProjection(source), format.getLastExtent()); } else { failure.call(this); } diff --git a/src/ol/format/feature.js b/src/ol/format/feature.js index 422d76434a..622700ca9b 100644 --- a/src/ol/format/feature.js +++ b/src/ol/format/feature.js @@ -72,6 +72,15 @@ ol.format.Feature.prototype.adaptOptions = function(options) { }; +/** + * Get the extent from the source of the last {@link readFeatures} call. + * @return {ol.Extent} Tile extent. + */ +ol.format.Feature.prototype.getLastExtent = function() { + return null; +}; + + /** * @abstract * @return {ol.format.FormatType} Format. diff --git a/src/ol/format/mvt.js b/src/ol/format/mvt.js index bc2a58ed2f..3b6e98f774 100644 --- a/src/ol/format/mvt.js +++ b/src/ol/format/mvt.js @@ -69,10 +69,25 @@ ol.format.MVT = function(opt_options) { */ this.layers_ = options.layers ? options.layers : null; + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = null; + }; ol.inherits(ol.format.MVT, ol.format.Feature); +/** + * @inheritDoc + * @api + */ +ol.format.MVT.prototype.getLastExtent = function() { + return this.extent_; +}; + + /** * @inheritDoc */ @@ -161,14 +176,17 @@ ol.format.MVT.prototype.readFeatures = function(source, opt_options) { } layer = tile.layers[name]; + var rawFeature; for (var i = 0, ii = layer.length; i < ii; ++i) { + rawFeature = layer.feature(i); if (featureClass === ol.render.Feature) { - feature = this.readRenderFeature_(layer.feature(i), name); + feature = this.readRenderFeature_(rawFeature, name); } else { - feature = this.readFeature_(layer.feature(i), name, opt_options); + feature = this.readFeature_(rawFeature, name, opt_options); } features.push(feature); } + this.extent_ = layer ? [0, 0, layer.extent, layer.extent] : null; } return features; diff --git a/src/ol/renderer/canvas/vectortilelayer.js b/src/ol/renderer/canvas/vectortilelayer.js index ee6c12ac60..2be15797da 100644 --- a/src/ol/renderer/canvas/vectortilelayer.js +++ b/src/ol/renderer/canvas/vectortilelayer.js @@ -13,6 +13,7 @@ goog.require('ol.render.canvas.ReplayGroup'); goog.require('ol.render.replay'); goog.require('ol.renderer.canvas.TileLayer'); goog.require('ol.renderer.vector'); +goog.require('ol.size'); goog.require('ol.transform'); @@ -119,23 +120,24 @@ ol.renderer.canvas.VectorTileLayer.prototype.createReplayGroup_ = function( return; } + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); + var sourceTileGrid = source.getTileGrid(); + var tileGrid = source.getTileGridForProjection(projection); + var resolution = tileGrid.getResolution(tile.tileCoord[0]); + var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord); + for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) { var sourceTile = tile.getTile(tile.tileKeys[t]); replayState.dirty = false; - var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); - var sourceTileGrid = source.getTileGrid(); var sourceTileCoord = sourceTile.tileCoord; var tileProjection = sourceTile.getProjection(); - var tileGrid = source.getTileGridForProjection(projection); - var resolution = tileGrid.getResolution(tile.tileCoord[0]); var sourceTileResolution = sourceTileGrid.getResolution(sourceTile.tileCoord[0]); - var tileExtent = tileGrid.getTileCoordExtent(tile.wrappedTileCoord); var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord); var sharedExtent = ol.extent.getIntersection(tileExtent, sourceTileExtent); var extent, reproject, tileResolution; if (tileProjection.getUnits() == ol.proj.Units.TILE_PIXELS) { - var tilePixelRatio = tileResolution = source.getTilePixelRatio(); + var tilePixelRatio = tileResolution = this.getTilePixelRatio_(source, sourceTile); var transform = ol.transform.compose(this.tmpTransform_, 0, 0, 1 / sourceTileResolution * tilePixelRatio, -1 / sourceTileResolution * tilePixelRatio, @@ -251,7 +253,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.forEachFeatureAtCoordinate = functi var sourceTileCoord = sourceTile.tileCoord; var sourceTileExtent = sourceTileGrid.getTileCoordExtent(sourceTileCoord, this.tmpExtent); origin = ol.extent.getTopLeft(sourceTileExtent); - tilePixelRatio = source.getTilePixelRatio(); + tilePixelRatio = this.getTilePixelRatio_(source, sourceTile); tileResolution = sourceTileGrid.getResolution(sourceTileCoord[0]) / tilePixelRatio; tileSpaceCoordinate = [ (coordinate[0] - origin[0]) / tileResolution, @@ -294,7 +296,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile var tileGrid = source.getTileGrid(); var tileCoord = tile.tileCoord; var tileResolution = - tileGrid.getResolution(tileCoord[0]) / source.getTilePixelRatio(); + tileGrid.getResolution(tileCoord[0]) / this.getTilePixelRatio_(source, tile); var viewState = frameState.viewState; var pixelRatio = frameState.pixelRatio; var renderResolution = viewState.resolution / pixelRatio; @@ -316,6 +318,21 @@ ol.renderer.canvas.VectorTileLayer.prototype.getReplayTransform_ = function(tile }; +/** + * @private + * @param {ol.source.VectorTile} source Source. + * @param {ol.VectorTile} tile Tile. + * @return {number} The tile's pixel ratio. + */ +ol.renderer.canvas.VectorTileLayer.prototype.getTilePixelRatio_ = function(source, tile) { + var extent = tile.getExtent(); + return extent ? + ol.extent.getWidth(extent) / + ol.size.toSize(source.getTileGrid().getTileSize(tile.tileCoord[0]))[0] : + source.getTilePixelRatio(); +}; + + /** * Handle changes in image style state. * @param {ol.events.Event} event Image style change event. @@ -331,7 +348,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.handleStyleImageChange_ = function( */ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, frameState, layerState) { var layer = this.getLayer(); - var source = layer.getSource(); + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); var renderMode = layer.getRenderMode(); var replays = ol.renderer.canvas.VectorTileLayer.VECTOR_REPLAYS[renderMode]; var pixelRatio = frameState.pixelRatio; @@ -340,7 +357,6 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra var offsetX = Math.round(pixelRatio * size[0] / 2); var offsetY = Math.round(pixelRatio * size[1] / 2); var tiles = this.renderedTiles; - var tilePixelRatio = layer.getSource().getTilePixelRatio(); var sourceTileGrid = source.getTileGrid(); var tileGrid = source.getTileGridForProjection(frameState.viewState.projection); var clips = []; @@ -355,6 +371,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.postCompose = function(context, fra tileGrid.getTileCoordExtent(tile.wrappedTileCoord)[0]; for (var t = 0, tt = tile.tileKeys.length; t < tt; ++t) { var sourceTile = tile.getTile(tile.tileKeys[t]); + var tilePixelRatio = this.getTilePixelRatio_(source, sourceTile); var replayGroup = sourceTile.getReplayGroup(layer, tileCoord.toString()); if (renderMode != ol.layer.VectorTileRenderType.VECTOR && !replayGroup.hasReplays(replays)) { continue; @@ -441,11 +458,10 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( var tileCoord = tile.wrappedTileCoord; var z = tileCoord[0]; var pixelRatio = frameState.pixelRatio; - var source = layer.getSource(); + var source = /** @type {ol.source.VectorTile} */ (layer.getSource()); var sourceTileGrid = source.getTileGrid(); var tileGrid = source.getTileGridForProjection(frameState.viewState.projection); var resolution = tileGrid.getResolution(z); - var tilePixelRatio = source.getTilePixelRatio(); var context = tile.getContext(layer); var size = source.getTilePixelSize(z, pixelRatio, frameState.viewState.projection); context.canvas.width = size[0]; @@ -453,6 +469,7 @@ ol.renderer.canvas.VectorTileLayer.prototype.renderTileImage_ = function( var tileExtent = tileGrid.getTileCoordExtent(tileCoord); for (var i = 0, ii = tile.tileKeys.length; i < ii; ++i) { var sourceTile = tile.getTile(tile.tileKeys[i]); + var tilePixelRatio = this.getTilePixelRatio_(source, sourceTile); var sourceTileCoord = sourceTile.tileCoord; var pixelScale = pixelRatio / resolution; var transform = ol.transform.reset(this.tmpTransform_); diff --git a/src/ol/vectorimagetile.js b/src/ol/vectorimagetile.js index a0f12c4a8c..b7f7d3e1bc 100644 --- a/src/ol/vectorimagetile.js +++ b/src/ol/vectorimagetile.js @@ -276,7 +276,7 @@ ol.VectorImageTile.prototype.finishLoading_ = function() { */ ol.VectorImageTile.defaultLoadFunction = function(tile, url) { var loader = ol.featureloader.loadFeaturesXhr( - url, tile.getFormat(), tile.onLoad_.bind(tile), tile.onError_.bind(tile)); + url, tile.getFormat(), tile.onLoad.bind(tile), tile.onError.bind(tile)); tile.setLoader(loader); }; diff --git a/src/ol/vectortile.js b/src/ol/vectortile.js index f3335f362c..a7f1887096 100644 --- a/src/ol/vectortile.js +++ b/src/ol/vectortile.js @@ -23,6 +23,12 @@ ol.VectorTile = function(tileCoord, state, src, format, tileLoadFunction) { */ this.consumers = 0; + /** + * @private + * @type {ol.Extent} + */ + this.extent_ = null; + /** * @private * @type {ol.format.Feature} @@ -82,6 +88,15 @@ ol.VectorTile.prototype.disposeInternal = function() { }; +/** + * Gets the extent of the vector tile. + * @return {ol.Extent} The extent. + */ +ol.VectorTile.prototype.getExtent = function() { + return this.extent_; +}; + + /** * Get the feature format assigned for reading this tile's features. * @return {ol.format.Feature} Feature format. @@ -147,21 +162,33 @@ ol.VectorTile.prototype.load = function() { * Handler for successful tile load. * @param {Array.} features The loaded features. * @param {ol.proj.Projection} dataProjection Data projection. + * @param {ol.Extent} extent Extent. */ -ol.VectorTile.prototype.onLoad_ = function(features, dataProjection) { +ol.VectorTile.prototype.onLoad = function(features, dataProjection, extent) { this.setProjection(dataProjection); this.setFeatures(features); + this.setExtent(extent); }; /** * Handler for tile load errors. */ -ol.VectorTile.prototype.onError_ = function() { +ol.VectorTile.prototype.onError = function() { this.setState(ol.TileState.ERROR); }; +/** + * Sets the extent of the vector tile. + * @param {ol.Extent} extent The extent. + * @api + */ +ol.VectorTile.prototype.setExtent = function(extent) { + this.extent_ = extent; +}; + + /** * @param {Array.} features Features. * @api diff --git a/test/spec/ol/format/mvt.test.js b/test/spec/ol/format/mvt.test.js index 1c441c250a..bc4f2ec517 100644 --- a/test/spec/ol/format/mvt.test.js +++ b/test/spec/ol/format/mvt.test.js @@ -88,6 +88,13 @@ where('ArrayBuffer.isView').describe('ol.format.MVT', function() { expect(features[0].getId()).to.be(2); }); + it('sets the extent of the last readFeatures call', function() { + var format = new ol.format.MVT(); + format.readFeatures(data); + var extent = format.getLastExtent(); + expect(extent.getWidth()).to.be(4096); + }); + }); }); diff --git a/test/spec/ol/renderer/canvas/vectortilelayer.test.js b/test/spec/ol/renderer/canvas/vectortilelayer.test.js index 69fbfa7b45..246327ceff 100644 --- a/test/spec/ol/renderer/canvas/vectortilelayer.test.js +++ b/test/spec/ol/renderer/canvas/vectortilelayer.test.js @@ -186,6 +186,14 @@ describe('ol.renderer.canvas.VectorTileLayer', function() { spy2.restore(); }); + it('uses the extent of the source tile', function() { + var renderer = map.getRenderer().getLayerRenderer(layer); + var tile = new ol.VectorTile([0, 0, 0], 2); + tile.setExtent([0, 0, 4096, 4096]); + var tilePixelRatio = renderer.getTilePixelRatio_(source, tile); + expect(tilePixelRatio).to.be(16); + }); + }); describe('#prepareFrame', function() {