Add a new webgl example with real time feature filtering

This commit is contained in:
Olivier Guyot
2019-04-02 23:34:28 +02:00
parent c6a859d1ed
commit 8fb6ed5c6f
3 changed files with 45904 additions and 0 deletions

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,25 @@
---
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
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.
tags: "webgl, icon, sprite, filter, feature"
---
<div id="map" class="map"></div>
<form>
<div id="status">Show impacts between <span class="min-year"></span> and <span class="max-year"></span></div>
<label>Minimum year:</label>
<input id="min-year" type="range" min="1850" max="2015" step="1" value="1850"/>
<label>Maximum year:</label>
<input id="max-year" type="range" min="1850" max="2015" step="1" value="2015"/>
</form>

View File

@@ -0,0 +1,162 @@
import Map from '../src/ol/Map.js';
import View from '../src/ol/View.js';
import TileLayer from '../src/ol/layer/Tile.js';
import Feature from '../src/ol/Feature';
import Point from '../src/ol/geom/Point';
import VectorLayer from '../src/ol/layer/Vector';
import {Vector} from '../src/ol/source';
import {fromLonLat} from '../src/ol/proj';
import WebGLPointsLayerRenderer from '../src/ol/renderer/webgl/PointsLayer';
import {clamp, lerp} from '../src/ol/math';
import Stamen from '../src/ol/source/Stamen';
const features = [];
const vectorSource = new Vector({
features: [],
attributions: 'NASA'
});
const oldColor = [180, 140, 140];
const newColor = [255, 80, 80];
const startTime = Date.now() * 0.001;
// hanle input values & events
const minYearInput = document.getElementById('min-year');
const maxYearInput = document.getElementById('max-year');
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);
updateStatusText();
class WebglPointsLayer extends VectorLayer {
createRenderer() {
return new WebGLPointsLayerRenderer(this, {
colorCallback: function(feature, vertex, component) {
// component at index 3 is alpha
if (component === 3) {
return 1;
}
// color is interpolated based on year
const ratio = clamp((feature.get('year') - 1800) / (2013 - 1800), 0, 1);
return lerp(oldColor[component], newColor[component], ratio) / 255;
},
sizeCallback: function(feature) {
return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
},
fragmentShader: [
'precision mediump float;',
'uniform float u_time;',
'uniform float u_minYear;',
'uniform float u_maxYear;',
'varying vec2 v_texCoord;',
'varying float v_opacity;',
'varying vec4 v_color;',
'void main(void) {',
' float impactYear = v_opacity;',
// filter out pixels if the year is outside of the given range
' if (impactYear < u_minYear || v_opacity > 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);',
' vec3 color = v_color.rgb;',
' float period = 8.0;',
' color.g *= 2.0 * (1.0 - sqrt(mod(u_time + impactYear * 0.025, period) / period));',
' gl_FragColor = vec4(color, v_color.a);',
' gl_FragColor.a *= alpha;',
' gl_FragColor.rgb *= gl_FragColor.a;',
'}'
].join(' '),
opacityCallback: function(feature) {
// here the opacity channel of the vertices is used to store the year of impact
return feature.get('year');
},
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;
let curIndex;
let prevIndex = 0;
let line;
while ((curIndex = csv.indexOf('\n', prevIndex)) > 0) {
line = csv.substr(prevIndex, curIndex - prevIndex).split(',');
prevIndex = curIndex + 1;
// skip header
if (prevIndex === 0) {
continue;
}
const coords = fromLonLat([parseFloat(line[4]), parseFloat(line[3])]);
features.push(new Feature({
mass: parseFloat(line[1]) || 0,
year: parseInt(line[2]) || 0,
geometry: new Point(coords)
}));
}
vectorSource.addFeatures(features);
};
client.send();
}
loadData();
const map = new Map({
layers: [
new TileLayer({
source: new Stamen({
layer: 'toner'
})
}),
new WebglPointsLayer({
source: vectorSource
})
],
target: document.getElementById('map'),
view: new View({
center: [0, 0],
zoom: 2
})
});
// animate the map
function animate() {
map.render();
window.requestAnimationFrame(animate);
}
animate();