Using WebGL to filter large quantities of features
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
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.
<!DOCTYPE html>
<html lang="en">
<head>
<title>Filtering features with WebGL</title>
<!-- The line below is only needed for old environments like Internet Explorer and Android 4.x -->
<script src="https://cdn.polyfill.io/v2/polyfill.min.js?features=requestAnimationFrame,Element.prototype.classList,URL"></script>
<style>
.map {
width: 100%;
height:400px;
}
</style>
</head>
<body>
<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>
<script src="index.js"></script>
</body>
</html>
import 'ol/ol.css';
import Map from 'ol/Map';
import View from 'ol/View';
import TileLayer from 'ol/layer/Tile';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';
import {Vector} from 'ol/source';
import {fromLonLat} from 'ol/proj';
import Stamen from 'ol/source/Stamen';
import WebGLPointsLayer from 'ol/layer/WebGLPoints';
var vectorSource = new Vector({
attributions: 'NASA'
});
var oldColor = 'rgba(242,56,22,0.61)';
var newColor = '#ffe52c';
var period = 12; // animation period in seconds
var animRatio =
['^',
['/',
['%',
['+',
['time'],
[
'interpolate',
['linear'],
['get', 'year'],
1850, 0,
2015, period
]
],
period
],
period
],
0.5
];
var style = {
variables: {
minYear: 1850,
maxYear: 2015
},
filter: ['between', ['get', 'year'], ['var', 'minYear'], ['var', 'maxYear']],
symbol: {
symbolType: 'circle',
size: ['*',
['interpolate', ['linear'], ['get', 'mass'], 0, 8, 200000, 26],
['-', 1.75, ['*', animRatio, 0.75]]
],
color: ['interpolate',
['linear'],
animRatio,
0, newColor,
1, oldColor
],
opacity: ['-', 1.0, ['*', animRatio, 0.75]]
}
};
// handle input values & events
var minYearInput = document.getElementById('min-year');
var 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() {
var div = document.getElementById('status');
div.querySelector('span.min-year').textContent = minYearInput.value;
div.querySelector('span.max-year').textContent = maxYearInput.value;
}
minYearInput.addEventListener('input', updateMinYear);
minYearInput.addEventListener('change', updateMinYear);
maxYearInput.addEventListener('input', updateMaxYear);
maxYearInput.addEventListener('change', updateMaxYear);
updateStatusText();
// load data
var client = new XMLHttpRequest();
client.open('GET', 'data/csv/meteorite_landings.csv');
client.onload = function() {
var csv = client.responseText;
var features = [];
var prevIndex = csv.indexOf('\n') + 1; // scan past the header line
var curIndex;
while ((curIndex = csv.indexOf('\n', prevIndex)) != -1) {
var line = csv.substr(prevIndex, curIndex - prevIndex).split(',');
prevIndex = curIndex + 1;
var 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)
}));
}
vectorSource.addFeatures(features);
};
client.send();
var map = new Map({
layers: [
new TileLayer({
source: new Stamen({
layer: 'toner'
})
}),
new WebGLPointsLayer({
style: style,
source: vectorSource,
disableHitDetection: true
})
],
target: document.getElementById('map'),
view: new View({
center: [0, 0],
zoom: 2
})
});
// animate the map
function animate() {
map.render();
window.requestAnimationFrame(animate);
}
animate();
{
"name": "filter-points-webgl",
"dependencies": {
"ol": "6.1.1"
},
"devDependencies": {
"parcel": "1.11.0"
},
"scripts": {
"start": "parcel index.html",
"build": "parcel build --experimental-scope-hoisting --public-url . index.html"
}
}