Using a WebGL-optimized layer to render a large quantities of points
This example shows how to use a WebGLPointsLayer
to show a large amount of points on the map.
The layer is given a style in JSON format which allows a certain level of customization of the final reprensentation.
The following operators can be used for numerical values:
["get", "attributeName"]
fetches a numeric attribute value for each feature["+", value, value]
adds two values (which an either be numeric, or the result of another operator)["*", value, value]
multiplies two values["clamp", value, min, max]
outputs a value between min
and max
["stretch", value, low1, high1, low2, high2]
outputs a value which has been mapped from the
low1..high1
range to the low2..high2
range<!DOCTYPE html>
<html lang="en">
<head>
<title>WebGL points layer</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>
<label>Choose a predefined style from the list below or edit it as JSON manually.</label><br>
<select id="style-select">
<option value="icons">Icons</option>
<option value="triangles">Triangles, color related to population</option>
<option value="triangles-latitude">Triangles, color related to latitude</option>
<option value="circles">Circles, size related to population</option>
<option value="circles-zoom">Circles, size related to zoom</option>
</select>
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
<small>
<span id="style-valid" style="display: none; color: forestgreen">✓ style is valid</span>
<span id="style-invalid" style="display: none; color: grey">✗ <span>style not yet valid...</span></span>
</small>
<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 WebGLPointsLayer from 'ol/layer/WebGLPoints';
import GeoJSON from 'ol/format/GeoJSON';
import Vector from 'ol/source/Vector';
import OSM from 'ol/source/OSM';
var vectorSource = new Vector({
url: 'data/geojson/world-cities.geojson',
format: new GeoJSON()
});
var predefinedStyles = {
'icons': {
symbol: {
symbolType: 'image',
src: 'data/icon.png',
size: [18, 28],
color: 'lightyellow',
rotateWithView: false,
offset: [0, 9]
}
},
'triangles': {
symbol: {
symbolType: 'triangle',
size: 18,
color: [
'interpolate',
['linear'],
['get', 'population'],
20000, '#5aca5b',
300000, '#ff6a19'
],
rotateWithView: true
}
},
'triangles-latitude': {
symbol: {
symbolType: 'triangle',
size: [
'interpolate',
['linear'],
['get', 'population'],
40000, 12,
2000000, 24
],
color: [
'interpolate',
['linear'],
['get', 'latitude'],
-60, '#ff14c3',
-20, '#ff621d',
20, '#ffed02',
60, '#00ff67'
],
offset: [0, 0],
opacity: 0.95
}
},
'circles': {
symbol: {
symbolType: 'circle',
size: [
'interpolate',
['linear'],
['get', 'population'],
40000, 8,
2000000, 28
],
color: '#006688',
rotateWithView: false,
offset: [0, 0],
opacity: [
'interpolate',
['linear'],
['get', 'population'],
40000, 0.6,
2000000, 0.92
]
}
},
'circles-zoom': {
symbol: {
symbolType: 'circle',
size: [
'interpolate',
['exponential', 2.5],
['zoom'],
2, 1,
14, 32
],
color: '#240572',
offset: [0, 0],
opacity: 0.95
}
}
};
var map = new Map({
layers: [
new TileLayer({
source: new OSM()
})
],
target: document.getElementById('map'),
view: new View({
center: [0, 0],
zoom: 2
})
});
var literalStyle;
var pointsLayer;
function refreshLayer(newStyle) {
var previousLayer = pointsLayer;
pointsLayer = new WebGLPointsLayer({
source: vectorSource,
style: newStyle,
disableHitDetection: true
});
map.addLayer(pointsLayer);
if (previousLayer) {
map.removeLayer(previousLayer);
previousLayer.dispose();
}
literalStyle = newStyle;
}
var spanValid = document.getElementById('style-valid');
var spanInvalid = document.getElementById('style-invalid');
function setStyleStatus(errorMsg) {
var isError = typeof errorMsg === 'string';
spanValid.style.display = errorMsg === null ? 'initial' : 'none';
spanInvalid.firstElementChild.innerText = isError ? errorMsg : '';
spanInvalid.style.display = isError ? 'initial' : 'none';
}
var editor = document.getElementById('style-editor');
editor.addEventListener('input', function() {
var textStyle = editor.value;
try {
var newLiteralStyle = JSON.parse(textStyle);
if (JSON.stringify(newLiteralStyle) !== JSON.stringify(literalStyle)) {
refreshLayer(newLiteralStyle);
}
setStyleStatus(null);
} catch (e) {
setStyleStatus(e.message);
}
});
var select = document.getElementById('style-select');
select.value = 'circles';
function onSelectChange() {
var style = select.value;
var newLiteralStyle = predefinedStyles[style];
editor.value = JSON.stringify(newLiteralStyle, null, 2);
try {
refreshLayer(newLiteralStyle);
setStyleStatus();
} catch (e) {
setStyleStatus(e.message);
}
}
onSelectChange();
select.addEventListener('change', onSelectChange);
{
"name": "webgl-points-layer",
"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"
}
}