From d4cc76f3f14b107c8511ad14c38eaebf008ae387 Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Fri, 19 Nov 2021 17:47:59 +0100 Subject: [PATCH] Add background to MapboxVector layer --- examples/mapbox-vector-layer.css | 3 - src/ol/layer/MapboxVector.js | 87 ++++++++++++++++++- src/ol/render/Feature.js | 9 +- src/ol/source/Tile.js | 1 - .../spec/ol/layer/MapboxVector.test.js | 64 +++++++++++++- 5 files changed, 151 insertions(+), 13 deletions(-) delete mode 100644 examples/mapbox-vector-layer.css diff --git a/examples/mapbox-vector-layer.css b/examples/mapbox-vector-layer.css deleted file mode 100644 index 33e90f7301..0000000000 --- a/examples/mapbox-vector-layer.css +++ /dev/null @@ -1,3 +0,0 @@ -.map { - background: #f8f4f0; -} diff --git a/src/ol/layer/MapboxVector.js b/src/ol/layer/MapboxVector.js index f855227a10..7f1e417fdd 100644 --- a/src/ol/layer/MapboxVector.js +++ b/src/ol/layer/MapboxVector.js @@ -3,11 +3,17 @@ */ import BaseEvent from '../events/Event.js'; import EventType from '../events/EventType.js'; +import GeometryType from '../geom/GeometryType.js'; import MVT from '../format/MVT.js'; +import RenderFeature from '../render/Feature.js'; import SourceState from '../source/State.js'; +import TileEventType from '../source/TileEventType.js'; import VectorTileLayer from '../layer/VectorTile.js'; import VectorTileSource from '../source/VectorTile.js'; +import {Fill, Style} from '../style.js'; import {applyStyle, setupVectorSource} from 'ol-mapbox-style'; +import {fromExtent} from '../geom/Polygon.js'; +import {getValue} from 'ol-mapbox-style/dist/stylefunction.js'; const mapboxBaseUrl = 'https://api.mapbox.com'; @@ -153,7 +159,10 @@ const SourceType = { /** * @typedef {Object} LayerObject * @property {string} id The layer id. + * @property {string} type The layer type. * @property {string} source The source id. + * @property {Object} layout The layout. + * @property {Object} paint The paint. */ /** @@ -291,6 +300,7 @@ class MapboxVectorLayer extends VectorTileLayer { this.sourceId = options.source; this.layers = options.layers; + if (options.accessToken) { this.accessToken = options.accessToken; } else { @@ -409,7 +419,7 @@ class MapboxVectorLayer extends VectorTileLayer { ); applyStyle(this, style, sourceIdOrLayersList) .then(() => { - source.setState(SourceState.READY); + this.configureSource(source, style); }) .catch((error) => { this.handleError(error); @@ -431,16 +441,87 @@ class MapboxVectorLayer extends VectorTileLayer { ).then((source) => { applyStyle(this, style, sourceIdOrLayersList) .then(() => { - this.setSource(source); + this.configureSource(source, style); }) .catch((error) => { - this.setSource(source); + this.configureSource(source, style); this.handleError(error); }); }); } } + /** + * Applies configuration from the provided source to this layer's source, + * and reconfigures the loader to add a feature that renders the background, + * if the style is configured with a background. + * @param {import("../source/VectorTile.js").default} source The source to configure from. + * @param {StyleObject} style The style to configure the background from. + */ + configureSource(source, style) { + const targetSource = this.getSource(); + if (source !== targetSource) { + targetSource.setAttributions(source.getAttributions()); + targetSource.setTileUrlFunction(source.getTileUrlFunction()); + targetSource.setTileLoadFunction(source.getTileLoadFunction()); + targetSource.tileGrid = source.tileGrid; + } + const background = style.layers.find( + (layer) => layer.type === 'background' + ); + if ( + !background || + !background.layout || + background.layout.visibility !== 'none' + ) { + const style = new Style({ + fill: new Fill(), + }); + targetSource.addEventListener(TileEventType.TILELOADEND, (event) => { + const tile = /** @type {import("../VectorTile.js").default} */ ( + /** @type {import("../source/Tile.js").TileSourceEvent} */ (event) + .tile + ); + const styleFuntion = () => { + const opacity = + /** @type {number} */ ( + getValue( + background, + 'paint', + 'background-opacity', + tile.tileCoord[0] + ) + ) || 1; + const color = /** @type {*} */ ( + getValue(background, 'paint', 'background-color', tile.tileCoord[0]) + ); + style + .getFill() + .setColor([ + color.r * 255, + color.g * 255, + color.b * 255, + color.a * opacity, + ]); + return style; + }; + const extentGeometry = fromExtent( + targetSource.tileGrid.getTileCoordExtent(tile.tileCoord) + ); + const renderFeature = new RenderFeature( + GeometryType.POLYGON, + extentGeometry.getFlatCoordinates(), + extentGeometry.getEnds(), + {layer: background.id}, + undefined + ); + renderFeature.styleFunction = styleFuntion; + tile.getFeatures().unshift(renderFeature); + }); + } + targetSource.setState(SourceState.READY); + } + /** * Handle configuration or loading error. * @param {Error} error The error. diff --git a/src/ol/render/Feature.js b/src/ol/render/Feature.js index c0193f099a..f9e64c1454 100644 --- a/src/ol/render/Feature.js +++ b/src/ol/render/Feature.js @@ -42,6 +42,11 @@ class RenderFeature { * @param {number|string|undefined} id Feature id. */ constructor(type, flatCoordinates, ends, properties, id) { + /** + * @type {import("../style/Style.js").StyleFunction|undefined} + */ + this.styleFunction; + /** * @private * @type {import("../extent.js").Extent|undefined} @@ -259,10 +264,10 @@ class RenderFeature { } /** - * @return {undefined} + * @return {import('../style/Style.js').StyleFunction|undefined} Style */ getStyleFunction() { - return undefined; + return this.styleFunction; } /** diff --git a/src/ol/source/Tile.js b/src/ol/source/Tile.js index e02b5643c4..8d9b16207f 100644 --- a/src/ol/source/Tile.js +++ b/src/ol/source/Tile.js @@ -90,7 +90,6 @@ class TileSource extends Source { options.tilePixelRatio !== undefined ? options.tilePixelRatio : 1; /** - * @protected * @type {import("../tilegrid/TileGrid.js").default} */ this.tileGrid = options.tileGrid !== undefined ? options.tileGrid : null; diff --git a/test/browser/spec/ol/layer/MapboxVector.test.js b/test/browser/spec/ol/layer/MapboxVector.test.js index e65dc9efd8..c80d2d454f 100644 --- a/test/browser/spec/ol/layer/MapboxVector.test.js +++ b/test/browser/spec/ol/layer/MapboxVector.test.js @@ -4,6 +4,8 @@ import MapboxVectorLayer, { normalizeSpriteUrl, normalizeStyleUrl, } from '../../../../../src/ol/layer/MapboxVector.js'; +import {get} from '../../../../../src/ol/proj.js'; +import {unByKey} from '../../../../../src/ol/Observable.js'; describe('ol/layer/MapboxVector', () => { describe('getMapboxPath()', () => { @@ -112,13 +114,67 @@ describe('ol/layer/MapboxVector', () => { type: 'vector', }, }, + layers: [], }) ), }); - layer.on('change:source', function () { - // we only get here when a new source was set, which is what ol-mapbox-style's - // setupVectorSource() does. - done(); + const source = layer.getSource(); + const key = source.on('change', function () { + if (source.getState() === 'ready') { + unByKey(key); + expect(source.getTileUrlFunction()([0, 0, 0])).to.be( + 'http://a.tiles.mapbox.com/v3/mapbox.geography-class/0/0/0.png' + ); + done(); + } + }); + }); + }); + + describe('background', function () { + it('adds a feature for the background', function (done) { + const layer = new MapboxVectorLayer({ + styleUrl: + 'data:,' + + encodeURIComponent( + JSON.stringify({ + version: 8, + sources: { + 'foo': { + url: '/spec/ol/data/{z}-{x}-{y}.vector.pbf', + type: 'vector', + }, + }, + layers: [ + { + id: 'background', + type: 'background', + paint: { + 'background-color': '#ff0000', + 'background-opacity': 0.8, + }, + }, + ], + }) + ), + }); + const source = layer.getSource(); + const key = source.on('change', function () { + if (source.getState() === 'ready') { + unByKey(key); + source.getTile(14, 8938, 5680, 1, get('EPSG:3857')).load(); + source.once('tileloadend', (event) => { + const features = event.tile.getFeatures(); + if (!features) { + event.tile.setFeatures([]); + } + expect(features[0].get('layer')).to.be('background'); + expect( + features[0].getStyleFunction()().getFill().getColor() + ).to.eql([255, 0, 0, 0.8]); + done(); + }); + } }); }); });