From 501c90b0a26e20ae74f40b591223e7f5db0ea8e6 Mon Sep 17 00:00:00 2001 From: Olivier Guyot Date: Mon, 28 Oct 2019 15:52:29 +0100 Subject: [PATCH] Expressions / introduced the case operator This operator is used for if/else control flow --- src/ol/style/expressions.js | 39 +++++++++++- test/spec/ol/style/expressions.test.js | 88 ++++++++++++++++++++++++++ 2 files changed, 126 insertions(+), 1 deletion(-) diff --git a/src/ol/style/expressions.js b/src/ol/style/expressions.js index a44aae4501..db4cdfe087 100644 --- a/src/ol/style/expressions.js +++ b/src/ol/style/expressions.js @@ -27,6 +27,9 @@ import {asArray, isStringColor} from '../color.js'; * * `['^', value1, value1]` returns the value of `value1` raised to the `value2` power * * * Transform operators: + * * `['case', condition1, output1, ...conditionN, outputN, fallback]` selects the first output whose corresponding + * condition evaluates to `true`. If no match is found, returns the `fallback` value. + * All conditions should be `boolean`, output and fallback can be any kind. * * `['match', input, match1, output1, ...matchN, outputN, fallback]` compares the `input` value against all * provided `matchX` values, returning the output associated with the first valid match. If no match is found, * returns the `fallback` value. @@ -289,6 +292,11 @@ function assertArgsEven(args) { throw new Error(`An even amount of arguments was expected, got ${args} instead`); } } +function assertArgsOdd(args) { + if (args.length % 2 === 0) { + throw new Error(`An even amount of arguments was expected, got ${args} instead`); + } +} function assertUniqueInferredType(args, types) { if (!isTypeUnique(types)) { throw new Error(`Could not infer only one type from the following expression: ${JSON.stringify(args)}`); @@ -601,7 +609,6 @@ Operators['match'] = { assertArgsEven(args); assertArgsMinCount(args, 4); - // compute input/output types const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY; const outputType = Operators['match'].getReturnType(args) & typeHint; assertUniqueInferredType(args, outputType); @@ -617,3 +624,33 @@ Operators['match'] = { return result; } }; +Operators['case'] = { + getReturnType: function(args) { + let type = ValueTypes.ANY; + for (let i = 1; i < args.length; i += 2) { + type = type & getValueType(args[i]); + } + type = type & getValueType(args[args.length - 1]); + return type; + }, + toGlsl: function(context, args, opt_typeHint) { + assertArgsOdd(args); + assertArgsMinCount(args, 3); + + const typeHint = opt_typeHint !== undefined ? opt_typeHint : ValueTypes.ANY; + const outputType = Operators['case'].getReturnType(args) & typeHint; + assertUniqueInferredType(args, outputType); + for (let i = 0; i < args.length - 1; i += 2) { + assertBoolean(args[i]); + } + + const fallback = expressionToGlsl(context, args[args.length - 1], outputType); + let result = null; + for (let i = args.length - 3; i >= 0; i -= 2) { + const condition = expressionToGlsl(context, args[i]); + const output = expressionToGlsl(context, args[i + 1], outputType); + result = `(${condition} ? ${output} : ${result || fallback})`; + } + return result; + } +}; diff --git a/test/spec/ol/style/expressions.test.js b/test/spec/ol/style/expressions.test.js index 029dbe8ae8..bff0248ddd 100644 --- a/test/spec/ol/style/expressions.test.js +++ b/test/spec/ol/style/expressions.test.js @@ -294,6 +294,94 @@ describe('ol.style.expressions', function() { }); }); + describe('case operator', function() { + let context; + + beforeEach(function () { + context = { + variables: [], + attributes: [], + stringLiteralsMap: {} + }; + }); + + it('correctly guesses the output type', function() { + expect(getValueType(['case', true, 0, false, [3, 4, 5], 'green'])) + .to.eql(ValueTypes.NONE); + expect(getValueType(['case', true, 0, false, 1, 2])) + .to.eql(ValueTypes.NUMBER); + expect(getValueType(['case', true, [0, 0, 0], true, [1, 2, 3], ['get', 'attr'], [4, 5, 6, 7], [8, 9, 0]])) + .to.eql(ValueTypes.COLOR | ValueTypes.NUMBER_ARRAY); + expect(getValueType(['case', true, 'red', true, 'yellow', ['get', 'attr'], 'green', 'white'])) + .to.eql(ValueTypes.COLOR | ValueTypes.STRING); + expect(getValueType(['case', true, [0, 0], false, [1, 1], [2, 2]])) + .to.eql(ValueTypes.NUMBER_ARRAY); + }); + + it('throws if no single output type could be inferred', function() { + let thrown = false; + try { + expressionToGlsl(context, ['case', false, 'red', true, 'yellow', 'green'], ValueTypes.COLOR); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(false); + + try { + expressionToGlsl(context, ['case', true, 'red', true, 'yellow', 'green']); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + + thrown = false; + try { + expressionToGlsl(context, ['case', true, 'red', false, 'yellow', 'green'], ValueTypes.NUMBER); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + + thrown = false; + try { + expressionToGlsl(context, ['case', true, 'red', false, 'yellow', 'not_a_color'], ValueTypes.COLOR); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + }); + + it('throws if invalid argument count', function() { + let thrown = false; + try { + expressionToGlsl(context, ['case', true, 0, false, 1]); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + + thrown = false; + try { + expressionToGlsl(context, ['case', true, 0]); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + + try { + expressionToGlsl(context, ['case', false]); + } catch (e) { + thrown = true; + } + expect(thrown).to.be(true); + }); + + it('correctly parses the expression (colors)', function() { + expect(expressionToGlsl(context, ['case', ['>', ['get', 'attr'], 3], 'red', ['>', ['get', 'attr'], 1], 'yellow', 'white'], ValueTypes.COLOR)) + .to.eql('((a_attr > 3.0) ? vec4(1.0, 0.0, 0.0, 1.0) : ((a_attr > 1.0) ? vec4(1.0, 1.0, 0.0, 1.0) : vec4(1.0, 1.0, 1.0, 1.0)))'); + }); + }); + describe('match operator', function() { let context;