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 }) ],