diff --git a/examples/filter-points-webgl.html b/examples/filter-points-webgl.html
index 068e4aa991..50a56a74e9 100644
--- a/examples/filter-points-webgl.html
+++ b/examples/filter-points-webgl.html
@@ -3,14 +3,14 @@ layout: example.html
title: Filtering features with WebGL
shortdesc: Using WebGL to filter large quantities of features
docs: >
- This example shows how to use `ol/renderer/webgl/PointsLayer` to dynamically filter a large amount
+ This example shows how to use `ol/layer/WebGLPoints` with a literal style to dynamically filter a large amount
of point geometries. The above map is based on a dataset from the NASA containing 45k recorded meteorite
landing sites. Each meteorite is marked by a circle on the map (the bigger the circle, the heavier
the object). A pulse effect has been added, which is slightly offset by the year of the impact.
- Adjusting the sliders causes the objects outside of the date range to be filtered out of the map. This is done using
- a custom fragment shader on the layer renderer, and by using the `v_opacity` attribute of the rendered objects
- to store the year of impact.
+ Adjusting the sliders causes the objects outside of the date range to be filtered out of the map. This is done
+ by mutating the variables in the `style` object provided to the WebGL layer. Also note that the last snippet
+ of code is necessary to make sure the map refreshes itself every frame.
tags: "webgl, icon, sprite, filter, feature"
experimental: true
diff --git a/examples/filter-points-webgl.js b/examples/filter-points-webgl.js
index e71b70cd97..96d63e02d4 100644
--- a/examples/filter-points-webgl.js
+++ b/examples/filter-points-webgl.js
@@ -3,162 +3,106 @@ import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js';
import Feature from '../src/ol/Feature.js';
import Point from '../src/ol/geom/Point.js';
-import VectorLayer from '../src/ol/layer/Vector.js';
import {Vector} from '../src/ol/source.js';
import {fromLonLat} from '../src/ol/proj.js';
-import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer.js';
-import {clamp} from '../src/ol/math.js';
import Stamen from '../src/ol/source/Stamen.js';
-import {formatColor} from '../src/ol/webgl/ShaderBuilder.js';
+import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
const vectorSource = new Vector({
attributions: 'NASA'
});
-const oldColor = [180, 140, 140];
-const newColor = [255, 80, 80];
+const oldColor = 'rgba(242,56,22,0.61)';
+const newColor = '#ffe52c';
+const period = 12; // animation period in seconds
+const animRatio =
+ ['pow',
+ ['/',
+ ['mod',
+ ['+',
+ ['time'],
+ ['stretch', ['get', 'year'], 1850, 2020, 0, period]
+ ],
+ period
+ ],
+ period
+ ],
+ 0.5
+ ];
-const startTime = Date.now() * 0.001;
+const style = {
+ variables: {
+ minYear: 1850,
+ maxYear: 2015
+ },
+ filter: ['between', ['get', 'year'], ['var', 'minYear'], ['var', 'maxYear']],
+ symbol: {
+ symbolType: 'circle',
+ size: ['*',
+ ['stretch', ['get', 'mass'], 0, 200000, 8, 26],
+ ['-', 1.5, ['*', animRatio, 0.5]]
+ ],
+ color: ['interpolate',
+ animRatio,
+ newColor, oldColor],
+ opacity: ['-', 1.0, ['*', animRatio, 0.75]]
+ }
+};
-// hanle input values & events
+// handle input values & events
const minYearInput = document.getElementById('min-year');
const maxYearInput = document.getElementById('max-year');
+
+function updateMinYear() {
+ style.variables.minYear = parseInt(minYearInput.value);
+ updateStatusText();
+}
+function updateMaxYear() {
+ style.variables.maxYear = parseInt(maxYearInput.value);
+ updateStatusText();
+}
function updateStatusText() {
const div = document.getElementById('status');
div.querySelector('span.min-year').textContent = minYearInput.value;
div.querySelector('span.max-year').textContent = maxYearInput.value;
}
-minYearInput.addEventListener('input', updateStatusText);
-minYearInput.addEventListener('change', updateStatusText);
-maxYearInput.addEventListener('input', updateStatusText);
-maxYearInput.addEventListener('change', updateStatusText);
+
+minYearInput.addEventListener('input', updateMinYear);
+minYearInput.addEventListener('change', updateMinYear);
+maxYearInput.addEventListener('input', updateMaxYear);
+maxYearInput.addEventListener('change', updateMaxYear);
updateStatusText();
-class WebglPointsLayer extends VectorLayer {
- createRenderer() {
- return new WebGLPointsLayerRenderer(this, {
- attributes: [
- {
- name: 'size',
- callback: function(feature) {
- return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
- }
- },
- {
- name: 'year',
- callback: function(feature) {
- return feature.get('year');
- }
- }
- ],
- vertexShader: [
- 'precision mediump float;',
+// load data
+const client = new XMLHttpRequest();
+client.open('GET', 'data/csv/meteorite_landings.csv');
+client.onload = function() {
+ const csv = client.responseText;
+ const features = [];
- 'uniform mat4 u_projectionMatrix;',
- 'uniform mat4 u_offsetScaleMatrix;',
- 'uniform mat4 u_offsetRotateMatrix;',
- 'attribute vec2 a_position;',
- 'attribute float a_index;',
- 'attribute float a_size;',
- 'attribute float a_year;',
- 'varying vec2 v_texCoord;',
- 'varying float v_year;',
+ let prevIndex = csv.indexOf('\n') + 1; // scan past the header line
- 'void main(void) {',
- ' mat4 offsetMatrix = u_offsetScaleMatrix;',
- ' float offsetX = a_index == 0.0 || a_index == 3.0 ? -a_size / 2.0 : a_size / 2.0;',
- ' float offsetY = a_index == 0.0 || a_index == 1.0 ? -a_size / 2.0 : a_size / 2.0;',
- ' vec4 offsets = offsetMatrix * vec4(offsetX, offsetY, 0.0, 0.0);',
- ' gl_Position = u_projectionMatrix * vec4(a_position, 0.0, 1.0) + offsets;',
- ' float u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;',
- ' float v = a_index == 0.0 || a_index == 1.0 ? 0.0 : 1.0;',
- ' v_texCoord = vec2(u, v);',
- ' v_year = a_year;',
- '}'
- ].join(' '),
- fragmentShader: [
- 'precision mediump float;',
+ let curIndex;
+ while ((curIndex = csv.indexOf('\n', prevIndex)) != -1) {
+ const line = csv.substr(prevIndex, curIndex - prevIndex).split(',');
+ prevIndex = curIndex + 1;
- 'uniform float u_time;',
- 'uniform float u_minYear;',
- 'uniform float u_maxYear;',
- 'varying vec2 v_texCoord;',
- 'varying float v_year;',
-
- 'void main(void) {',
-
- // filter out pixels if the year is outside of the given range
- ' if (v_year < u_minYear || v_year > u_maxYear) {',
- ' discard;',
- ' }',
-
- ' vec2 texCoord = v_texCoord * 2.0 - vec2(1.0, 1.0);',
- ' float sqRadius = texCoord.x * texCoord.x + texCoord.y * texCoord.y;',
- ' float value = 2.0 * (1.0 - sqRadius);',
- ' float alpha = smoothstep(0.0, 1.0, value);',
-
- // color is interpolated based on year
- ' float ratio = clamp((v_year - 1800.0) / (2013.0 - 1800.0), 0.0, 1.1);',
- ' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
- ' vec3(' + formatColor(newColor) + '), ratio);',
-
- ' float period = 8.0;',
- ' color.g *= 2.0 * (1.0 - sqrt(mod(u_time + v_year * 0.025, period) / period));',
-
- ' gl_FragColor = vec4(color, 1.0);',
- ' gl_FragColor.a *= alpha;',
- ' gl_FragColor.rgb *= gl_FragColor.a;',
- '}'
- ].join(' '),
- uniforms: {
- u_time: function() {
- return Date.now() * 0.001 - startTime;
- },
- u_minYear: function() {
- return parseInt(minYearInput.value);
- },
- u_maxYear: function() {
- return parseInt(maxYearInput.value);
- }
- }
- });
- }
-}
-
-
-function loadData() {
- const client = new XMLHttpRequest();
- client.open('GET', 'data/csv/meteorite_landings.csv');
- client.onload = function() {
- const csv = client.responseText;
- const features = [];
-
- let prevIndex = csv.indexOf('\n') + 1; // scan past the header line
-
- let curIndex;
- while ((curIndex = csv.indexOf('\n', prevIndex)) != -1) {
- const line = csv.substr(prevIndex, curIndex - prevIndex).split(',');
- prevIndex = curIndex + 1;
-
- const coords = fromLonLat([parseFloat(line[4]), parseFloat(line[3])]);
- if (isNaN(coords[0]) || isNaN(coords[1])) {
- // guard against bad data
- continue;
- }
-
- features.push(new Feature({
- mass: parseFloat(line[1]) || 0,
- year: parseInt(line[2]) || 0,
- geometry: new Point(coords)
- }));
+ const coords = fromLonLat([parseFloat(line[4]), parseFloat(line[3])]);
+ if (isNaN(coords[0]) || isNaN(coords[1])) {
+ // guard against bad data
+ continue;
}
- vectorSource.addFeatures(features);
- };
- client.send();
-}
+ features.push(new Feature({
+ mass: parseFloat(line[1]) || 0,
+ year: parseInt(line[2]) || 0,
+ geometry: new Point(coords)
+ }));
+ }
-loadData();
+ vectorSource.addFeatures(features);
+};
+client.send();
const map = new Map({
layers: [
@@ -167,7 +111,8 @@ const map = new Map({
layer: 'toner'
})
}),
- new WebglPointsLayer({
+ new WebGLPointsLayer({
+ style: style,
source: vectorSource
})
],