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',
+});