Merge pull request #10016 from jahow/webgl-attribute-link
WebGL Points Layer: allow expressions in literal style
This commit is contained in:
@@ -3,13 +3,31 @@ layout: example.html
|
|||||||
title: WebGL points layer
|
title: WebGL points layer
|
||||||
shortdesc: Using a WebGL-optimized layer to render a large quantities of points
|
shortdesc: Using a WebGL-optimized layer to render a large quantities of points
|
||||||
docs: >
|
docs: >
|
||||||
<p>This example shows how to use a `WebGLPointsLayer` to show a large amount of points on the map.</p>
|
<p>This example shows how to use a <code>WebGLPointsLayer</code> to show a large amount of points on the map.</p>
|
||||||
|
<p>The layer is given a style in JSON format which allows a certain level of customization of the final reprensentation.</p>
|
||||||
|
<p>
|
||||||
|
The following operators can be used for numerical values:
|
||||||
|
<ul>
|
||||||
|
<li><code>["get", "attributeName"]</code> fetches a numeric attribute value for each feature</li>
|
||||||
|
<li><code>["+", value, value]</code> adds two values (which an either be numeric, or the result of another operator)</li>
|
||||||
|
<li><code>["*", value, value]</code> multiplies two values</li>
|
||||||
|
<li><code>["clamp", value, min, max]</code> outputs a value between <code>min</code> and <code>max</code></li>
|
||||||
|
<li><code>["stretch", value, low1, high1, low2, high2]</code> outputs a value which has been mapped from the
|
||||||
|
<code>low1..high1</code> range to the <code>low2..high2</code> range</li>
|
||||||
|
</ul>
|
||||||
|
</p>
|
||||||
|
|
||||||
tags: "webgl, point, layer, feature"
|
tags: "webgl, point, layer, feature"
|
||||||
---
|
---
|
||||||
|
|
||||||
<div id="map" class="map"></div>
|
<div id="map" class="map"></div>
|
||||||
<label>Current style used for the layer</label>
|
<label>Choose a predefined style from the list below or edit it as JSON manually.</label><br>
|
||||||
|
<select id="style-select">
|
||||||
|
<option>Predefined styles</option>
|
||||||
|
<option value="icons">Icons</option>
|
||||||
|
<option value="triangles">Triangles, color related to population</option>
|
||||||
|
<option value="circles">Circles, size related to population</option>
|
||||||
|
</select>
|
||||||
<textarea style="width: 100%; height: 20rem; font-family: monospace; font-size: small;" id="style-editor"></textarea>
|
<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">
|
<small id="style-valid" style="display: none; color: forestgreen">
|
||||||
✓ style is valid
|
✓ style is valid
|
||||||
|
|||||||
@@ -11,15 +11,42 @@ const vectorSource = new Vector({
|
|||||||
format: new GeoJSON()
|
format: new GeoJSON()
|
||||||
});
|
});
|
||||||
|
|
||||||
let literalStyle = {
|
const predefinedStyles = {
|
||||||
|
'icons': {
|
||||||
symbol: {
|
symbol: {
|
||||||
size: 4,
|
symbolType: 'image',
|
||||||
color: '#3388FF',
|
src: 'data/icon.png',
|
||||||
|
size: [18, 28],
|
||||||
|
color: 'lightyellow',
|
||||||
|
rotateWithView: false,
|
||||||
|
offset: [0, 9]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'triangles': {
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'triangle',
|
||||||
|
size: 18,
|
||||||
|
color: [
|
||||||
|
['stretch', ['get', 'population'], 20000, 300000, 0.1, 1.0],
|
||||||
|
['stretch', ['get', 'population'], 20000, 300000, 0.6, 0.3],
|
||||||
|
0.6,
|
||||||
|
1.0
|
||||||
|
],
|
||||||
|
rotateWithView: true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
'circles': {
|
||||||
|
symbol: {
|
||||||
|
symbolType: 'circle',
|
||||||
|
size: ['stretch', ['get', 'population'], 40000, 2000000, 8, 28],
|
||||||
|
color: '#006688',
|
||||||
rotateWithView: false,
|
rotateWithView: false,
|
||||||
offset: [0, 0],
|
offset: [0, 0],
|
||||||
opacity: 1
|
opacity: ['stretch', ['get', 'population'], 40000, 2000000, 0.6, 0.92]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
let literalStyle = predefinedStyles['circles'];
|
||||||
let pointsLayer;
|
let pointsLayer;
|
||||||
|
|
||||||
const map = new Map({
|
const map = new Map({
|
||||||
@@ -36,6 +63,7 @@ const map = new Map({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const editor = document.getElementById('style-editor');
|
const editor = document.getElementById('style-editor');
|
||||||
|
editor.value = JSON.stringify(literalStyle, null, 2);
|
||||||
|
|
||||||
function refreshLayer() {
|
function refreshLayer() {
|
||||||
if (pointsLayer) {
|
if (pointsLayer) {
|
||||||
@@ -46,7 +74,6 @@ function refreshLayer() {
|
|||||||
style: literalStyle
|
style: literalStyle
|
||||||
});
|
});
|
||||||
map.addLayer(pointsLayer);
|
map.addLayer(pointsLayer);
|
||||||
editor.value = JSON.stringify(literalStyle, null, ' ');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setStyleStatus(valid) {
|
function setStyleStatus(valid) {
|
||||||
@@ -55,8 +82,13 @@ function setStyleStatus(valid) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
editor.addEventListener('input', function() {
|
editor.addEventListener('input', function() {
|
||||||
|
const textStyle = editor.value;
|
||||||
|
if (JSON.stringify(JSON.parse(textStyle)) === JSON.stringify(literalStyle)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
literalStyle = JSON.parse(editor.value);
|
literalStyle = JSON.parse(textStyle);
|
||||||
refreshLayer();
|
refreshLayer();
|
||||||
setStyleStatus(true);
|
setStyleStatus(true);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -64,3 +96,11 @@ editor.addEventListener('input', function() {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
refreshLayer();
|
refreshLayer();
|
||||||
|
|
||||||
|
const select = document.getElementById('style-select');
|
||||||
|
select.addEventListener('change', function() {
|
||||||
|
const style = select.value;
|
||||||
|
literalStyle = predefinedStyles[style];
|
||||||
|
editor.value = JSON.stringify(literalStyle, null, 2);
|
||||||
|
refreshLayer();
|
||||||
|
});
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ const vector = new WebGLPointsLayer({
|
|||||||
}),
|
}),
|
||||||
style: {
|
style: {
|
||||||
symbol: {
|
symbol: {
|
||||||
|
symbolType: 'square',
|
||||||
size: 4,
|
size: 4,
|
||||||
color: 'white'
|
color: 'white'
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,8 @@
|
|||||||
*/
|
*/
|
||||||
import {assign} from '../obj.js';
|
import {assign} from '../obj.js';
|
||||||
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
import WebGLPointsLayerRenderer from '../renderer/webgl/PointsLayer.js';
|
||||||
import {
|
import {getSymbolFragmentShader, getSymbolVertexShader, parseSymbolStyle} from '../webgl/ShaderBuilder.js';
|
||||||
formatArray,
|
|
||||||
formatColor,
|
|
||||||
formatNumber,
|
|
||||||
getSymbolFragmentShader,
|
|
||||||
getSymbolVertexShader
|
|
||||||
} from '../webgl/ShaderBuilder.js';
|
|
||||||
import {assert} from '../asserts.js';
|
import {assert} from '../asserts.js';
|
||||||
import {asArray} from '../color.js';
|
|
||||||
import Layer from './Layer.js';
|
import Layer from './Layer.js';
|
||||||
|
|
||||||
|
|
||||||
@@ -90,42 +83,13 @@ class WebGLPointsLayer extends Layer {
|
|||||||
* @inheritDoc
|
* @inheritDoc
|
||||||
*/
|
*/
|
||||||
createRenderer() {
|
createRenderer() {
|
||||||
const symbolStyle = this.style.symbol;
|
const parseResult = parseSymbolStyle(this.style.symbol);
|
||||||
const size = Array.isArray(symbolStyle.size) ?
|
|
||||||
formatArray(symbolStyle.size) : formatNumber(symbolStyle.size);
|
|
||||||
const color = symbolStyle.color !== undefined ?
|
|
||||||
(typeof symbolStyle.color === 'string' ? asArray(symbolStyle.color) : symbolStyle.color) :
|
|
||||||
[255, 255, 255, 1];
|
|
||||||
const texCoord = symbolStyle.textureCoord || [0, 0, 1, 1];
|
|
||||||
const offset = symbolStyle.offset || [0, 0];
|
|
||||||
const opacity = symbolStyle.opacity !== undefined ? symbolStyle.opacity : 1;
|
|
||||||
|
|
||||||
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
|
||||||
const params = {
|
|
||||||
uniforms: [],
|
|
||||||
colorExpression: 'vec4(' + formatColor(color) + ') * vec4(1.0, 1.0, 1.0, ' + formatNumber(opacity) + ')',
|
|
||||||
sizeExpression: 'vec2(' + size + ')',
|
|
||||||
offsetExpression: 'vec2(' + formatArray(offset) + ')',
|
|
||||||
texCoordExpression: 'vec4(' + formatArray(texCoord) + ')',
|
|
||||||
rotateWithView: !!symbolStyle.rotateWithView
|
|
||||||
};
|
|
||||||
|
|
||||||
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
|
||||||
const uniforms = {};
|
|
||||||
|
|
||||||
if (symbolStyle.symbolType === 'image' && symbolStyle.src) {
|
|
||||||
const texture = new Image();
|
|
||||||
texture.src = symbolStyle.src;
|
|
||||||
params.uniforms.push('sampler2D u_texture');
|
|
||||||
params.colorExpression = params.colorExpression +
|
|
||||||
' * texture2D(u_texture, v_texCoord);';
|
|
||||||
uniforms['u_texture'] = texture;
|
|
||||||
}
|
|
||||||
|
|
||||||
return new WebGLPointsLayerRenderer(this, {
|
return new WebGLPointsLayerRenderer(this, {
|
||||||
vertexShader: getSymbolVertexShader(params),
|
vertexShader: getSymbolVertexShader(parseResult.params),
|
||||||
fragmentShader: getSymbolFragmentShader(params),
|
fragmentShader: getSymbolFragmentShader(parseResult.params),
|
||||||
uniforms: uniforms
|
uniforms: parseResult.uniforms,
|
||||||
|
attributes: parseResult.attributes
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,8 @@
|
|||||||
* @module ol/webgl/ShaderBuilder
|
* @module ol/webgl/ShaderBuilder
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import {asArray} from '../color.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Will return the number as a float with a dot separator, which is required by GLSL.
|
* Will return the number as a float with a dot separator, which is required by GLSL.
|
||||||
* @param {number} v Numerical value.
|
* @param {number} v Numerical value.
|
||||||
@@ -65,6 +67,9 @@ export function formatColor(colorArray) {
|
|||||||
* The following attributes are hardcoded and expected to be present in the vertex buffers:
|
* The following attributes are hardcoded and expected to be present in the vertex buffers:
|
||||||
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
|
* `vec2 a_position`, `float a_index` (being the index of the vertex in the quad, 0 to 3).
|
||||||
*
|
*
|
||||||
|
* The following varyings are hardcoded and gives the coordinate of the pixel both in the quad on the texture:
|
||||||
|
* `vec2 v_quadCoord`, `vec2 v_texCoord`
|
||||||
|
*
|
||||||
* @param {ShaderParameters} parameters Parameters for the shader.
|
* @param {ShaderParameters} parameters Parameters for the shader.
|
||||||
* @returns {string} The full shader as a string.
|
* @returns {string} The full shader as a string.
|
||||||
*/
|
*/
|
||||||
@@ -90,6 +95,7 @@ ${attributes.map(function(attribute) {
|
|||||||
return 'attribute ' + attribute + ';';
|
return 'attribute ' + attribute + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
${varyings.map(function(varying) {
|
${varyings.map(function(varying) {
|
||||||
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
@@ -105,6 +111,9 @@ void main(void) {
|
|||||||
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
v_texCoord = vec2(u, v);
|
v_texCoord = vec2(u, v);
|
||||||
|
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v_quadCoord = vec2(u, v);
|
||||||
${varyings.map(function(varying) {
|
${varyings.map(function(varying) {
|
||||||
return ' ' + varying.name + ' = ' + varying.expression + ';';
|
return ' ' + varying.name + ' = ' + varying.expression + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
@@ -131,6 +140,7 @@ ${uniforms.map(function(uniform) {
|
|||||||
return 'uniform ' + uniform + ';';
|
return 'uniform ' + uniform + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
${varyings.map(function(varying) {
|
${varyings.map(function(varying) {
|
||||||
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
return 'varying ' + varying.type + ' ' + varying.name + ';';
|
||||||
}).join('\n')}
|
}).join('\n')}
|
||||||
@@ -141,3 +151,166 @@ void main(void) {
|
|||||||
|
|
||||||
return body;
|
return body;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base type for values fed to operators; can be a number literal or the output of another operator
|
||||||
|
* @typedef {Array<*>|number} OperatorValue
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses the provided expressions and produces a GLSL-compatible assignment string, such as:
|
||||||
|
* `['add', ['*', ['get', 'size'], 0.001], 12] => '(a_size * (0.001)) + (12.0)'
|
||||||
|
*
|
||||||
|
* The following operators can be used:
|
||||||
|
* * `['get', 'attributeName']` fetches a feature attribute (it will be prefixed by `a_` in the shader)
|
||||||
|
* * `['*', value1, value1]` multiplies value1 by value2
|
||||||
|
* * `['+', value1, value1]` adds value1 and value2
|
||||||
|
* * `['clamp', value1, value2, value3]` clamps value1 between values2 and value3
|
||||||
|
* * `['stretch', value1, value2, value3, value4, value5]` maps value1 from [value2, value3] range to
|
||||||
|
* [value4, value5] range, clamping values along the way
|
||||||
|
*
|
||||||
|
* Values can either be literals (numbers) or another operator, as they will be evaluated recursively.
|
||||||
|
*
|
||||||
|
* Also takes in an array where new attributes will be pushed, so that the user of the `parse` function
|
||||||
|
* knows which attributes are expected to be available at evaluation time.
|
||||||
|
*
|
||||||
|
* A prefix must be specified so that the attributes can either be written as `a_name` or `v_name` in
|
||||||
|
* the final assignment string.
|
||||||
|
*
|
||||||
|
* @param {OperatorValue} value Either literal or an operator.
|
||||||
|
* @param {Array<string>} attributes Array containing the attribute names prefixed with `a_`; it
|
||||||
|
* it passed along recursively
|
||||||
|
* @param {string} attributePrefix Prefix added to attribute names in the final output (typically `a_` or `v_`).
|
||||||
|
* @returns {string} Assignment string.
|
||||||
|
*/
|
||||||
|
export function parse(value, attributes, attributePrefix) {
|
||||||
|
const v = value;
|
||||||
|
function p(value) {
|
||||||
|
return parse(value, attributes, attributePrefix);
|
||||||
|
}
|
||||||
|
if (Array.isArray(v)) {
|
||||||
|
switch (v[0]) {
|
||||||
|
case 'get':
|
||||||
|
if (attributes.indexOf(v[1]) === -1) {
|
||||||
|
attributes.push(v[1]);
|
||||||
|
}
|
||||||
|
return attributePrefix + v[1];
|
||||||
|
case '*': return `(${p(v[1])} * ${p(v[2])})`;
|
||||||
|
case '+': return `(${p(v[1])} + ${p(v[2])})`;
|
||||||
|
case 'clamp': return `clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])})`;
|
||||||
|
case 'stretch': return `(clamp(${p(v[1])}, ${p(v[2])}, ${p(v[3])}) * ((${p(v[5])} - ${p(v[4])}) / (${p(v[3])} - ${p(v[2])})) + ${p(v[4])})`;
|
||||||
|
default: throw new Error('Unrecognized literal style expression: ' + JSON.stringify(value));
|
||||||
|
}
|
||||||
|
} else if (typeof value === 'number') {
|
||||||
|
return formatNumber(value);
|
||||||
|
} else {
|
||||||
|
throw new Error('Invalid value type in expression: ' + JSON.stringify(value));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {Object} StyleParseResult
|
||||||
|
* @property {ShaderParameters} params Symbol shader params.
|
||||||
|
* @property {Object.<string,import("./Helper").UniformValue>} uniforms Uniform definitions.
|
||||||
|
* @property {Array<import("../renderer/webgl/PointsLayer").CustomAttribute>} attributes Attribute descriptions.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a {@link import("../style/LiteralStyle").LiteralSymbolStyle} object and outputs shader parameters to be
|
||||||
|
* then fed to {@link getSymbolVertexShader} and {@link getSymbolFragmentShader}.
|
||||||
|
*
|
||||||
|
* Also returns `uniforms` and `attributes` properties as expected by the
|
||||||
|
* {@link module:ol/renderer/webgl/PointsLayer~WebGLPointsLayerRenderer}.
|
||||||
|
*
|
||||||
|
* @param {import("../style/LiteralStyle").LiteralSymbolStyle} style Symbol style.
|
||||||
|
* @returns {StyleParseResult} Result containing shader params, attributes and uniforms.
|
||||||
|
*/
|
||||||
|
export function parseSymbolStyle(style) {
|
||||||
|
const size = Array.isArray(style.size) && typeof style.size[0] == 'number' ?
|
||||||
|
style.size : [style.size, style.size];
|
||||||
|
const color = (typeof style.color === 'string' ?
|
||||||
|
asArray(style.color).map(function(c, i) {
|
||||||
|
return i < 3 ? c / 255 : c;
|
||||||
|
}) :
|
||||||
|
style.color || [255, 255, 255, 1]);
|
||||||
|
const texCoord = style.textureCoord || [0, 0, 1, 1];
|
||||||
|
const offset = style.offset || [0, 0];
|
||||||
|
const opacity = style.opacity !== undefined ? style.opacity : 1;
|
||||||
|
|
||||||
|
let attributes = [];
|
||||||
|
const varyings = [];
|
||||||
|
function pA(value) {
|
||||||
|
return parse(value, attributes, 'a_');
|
||||||
|
}
|
||||||
|
function pV(value) {
|
||||||
|
return parse(value, varyings, 'v_');
|
||||||
|
}
|
||||||
|
|
||||||
|
let opacityFilter = '1.0';
|
||||||
|
const visibleSize = pV(size[0]);
|
||||||
|
switch (style.symbolType) {
|
||||||
|
case 'square': break;
|
||||||
|
case 'image': break;
|
||||||
|
// taken from https://thebookofshaders.com/07/
|
||||||
|
case 'circle':
|
||||||
|
opacityFilter = `(1.0-smoothstep(1.-4./${visibleSize},1.,dot(v_quadCoord-.5,v_quadCoord-.5)*4.))`;
|
||||||
|
break;
|
||||||
|
case 'triangle':
|
||||||
|
const st = '(v_quadCoord*2.-1.)';
|
||||||
|
const a = `(atan(${st}.x,${st}.y))`;
|
||||||
|
opacityFilter = `(1.0-smoothstep(.5-3./${visibleSize},.5,cos(floor(.5+${a}/2.094395102)*2.094395102-${a})*length(${st})))`;
|
||||||
|
break;
|
||||||
|
|
||||||
|
default: throw new Error('Unexpected symbol type: ' + style.symbolType);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @type {import('../webgl/ShaderBuilder.js').ShaderParameters} */
|
||||||
|
const params = {
|
||||||
|
uniforms: [],
|
||||||
|
colorExpression: `vec4(${pV(color[0])}, ${pV(color[1])}, ${pV(color[2])}, ${pV(color[3])})` +
|
||||||
|
` * vec4(1.0, 1.0, 1.0, ${pV(opacity)} * ${opacityFilter})`,
|
||||||
|
sizeExpression: `vec2(${pA(size[0])}, ${pA(size[1])})`,
|
||||||
|
offsetExpression: `vec2(${pA(offset[0])}, ${pA(offset[1])})`,
|
||||||
|
texCoordExpression: `vec4(${pA(texCoord[0])}, ${pA(texCoord[1])}, ${pA(texCoord[2])}, ${pA(texCoord[3])})`,
|
||||||
|
rotateWithView: !!style.rotateWithView
|
||||||
|
};
|
||||||
|
|
||||||
|
attributes = attributes.concat(varyings).filter(function(attrName, index, arr) {
|
||||||
|
return arr.indexOf(attrName) === index;
|
||||||
|
});
|
||||||
|
params.attributes = attributes.map(function(attributeName) {
|
||||||
|
return `float a_${attributeName}`;
|
||||||
|
});
|
||||||
|
params.varyings = varyings.map(function(attributeName) {
|
||||||
|
return {
|
||||||
|
name: `v_${attributeName}`,
|
||||||
|
type: 'float',
|
||||||
|
expression: `a_${attributeName}`
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
/** @type {Object.<string,import("../webgl/Helper").UniformValue>} */
|
||||||
|
const uniforms = {};
|
||||||
|
|
||||||
|
if (style.symbolType === 'image' && style.src) {
|
||||||
|
const texture = new Image();
|
||||||
|
texture.src = style.src;
|
||||||
|
params.uniforms.push('sampler2D u_texture');
|
||||||
|
params.colorExpression = params.colorExpression +
|
||||||
|
' * texture2D(u_texture, v_texCoord)';
|
||||||
|
uniforms['u_texture'] = texture;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
params: params,
|
||||||
|
attributes: attributes.map(function(attributeName) {
|
||||||
|
return {
|
||||||
|
name: attributeName,
|
||||||
|
callback: function(feature) {
|
||||||
|
return feature.get(attributeName) || 0;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
uniforms: uniforms
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ import {
|
|||||||
getSymbolVertexShader,
|
getSymbolVertexShader,
|
||||||
formatNumber,
|
formatNumber,
|
||||||
getSymbolFragmentShader,
|
getSymbolFragmentShader,
|
||||||
formatColor, formatArray
|
formatColor, formatArray, parse, parseSymbolStyle
|
||||||
} from '../../../../src/ol/webgl/ShaderBuilder.js';
|
} from '../../../../src/ol/webgl/ShaderBuilder.js';
|
||||||
|
|
||||||
describe('ol.webgl.ShaderBuilder', function() {
|
describe('ol.webgl.ShaderBuilder', function() {
|
||||||
@@ -57,6 +57,7 @@ attribute vec2 a_position;
|
|||||||
attribute float a_index;
|
attribute float a_index;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
varying float v_opacity;
|
varying float v_opacity;
|
||||||
varying vec3 v_test;
|
varying vec3 v_test;
|
||||||
void main(void) {
|
void main(void) {
|
||||||
@@ -71,6 +72,9 @@ void main(void) {
|
|||||||
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
v_texCoord = vec2(u, v);
|
v_texCoord = vec2(u, v);
|
||||||
|
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v_quadCoord = vec2(u, v);
|
||||||
v_opacity = 0.4;
|
v_opacity = 0.4;
|
||||||
v_test = vec3(1.0, 2.0, 3.0);
|
v_test = vec3(1.0, 2.0, 3.0);
|
||||||
}`);
|
}`);
|
||||||
@@ -94,6 +98,7 @@ attribute vec2 a_position;
|
|||||||
attribute float a_index;
|
attribute float a_index;
|
||||||
attribute vec2 a_myAttr;
|
attribute vec2 a_myAttr;
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
mat4 offsetMatrix = u_offsetScaleMatrix;
|
mat4 offsetMatrix = u_offsetScaleMatrix;
|
||||||
@@ -107,6 +112,9 @@ void main(void) {
|
|||||||
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
v_texCoord = vec2(u, v);
|
v_texCoord = vec2(u, v);
|
||||||
|
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v_quadCoord = vec2(u, v);
|
||||||
|
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
@@ -128,6 +136,7 @@ attribute vec2 a_position;
|
|||||||
attribute float a_index;
|
attribute float a_index;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
mat4 offsetMatrix = u_offsetScaleMatrix * u_offsetRotateMatrix;
|
||||||
@@ -141,6 +150,9 @@ void main(void) {
|
|||||||
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
float u = a_index == 0.0 || a_index == 3.0 ? texCoord.s : texCoord.q;
|
||||||
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
float v = a_index == 2.0 || a_index == 3.0 ? texCoord.t : texCoord.p;
|
||||||
v_texCoord = vec2(u, v);
|
v_texCoord = vec2(u, v);
|
||||||
|
u = a_index == 0.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v = a_index == 2.0 || a_index == 3.0 ? 0.0 : 1.0;
|
||||||
|
v_quadCoord = vec2(u, v);
|
||||||
|
|
||||||
}`);
|
}`);
|
||||||
});
|
});
|
||||||
@@ -168,6 +180,7 @@ void main(void) {
|
|||||||
expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float;
|
expect(getSymbolFragmentShader(parameters)).to.eql(`precision mediump float;
|
||||||
|
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
varying float v_opacity;
|
varying float v_opacity;
|
||||||
varying vec3 v_test;
|
varying vec3 v_test;
|
||||||
void main(void) {
|
void main(void) {
|
||||||
@@ -188,6 +201,7 @@ void main(void) {
|
|||||||
uniform float u_myUniform;
|
uniform float u_myUniform;
|
||||||
uniform vec2 u_myUniform2;
|
uniform vec2 u_myUniform2;
|
||||||
varying vec2 v_texCoord;
|
varying vec2 v_texCoord;
|
||||||
|
varying vec2 v_quadCoord;
|
||||||
|
|
||||||
void main(void) {
|
void main(void) {
|
||||||
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
gl_FragColor = vec4(1.0, 1.0, 1.0, 1.0);
|
||||||
@@ -196,4 +210,111 @@ void main(void) {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('parse', function() {
|
||||||
|
let attributes, prefix, parseFn;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
attributes = [];
|
||||||
|
prefix = 'a_';
|
||||||
|
parseFn = function(value) {
|
||||||
|
return parse(value, attributes, prefix);
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses expressions & literal values', function() {
|
||||||
|
expect(parseFn(1)).to.eql('1.0');
|
||||||
|
expect(parseFn(['get', 'myAttr'])).to.eql('a_myAttr');
|
||||||
|
expect(parseFn(['+', ['*', ['get', 'size'], 0.001], 12])).to.eql('((a_size * 0.001) + 12.0)');
|
||||||
|
expect(parseFn(['clamp', ['get', 'attr2'], ['get', 'attr3'], 20])).to.eql('clamp(a_attr2, a_attr3, 20.0)');
|
||||||
|
expect(parseFn(['stretch', ['get', 'size'], 10, 100, 4, 8])).to.eql('(clamp(a_size, 10.0, 100.0) * ((8.0 - 4.0) / (100.0 - 10.0)) + 4.0)');
|
||||||
|
expect(attributes).to.eql(['myAttr', 'size', 'attr2', 'attr3']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('does not register an attribute several times', function() {
|
||||||
|
parseFn(['get', 'myAttr']);
|
||||||
|
parseFn(['clamp', ['get', 'attr2'], ['get', 'attr2'], ['get', 'myAttr']]);
|
||||||
|
expect(attributes).to.eql(['myAttr', 'attr2']);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('parseSymbolStyle', function() {
|
||||||
|
it('parses a style without expressions', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'square',
|
||||||
|
size: [4, 8],
|
||||||
|
color: '#336699',
|
||||||
|
rotateWithView: true
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: [],
|
||||||
|
attributes: [],
|
||||||
|
varyings: [],
|
||||||
|
colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)',
|
||||||
|
sizeExpression: 'vec2(4.0, 8.0)',
|
||||||
|
offsetExpression: 'vec2(0.0, 0.0)',
|
||||||
|
texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)',
|
||||||
|
rotateWithView: true
|
||||||
|
});
|
||||||
|
expect(result.attributes).to.eql([]);
|
||||||
|
expect(result.uniforms).to.eql({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a style with expressions', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'square',
|
||||||
|
size: ['get', 'attr1'],
|
||||||
|
color: [
|
||||||
|
1.0, 0.0, 0.5, ['get', 'attr2']
|
||||||
|
],
|
||||||
|
textureCoord: [0.5, 0.5, 0.5, 1],
|
||||||
|
offset: [3, ['get', 'attr3']]
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: [],
|
||||||
|
attributes: ['float a_attr1', 'float a_attr3', 'float a_attr2'],
|
||||||
|
varyings: [{
|
||||||
|
name: 'v_attr1',
|
||||||
|
type: 'float',
|
||||||
|
expression: 'a_attr1'
|
||||||
|
}, {
|
||||||
|
name: 'v_attr2',
|
||||||
|
type: 'float',
|
||||||
|
expression: 'a_attr2'
|
||||||
|
}],
|
||||||
|
colorExpression: 'vec4(1.0, 0.0, 0.5, v_attr2) * vec4(1.0, 1.0, 1.0, 1.0 * 1.0)',
|
||||||
|
sizeExpression: 'vec2(a_attr1, a_attr1)',
|
||||||
|
offsetExpression: 'vec2(3.0, a_attr3)',
|
||||||
|
texCoordExpression: 'vec4(0.5, 0.5, 0.5, 1.0)',
|
||||||
|
rotateWithView: false
|
||||||
|
});
|
||||||
|
expect(result.attributes.length).to.eql(3);
|
||||||
|
expect(result.attributes[0].name).to.eql('attr1');
|
||||||
|
expect(result.attributes[1].name).to.eql('attr3');
|
||||||
|
expect(result.attributes[2].name).to.eql('attr2');
|
||||||
|
expect(result.uniforms).to.eql({});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('parses a style with a uniform (texture)', function() {
|
||||||
|
const result = parseSymbolStyle({
|
||||||
|
symbolType: 'image',
|
||||||
|
src: '../data/image.png',
|
||||||
|
size: 6,
|
||||||
|
color: '#336699',
|
||||||
|
opacity: 0.5
|
||||||
|
});
|
||||||
|
expect(result.params).to.eql({
|
||||||
|
uniforms: ['sampler2D u_texture'],
|
||||||
|
attributes: [],
|
||||||
|
varyings: [],
|
||||||
|
colorExpression: 'vec4(0.2, 0.4, 0.6, 1.0) * vec4(1.0, 1.0, 1.0, 0.5 * 1.0) * texture2D(u_texture, v_texCoord)',
|
||||||
|
sizeExpression: 'vec2(6.0, 6.0)',
|
||||||
|
offsetExpression: 'vec2(0.0, 0.0)',
|
||||||
|
texCoordExpression: 'vec4(0.0, 0.0, 1.0, 1.0)',
|
||||||
|
rotateWithView: false
|
||||||
|
});
|
||||||
|
expect(result.attributes).to.eql([]);
|
||||||
|
expect(result.uniforms).to.have.property('u_texture');
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user