Merge pull request #9994 from jahow/add-webgl-layer-type
Add a new layer type: WebGLPointsLayer
This commit is contained in:
1
examples/data/geojson/world-cities.geojson
Normal file
1
examples/data/geojson/world-cities.geojson
Normal file
File diff suppressed because one or more lines are too long
@@ -7,8 +7,9 @@ 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, lerp} from '../src/ol/math.js';
|
||||
import {clamp} from '../src/ol/math.js';
|
||||
import Stamen from '../src/ol/source/Stamen.js';
|
||||
import {formatColor} from '../src/ol/webgl/ShaderBuilder.js';
|
||||
|
||||
const vectorSource = new Vector({
|
||||
attributions: 'NASA'
|
||||
@@ -36,36 +37,58 @@ updateStatusText();
|
||||
class WebglPointsLayer extends VectorLayer {
|
||||
createRenderer() {
|
||||
return new WebGLPointsLayerRenderer(this, {
|
||||
colorCallback: function(feature, color) {
|
||||
// color is interpolated based on year
|
||||
const ratio = clamp((feature.get('year') - 1800) / (2013 - 1800), 0, 1);
|
||||
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;',
|
||||
|
||||
color[0] = lerp(oldColor[0], newColor[0], ratio) / 255;
|
||||
color[1] = lerp(oldColor[1], newColor[1], ratio) / 255;
|
||||
color[2] = lerp(oldColor[2], newColor[2], ratio) / 255;
|
||||
color[3] = 1;
|
||||
'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;',
|
||||
|
||||
return color;
|
||||
},
|
||||
sizeCallback: function(feature) {
|
||||
return 18 * clamp(feature.get('mass') / 200000, 0, 1) + 8;
|
||||
},
|
||||
'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;',
|
||||
|
||||
'uniform float u_time;',
|
||||
'uniform float u_minYear;',
|
||||
'uniform float u_maxYear;',
|
||||
|
||||
'varying vec2 v_texCoord;',
|
||||
'varying float v_opacity;',
|
||||
'varying vec4 v_color;',
|
||||
'varying float v_year;',
|
||||
|
||||
'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) {',
|
||||
' if (v_year < u_minYear || v_year > u_maxYear) {',
|
||||
' discard;',
|
||||
' }',
|
||||
|
||||
@@ -74,19 +97,19 @@ class WebglPointsLayer extends VectorLayer {
|
||||
' 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));',
|
||||
// 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);',
|
||||
|
||||
' gl_FragColor = vec4(color, v_color.a);',
|
||||
' 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(' '),
|
||||
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;
|
||||
|
||||
@@ -8,7 +8,7 @@ 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 {lerp} from '../src/ol/math.js';
|
||||
import {formatColor, formatNumber} from '../src/ol/webgl/ShaderBuilder.js';
|
||||
|
||||
const key = 'pk.eyJ1IjoidHNjaGF1YiIsImEiOiJjaW5zYW5lNHkxMTNmdWttM3JyOHZtMmNtIn0.CDIBD8H-G2Gf-cPkIuWtRg';
|
||||
|
||||
@@ -21,47 +21,153 @@ const texture = new Image();
|
||||
texture.src = 'data/ufo_shapes.png';
|
||||
|
||||
// This describes the content of the associated sprite sheet
|
||||
// coords are u0, v0, u1, v1 for a given shape
|
||||
// coords are u0, v0 for a given shape (all icons have a size of 0.25 x 0.5)
|
||||
const shapeTextureCoords = {
|
||||
'light': [0, 0.5, 0.25, 0],
|
||||
'sphere': [0.25, 0.5, 0.5, 0],
|
||||
'circle': [0.25, 0.5, 0.5, 0],
|
||||
'disc': [0.5, 0.5, 0.75, 0],
|
||||
'oval': [0.5, 0.5, 0.75, 0],
|
||||
'triangle': [0.75, 0.5, 1, 0],
|
||||
'fireball': [0, 1, 0.25, 0.5],
|
||||
'default': [0.75, 1, 1, 0.5]
|
||||
'light': [0, 0],
|
||||
'sphere': [0.25, 0],
|
||||
'circle': [0.25, 0],
|
||||
'disc': [0.5, 0],
|
||||
'oval': [0.5, 0],
|
||||
'triangle': [0.75, 0],
|
||||
'fireball': [0, 0.5],
|
||||
'default': [0.75, 0.5]
|
||||
};
|
||||
|
||||
const oldColor = [255, 160, 110];
|
||||
const newColor = [180, 255, 200];
|
||||
const size = 16;
|
||||
|
||||
class WebglPointsLayer extends VectorLayer {
|
||||
createRenderer() {
|
||||
return new WebGLPointsLayerRenderer(this, {
|
||||
texture: texture,
|
||||
colorCallback: function(feature, color) {
|
||||
// color is interpolated based on year (min is 1910, max is 2013)
|
||||
// please note: most values are between 2000-2013
|
||||
const ratio = (feature.get('year') - 1950) / (2013 - 1950);
|
||||
|
||||
color[0] = lerp(oldColor[0], newColor[0], ratio * ratio) / 255;
|
||||
color[1] = lerp(oldColor[1], newColor[1], ratio * ratio) / 255;
|
||||
color[2] = lerp(oldColor[2], newColor[2], ratio * ratio) / 255;
|
||||
color[3] = 1;
|
||||
|
||||
return color;
|
||||
},
|
||||
texCoordCallback: function(feature, component) {
|
||||
let coords = shapeTextureCoords[feature.get('shape')];
|
||||
if (!coords) {
|
||||
coords = shapeTextureCoords['default'];
|
||||
attributes: [
|
||||
{
|
||||
name: 'year',
|
||||
callback: function(feature) {
|
||||
return feature.get('year');
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'texCoordU',
|
||||
callback: function(feature) {
|
||||
let coords = shapeTextureCoords[feature.get('shape')];
|
||||
if (!coords) {
|
||||
coords = shapeTextureCoords['default'];
|
||||
}
|
||||
return coords[0];
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'texCoordV',
|
||||
callback: function(feature) {
|
||||
let coords = shapeTextureCoords[feature.get('shape')];
|
||||
if (!coords) {
|
||||
coords = shapeTextureCoords['default'];
|
||||
}
|
||||
return coords[1];
|
||||
}
|
||||
}
|
||||
return coords[component];
|
||||
],
|
||||
uniforms: {
|
||||
u_texture: texture
|
||||
},
|
||||
sizeCallback: function() {
|
||||
return 16;
|
||||
}
|
||||
vertexShader: [
|
||||
'precision mediump float;',
|
||||
|
||||
'uniform mat4 u_projectionMatrix;',
|
||||
'uniform mat4 u_offsetScaleMatrix;',
|
||||
'uniform mat4 u_offsetRotateMatrix;',
|
||||
'attribute vec2 a_position;',
|
||||
'attribute float a_index;',
|
||||
'attribute float a_year;',
|
||||
'attribute float a_texCoordU;',
|
||||
'attribute float a_texCoordV;',
|
||||
'varying vec2 v_texCoord;',
|
||||
'varying float v_year;',
|
||||
|
||||
'void main(void) {',
|
||||
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
||||
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||
' 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 ? a_texCoordU : a_texCoordU + 0.25;',
|
||||
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
||||
' v_texCoord = vec2(u, v);',
|
||||
' v_year = a_year;',
|
||||
'}'
|
||||
].join(' '),
|
||||
fragmentShader: [
|
||||
'precision mediump float;',
|
||||
|
||||
'uniform float u_time;',
|
||||
'uniform float u_minYear;',
|
||||
'uniform float u_maxYear;',
|
||||
'uniform sampler2D u_texture;',
|
||||
'varying vec2 v_texCoord;',
|
||||
'varying float v_year;',
|
||||
|
||||
'void main(void) {',
|
||||
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
||||
' if (textureColor.a < 0.1) {',
|
||||
' discard;',
|
||||
' }',
|
||||
|
||||
// color is interpolated based on year
|
||||
' float ratio = clamp((v_year - 1950.0) / (2013.0 - 1950.0), 0.0, 1.1);',
|
||||
' vec3 color = mix(vec3(' + formatColor(oldColor) + '),',
|
||||
' vec3(' + formatColor(newColor) + '), ratio);',
|
||||
|
||||
' gl_FragColor = vec4(color, 1.0) * textureColor;',
|
||||
' gl_FragColor.rgb *= gl_FragColor.a;',
|
||||
'}'
|
||||
].join(' '),
|
||||
hitVertexShader: [
|
||||
'precision mediump float;',
|
||||
|
||||
'uniform mat4 u_projectionMatrix;',
|
||||
'uniform mat4 u_offsetScaleMatrix;',
|
||||
'uniform mat4 u_offsetRotateMatrix;',
|
||||
'attribute vec2 a_position;',
|
||||
'attribute float a_index;',
|
||||
'attribute vec4 a_hitColor;',
|
||||
'attribute float a_texCoordU;',
|
||||
'attribute float a_texCoordV;',
|
||||
'varying vec2 v_texCoord;',
|
||||
'varying vec4 v_hitColor;',
|
||||
|
||||
'void main(void) {',
|
||||
' mat4 offsetMatrix = u_offsetScaleMatrix;',
|
||||
' float offsetX = a_index == 0.0 || a_index == 3.0 ? ',
|
||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||
' float offsetY = a_index == 0.0 || a_index == 1.0 ? ',
|
||||
' ' + formatNumber(-size / 2) + ' : ' + formatNumber(size / 2) + ';',
|
||||
' 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 ? a_texCoordU : a_texCoordU + 0.25;',
|
||||
' float v = a_index == 2.0 || a_index == 3.0 ? a_texCoordV : a_texCoordV + 0.5;',
|
||||
' v_texCoord = vec2(u, v);',
|
||||
' v_hitColor = a_hitColor;',
|
||||
'}'
|
||||
].join(' '),
|
||||
hitFragmentShader: [
|
||||
'precision mediump float;',
|
||||
|
||||
'uniform sampler2D u_texture;',
|
||||
'varying vec2 v_texCoord;',
|
||||
'varying vec4 v_hitColor;',
|
||||
|
||||
'void main(void) {',
|
||||
' vec4 textureColor = texture2D(u_texture, v_texCoord);',
|
||||
' if (textureColor.a < 0.1) {',
|
||||
' discard;',
|
||||
' }',
|
||||
|
||||
' gl_FragColor = v_hitColor;',
|
||||
'}'
|
||||
].join(' ')
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
19
examples/webgl-points-layer.html
Normal file
19
examples/webgl-points-layer.html
Normal file
@@ -0,0 +1,19 @@
|
||||
---
|
||||
layout: example.html
|
||||
title: WebGL points layer
|
||||
shortdesc: Using a WebGL-optimized layer to render a large quantities of points
|
||||
docs: >
|
||||
<p>This example shows how to use a `WebGLPointsLayer` to show a large amount of points on the map.</p>
|
||||
|
||||
tags: "webgl, point, layer, feature"
|
||||
---
|
||||
|
||||
<div id="map" class="map"></div>
|
||||
<label>Current style used for the layer</label>
|
||||
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
|
||||
<small id="style-valid" style="display: none; color: forestgreen">
|
||||
✓ style is valid
|
||||
</small>
|
||||
<small id="style-invalid" style="display: none; color: grey">
|
||||
✗ style not yet valid...
|
||||
</small>
|
||||
66
examples/webgl-points-layer.js
Normal file
66
examples/webgl-points-layer.js
Normal file
@@ -0,0 +1,66 @@
|
||||
import Map from '../src/ol/Map.js';
|
||||
import View from '../src/ol/View.js';
|
||||
import TileLayer from '../src/ol/layer/Tile.js';
|
||||
import WebGLPointsLayer from '../src/ol/layer/WebGLPoints.js';
|
||||
import GeoJSON from '../src/ol/format/GeoJSON.js';
|
||||
import Vector from '../src/ol/source/Vector.js';
|
||||
import OSM from '../src/ol/source/OSM.js';
|
||||
|
||||
const vectorSource = new Vector({
|
||||
url: 'data/geojson/world-cities.geojson',
|
||||
format: new GeoJSON()
|
||||
});
|
||||
|
||||
let literalStyle = {
|
||||
symbol: {
|
||||
size: 4,
|
||||
color: '#3388FF',
|
||||
rotateWithView: false,
|
||||
offset: [0, 0],
|
||||
opacity: 1
|
||||
}
|
||||
};
|
||||
let pointsLayer;
|
||||
|
||||
const map = new Map({
|
||||
layers: [
|
||||
new TileLayer({
|
||||
source: new OSM()
|
||||
})
|
||||
],
|
||||
target: document.getElementById('map'),
|
||||
view: new View({
|
||||
center: [0, 0],
|
||||
zoom: 2
|
||||
})
|
||||
});
|
||||
|
||||
const editor = document.getElementById('style-editor');
|
||||
|
||||
function refreshLayer() {
|
||||
if (pointsLayer) {
|
||||
map.removeLayer(pointsLayer);
|
||||
}
|
||||
pointsLayer = new WebGLPointsLayer({
|
||||
source: vectorSource,
|
||||
style: literalStyle
|
||||
});
|
||||
map.addLayer(pointsLayer);
|
||||
editor.value = JSON.stringify(literalStyle, null, ' ');
|
||||
}
|
||||
|
||||
function setStyleStatus(valid) {
|
||||
document.getElementById('style-valid').style.display = valid ? 'initial' : 'none';
|
||||
document.getElementById('style-invalid').style.display = !valid ? 'initial' : 'none';
|
||||
}
|
||||
|
||||
editor.addEventListener('input', function() {
|
||||
try {
|
||||
literalStyle = JSON.parse(editor.value);
|
||||
refreshLayer();
|
||||
setStyleStatus(true);
|
||||
} catch (e) {
|
||||
setStyleStatus(false);
|
||||
}
|
||||
});
|
||||
refreshLayer();
|
||||
Reference in New Issue
Block a user