diff --git a/src/ol/webgl/ShaderBuilder.js b/src/ol/webgl/ShaderBuilder.js index f4615fc593..05406d65fc 100644 --- a/src/ol/webgl/ShaderBuilder.js +++ b/src/ol/webgl/ShaderBuilder.js @@ -3,7 +3,7 @@ * @module ol/webgl/ShaderBuilder */ -import {asArray} from '../color.js'; +import {asArray, isStringColor} from '../color.js'; /** * Will return the number as a float with a dot separator, which is required by GLSL. @@ -37,6 +37,89 @@ export function formatColor(color) { }).map(formatNumber).join(', '); } +/** + * Possible inferred types from a given value or expression + * @enum {number} + */ +const ValueTypes = { + UNKNOWN: -1, + NUMBER: 0, + STRING: 1, + COLOR: 2, + COLOR_OR_STRING: 3 +}; + +function getValueType(value) { + if (typeof value === 'number') { + return ValueTypes.NUMBER; + } + if (typeof value === 'string') { + if (isStringColor(value)) { + return ValueTypes.COLOR_OR_STRING; + } + return ValueTypes.STRING; + } + if (!Array.isArray(value)) { + throw new Error(`Unrecognized value type: ${JSON.stringify(value)}`); + } + if (value.length === 3 || value.length === 4) { + const onlyNumbers = value.every(function(v) { + return typeof v === 'number'; + }); + if (onlyNumbers) { + return ValueTypes.COLOR; + } + } + if (typeof value[0] !== 'string') { + return ValueTypes.UNKNOWN; + } + switch (value[0]) { + case 'get': + case 'var': + case 'time': + case '*': + case '+': + case 'clamp': + case 'stretch': + case '>': + case '>=': + case '<': + case '<=': + case '==': + case '!': + case 'between': + return ValueTypes.NUMBER; + case 'interpolate': + return ValueTypes.COLOR; + default: + return ValueTypes.UNKNOWN; + } +} + +/** + * @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator. + * @returns {boolean} True if a numeric value, false otherwise + */ +export function isValueTypeNumber(value) { + return getValueType(value) === ValueTypes.NUMBER; +} + +/** + * @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator. + * @returns {boolean} True if a string value, false otherwise + */ +export function isValueTypeString(value) { + return getValueType(value) === ValueTypes.STRING || getValueType(value) === ValueTypes.COLOR_OR_STRING; +} + +/** + * @param {import("../style/LiteralStyle").ExpressionValue} value Either literal or an operator. + * @returns {boolean} True if a color value, false otherwise + */ +export function isValueTypeColor(value) { + return getValueType(value) === ValueTypes.COLOR || getValueType(value) === ValueTypes.COLOR_OR_STRING; +} + /** * 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)' diff --git a/test/spec/ol/webgl/shaderbuilder.test.js b/test/spec/ol/webgl/shaderbuilder.test.js index 0f985a1c77..944b72346a 100644 --- a/test/spec/ol/webgl/shaderbuilder.test.js +++ b/test/spec/ol/webgl/shaderbuilder.test.js @@ -2,6 +2,9 @@ import { formatArray, formatColor, formatNumber, + isValueTypeColor, + isValueTypeNumber, + isValueTypeString, parse, parseLiteralStyle, ShaderBuilder @@ -36,6 +39,46 @@ describe('ol.webgl.ShaderBuilder', function() { }); }); + describe('value type checking', function() { + it('correctly recognizes a number value', function() { + expect(isValueTypeNumber(1234)).to.eql(true); + expect(isValueTypeNumber(['time'])).to.eql(true); + expect(isValueTypeNumber(['clamp', ['get', 'attr'], -1, 1])).to.eql(true); + expect(isValueTypeNumber(['interpolate', ['get', 'attr'], 'red', 'green'])).to.eql(false); + expect(isValueTypeNumber('yellow')).to.eql(false); + expect(isValueTypeNumber('#113366')).to.eql(false); + expect(isValueTypeNumber('rgba(252,171,48,0.62)')).to.eql(false); + }); + it('correctly recognizes a color value', function() { + expect(isValueTypeColor(1234)).to.eql(false); + expect(isValueTypeColor(['time'])).to.eql(false); + expect(isValueTypeColor(['clamp', ['get', 'attr'], -1, 1])).to.eql(false); + expect(isValueTypeColor(['interpolate', ['get', 'attr'], 'red', 'green'])).to.eql(true); + expect(isValueTypeColor('yellow')).to.eql(true); + expect(isValueTypeColor('#113366')).to.eql(true); + expect(isValueTypeColor('rgba(252,171,48,0.62)')).to.eql(true); + expect(isValueTypeColor('abcd')).to.eql(false); + }); + it('correctly recognizes a string value', function() { + expect(isValueTypeString(1234)).to.eql(false); + expect(isValueTypeString(['time'])).to.eql(false); + expect(isValueTypeString(['clamp', ['get', 'attr'], -1, 1])).to.eql(false); + expect(isValueTypeString(['interpolate', ['get', 'attr'], 'red', 'green'])).to.eql(false); + expect(isValueTypeString('yellow')).to.eql(true); + expect(isValueTypeString('#113366')).to.eql(true); + expect(isValueTypeString('rgba(252,171,48,0.62)')).to.eql(true); + expect(isValueTypeString('abcd')).to.eql(true); + }); + it('throws on an unsupported type', function(done) { + try { + isValueTypeColor(true); + } catch (e) { + done(); + } + done(true); + }); + }); + describe('getSymbolVertexShader', function() { it('generates a symbol vertex shader (with varying)', function() { const builder = new ShaderBuilder();