diff --git a/examples/cog-style.html b/examples/cog-style.html new file mode 100644 index 0000000000..0063a21455 --- /dev/null +++ b/examples/cog-style.html @@ -0,0 +1,20 @@ +--- +layout: example.html +title: Change Tile Layer Style +shortdesc: Updating the style of a WebGL tile layer +docs: > + When you want to change the style of a WebGL tile layer based on some change in your + application state, you should use the `layer.updateStyleVariables()` method. A layer can + be efficiently rendered even if style variables are changed on every render frame. + In cases where you need to completely replace the style of a layer, you can call the + `layer.setStyle()` method. This method should not be called in response to frequent + user events (e.g. mouse movement, dragging a slider, etc.). +tags: "cog, webgl, style" +--- +
+Set the layer style + diff --git a/examples/cog-style.js b/examples/cog-style.js new file mode 100644 index 0000000000..f577de02f9 --- /dev/null +++ b/examples/cog-style.js @@ -0,0 +1,105 @@ +import GeoTIFF from '../src/ol/source/GeoTIFF.js'; +import Map from '../src/ol/Map.js'; +import TileLayer from '../src/ol/layer/WebGLTile.js'; +import View from '../src/ol/View.js'; + +const max = 3000; +function normalize(value) { + return ['/', value, max]; +} + +const red = normalize(['band', 1]); +const green = normalize(['band', 2]); +const blue = normalize(['band', 3]); +const nir = normalize(['band', 4]); + +const trueColor = { + color: ['array', red, green, blue, 1], + gamma: 1.1, +}; + +const falseColor = { + color: ['array', nir, red, green, 1], + gamma: 1.1, +}; + +const ndvi = { + color: [ + 'interpolate', + ['linear'], + ['/', ['-', nir, red], ['+', nir, red]], + // color ramp for NDVI values, ranging from -1 to 1 + -0.2, + [191, 191, 191], + -0.1, + [219, 219, 219], + 0, + [255, 255, 224], + 0.025, + [255, 250, 204], + 0.05, + [237, 232, 181], + 0.075, + [222, 217, 156], + 0.1, + [204, 199, 130], + 0.125, + [189, 184, 107], + 0.15, + [176, 194, 97], + 0.175, + [163, 204, 89], + 0.2, + [145, 191, 82], + 0.25, + [128, 179, 71], + 0.3, + [112, 163, 64], + 0.35, + [97, 150, 54], + 0.4, + [79, 138, 46], + 0.45, + [64, 125, 36], + 0.5, + [48, 110, 28], + 0.55, + [33, 97, 18], + 0.6, + [15, 84, 10], + 0.65, + [0, 69, 0], + ], +}; + +const layer = new TileLayer({ + style: trueColor, + source: new GeoTIFF({ + normalize: false, + sources: [ + { + url: 'https://s2downloads.eox.at/demo/EOxCloudless/2020/rgbnir/s2cloudless2020-16bits_sinlge-file_z0-4.tif', + }, + ], + }), +}); + +const map = new Map({ + target: 'map', + layers: [layer], + view: new View({ + projection: 'EPSG:4326', + center: [0, 0], + zoom: 2, + maxZoom: 6, + }), +}); + +const styles = {trueColor, falseColor, ndvi}; +const styleSelector = document.getElementById('style'); + +function update() { + const style = styles[styleSelector.value]; + layer.setStyle(style); +} +styleSelector.addEventListener('change', update); diff --git a/src/ol/layer/WebGLTile.js b/src/ol/layer/WebGLTile.js index c4847b6792..dc34479ce9 100644 --- a/src/ol/layer/WebGLTile.js +++ b/src/ol/layer/WebGLTile.js @@ -311,6 +311,28 @@ class WebGLTileLayer extends BaseTileLayer { }); } + /** + * Update the layer style. The `updateStyleVariables` function is a more efficient + * way to update layer rendering. In cases where the whole style needs to be updated, + * this method may be called instead. + * @param {Style} style The new style. + */ + setStyle(style) { + this.style_ = style; + const source = this.getSource(); + const parsedStyle = parseStyle( + this.style_, + 'bandCount' in source ? source.bandCount : 4 + ); + const renderer = this.getRenderer(); + renderer.reset({ + vertexShader: parsedStyle.vertexShader, + fragmentShader: parsedStyle.fragmentShader, + uniforms: parsedStyle.uniforms, + }); + this.changed(); + } + /** * Update any variables used by the layer style and trigger a re-render. * @param {Object} variables Variables to update. diff --git a/src/ol/renderer/webgl/Layer.js b/src/ol/renderer/webgl/Layer.js index c1b35cc5c1..39610eb4b0 100644 --- a/src/ol/renderer/webgl/Layer.js +++ b/src/ol/renderer/webgl/Layer.js @@ -90,6 +90,17 @@ class WebGLLayerRenderer extends LayerRenderer { layer.addChangeListener(LayerProperty.MAP, this.removeHelper_.bind(this)); } + /** + * Reset options (only handles uniforms). + * @param {Options} options Options. + */ + reset(options) { + this.uniforms_ = options.uniforms; + if (this.helper) { + this.helper.setUniforms(this.uniforms_); + } + } + removeHelper_() { if (this.helper) { this.helper.dispose(); diff --git a/src/ol/renderer/webgl/TileLayer.js b/src/ol/renderer/webgl/TileLayer.js index 16b3c3309e..68c0917031 100644 --- a/src/ol/renderer/webgl/TileLayer.js +++ b/src/ol/renderer/webgl/TileLayer.js @@ -213,6 +213,24 @@ class WebGLTileLayerRenderer extends WebGLLayerRenderer { this.tileTextureCache_ = new LRUCache(cacheSize); } + /** + * @param {Options} options Options. + */ + reset(options) { + super.reset({ + uniforms: options.uniforms, + }); + this.vertexShader_ = options.vertexShader; + this.fragmentShader_ = options.fragmentShader; + + if (this.helper) { + this.program_ = this.helper.getProgram( + this.fragmentShader_, + this.vertexShader_ + ); + } + } + afterHelperCreated() { this.program_ = this.helper.getProgram( this.fragmentShader_, diff --git a/src/ol/webgl/Helper.js b/src/ol/webgl/Helper.js index ef70a3f4d2..ae8358c00d 100644 --- a/src/ol/webgl/Helper.js +++ b/src/ol/webgl/Helper.js @@ -340,7 +340,6 @@ class WebGLHelper extends Disposable { * @type {WebGLRenderingContext} */ this.gl_ = getContext(this.canvas_); - const gl = this.getGL(); /** * @private @@ -407,14 +406,11 @@ class WebGLHelper extends Disposable { */ this.uniforms_ = []; if (options.uniforms) { - for (const name in options.uniforms) { - this.uniforms_.push({ - name: name, - value: options.uniforms[name], - }); - } + this.setUniforms(options.uniforms); } + const gl = this.getGL(); + /** * An array of PostProcessingPass objects is kept in this variable, built from the steps provided in the * options. If no post process was given, a default one is used (so as not to have to make an exception to @@ -447,6 +443,20 @@ class WebGLHelper extends Disposable { this.startTime_ = Date.now(); } + /** + * @param {Object} uniforms Uniform definitions. + */ + setUniforms(uniforms) { + this.uniforms_ = []; + for (const name in uniforms) { + this.uniforms_.push({ + name: name, + value: uniforms[name], + }); + } + this.uniformLocations_ = {}; + } + /** * @param {string} canvasCacheKey The canvas cache key. * @return {boolean} The provided key matches the one this helper was constructed with. diff --git a/test/rendering/cases/cog-style/expected.png b/test/rendering/cases/cog-style/expected.png new file mode 100644 index 0000000000..b2c5aa12f2 Binary files /dev/null and b/test/rendering/cases/cog-style/expected.png differ diff --git a/test/rendering/cases/cog-style/main.js b/test/rendering/cases/cog-style/main.js new file mode 100644 index 0000000000..80c5cbf872 --- /dev/null +++ b/test/rendering/cases/cog-style/main.js @@ -0,0 +1,45 @@ +import GeoTIFF from '../../../../src/ol/source/GeoTIFF.js'; +import Map from '../../../../src/ol/Map.js'; +import TileLayer from '../../../../src/ol/layer/WebGLTile.js'; + +const source = new GeoTIFF({ + sources: [ + { + url: '/data/raster/sentinel-b04.tif', + min: 0, + max: 10000, + }, + { + url: '/data/raster/sentinel-b08.tif', + min: 0, + max: 10000, + }, + ], + transition: 0, +}); + +const layer = new TileLayer({ + source: source, +}); + +new Map({ + layers: [layer], + target: 'map', + view: source.getView(), +}); + +layer.setStyle({ + color: [ + 'interpolate', + ['linear'], + ['/', ['-', ['band', 2], ['band', 1]], ['+', ['band', 2], ['band', 1]]], + -0.2, + [200, 0, 0], + 1, + [0, 255, 0], + ], +}); + +render({ + message: 'update the layer style', +});