Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support case expressions in Canvas rendering #15376

Merged
merged 1 commit into from Nov 26, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
26 changes: 25 additions & 1 deletion src/ol/expr/cpu.js
Expand Up @@ -169,6 +169,9 @@ function compileExpression(expression, context) {
case Ops.Sqrt: {
return compileNumericExpression(expression, context);
}
case Ops.Case: {
return compileCaseExpression(expression, context);
}
case Ops.Match: {
return compileMatchExpression(expression, context);
}
Expand All @@ -183,7 +186,6 @@ function compileExpression(expression, context) {
// Ops.Zoom
// Ops.Time
// Ops.Between
// Ops.Case
// Ops.In
// Ops.Array
// Ops.Color
Expand Down Expand Up @@ -413,6 +415,28 @@ function compileNumericExpression(expression, context) {
}
}

/**
* @param {import('./expression.js').CallExpression} expression The call expression.
* @param {import('./expression.js').ParsingContext} context The parsing context.
* @return {ExpressionEvaluator} The evaluator function.
*/
function compileCaseExpression(expression, context) {
const length = expression.args.length;
const args = new Array(length);
for (let i = 0; i < length; ++i) {
args[i] = compileExpression(expression.args[i], context);
}
return (context) => {
for (let i = 0; i < length - 1; i += 2) {
const condition = args[i](context);
if (condition) {
return args[i + 1](context);
}
}
return args[length - 1](context);
};
}

/**
* @param {import('./expression.js').CallExpression} expression The call expression.
* @param {import('./expression.js').ParsingContext} context The parsing context.
Expand Down
18 changes: 9 additions & 9 deletions src/ol/expr/expression.js
Expand Up @@ -24,18 +24,18 @@ import {isStringColor} from '../color.js';
* green, blue and alpha. {@link import("../source/DataTile.js").default} sources can have any number
* of bands, depending on the underlying data source and
* {@link import("../source/GeoTIFF.js").Options configuration}. `xOffset` and `yOffset` are optional
* and allow specifying pixel offsets for x and y. This is used for sampling data from neighboring pixels.
* and allow specifying pixel offsets for x and y. This is used for sampling data from neighboring pixels (WebGL only).
* * `['get', 'attributeName', typeHint]` fetches a feature property value, similar to `feature.get('attributeName')`
* A type hint can optionally be specified, in case the resulting expression contains a type ambiguity which
* will make it invalid. Type hints can be one of: 'string', 'color', 'number', 'boolean', 'number[]'
* * `['geometry-type']` returns a feature's geometry type as string, either: 'LineString', 'Point' or 'Polygon'
* `Multi*` values are returned as their singular equivalent
* `Circle` geometries are returned as 'Polygon'
* `GeometryCollection` geometries are returned as the type of the first geometry found in the collection
* `GeometryCollection` geometries are returned as the type of the first geometry found in the collection (WebGL only).
* * `['resolution']` returns the current resolution
* * `['time']` returns the time in seconds since the creation of the layer
* * `['time']` The time in seconds since the creation of the layer (WebGL only).
* * `['var', 'varName']` fetches a value from the style variables; will throw an error if that variable is undefined
* * `['zoom']` returns the current zoom level
* * `['zoom']` The current zoom level (WebGL only).
*
* * Math operators:
* * `['*', value1, value2, ...]` multiplies the values (either numbers or colors)
Expand Down Expand Up @@ -82,9 +82,9 @@ import {isStringColor} from '../color.js';
* * `['all', value1, value2, ...]` returns `true` if all the inputs are `true`, `false` otherwise.
* * `['any', value1, value2, ...]` returns `true` if any of the inputs are `true`, `false` otherwise.
* * `['between', value1, value2, value3]` returns `true` if `value1` is contained between `value2` and `value3`
* (inclusively), or `false` otherwise.
* (inclusively), or `false` otherwise (WebGL only).
* * `['in', needle, haystack]` returns `true` if `needle` is found in `haystack`, and
* `false` otherwise.
* `false` otherwise (WebGL only).
* This operator has the following limitations:
* * `haystack` has to be an array of numbers or strings (searching for a substring in a string is not supported yet)
* * Only literal arrays are supported as `haystack` for now; this means that `haystack` cannot be the result of an
Expand All @@ -93,15 +93,15 @@ import {isStringColor} from '../color.js';
*
* * Conversion operators:
* * `['array', value1, ...valueN]` creates a numerical array from `number` values; please note that the amount of
* values can currently only be 2, 3 or 4.
* values can currently only be 2, 3 or 4 (WebGL only).
* * `['color', red, green, blue, alpha]` or `['color', shade, alpha]` creates a `color` value from `number` values;
* the `alpha` parameter is optional; if not specified, it will be set to 1.
* the `alpha` parameter is optional; if not specified, it will be set to 1 (WebGL only).
* Note: `red`, `green` and `blue` or `shade` components must be values between 0 and 255; `alpha` between 0 and 1.
* * `['palette', index, colors]` picks a `color` value from an array of colors using the given index; the `index`
* expression must evaluate to a number; the items in the `colors` array must be strings with hex colors
* (e.g. `'#86A136'`), colors using the rgba[a] functional notation (e.g. `'rgb(134, 161, 54)'` or `'rgba(134, 161, 54, 1)'`),
* named colors (e.g. `'red'`), or array literals with 3 ([r, g, b]) or 4 ([r, g, b, a]) values (with r, g, and b
* in the 0-255 range and a in the 0-1 range).
* in the 0-255 range and a in the 0-1 range) (WebGL only).
*
* Values can either be literals or another operator, as they will be evaluated recursively.
* Literal values can be of the following types:
Expand Down
48 changes: 48 additions & 0 deletions test/node/ol/expr/cpu.test.js
Expand Up @@ -432,6 +432,54 @@ describe('ol/expr/cpu.js', () => {
},
expected: Math.sqrt(42),
},
{
name: 'case (first condition)',
type: StringType,
expression: [
'case',
['<', ['get', 'value'], 42],
'small',
['<', ['get', 'value'], 100],
'big',
'bigger',
],
context: {
properties: {value: 40},
},
expected: 'small',
},
{
name: 'case (second condition)',
type: StringType,
expression: [
'case',
['<', ['get', 'value'], 42],
'small',
['<', ['get', 'value'], 100],
'big',
'bigger',
],
context: {
properties: {value: 50},
},
expected: 'big',
},
{
name: 'case (fallback)',
type: StringType,
expression: [
'case',
['<', ['get', 'value'], 42],
'small',
['<', ['get', 'value'], 100],
'big',
'biggest',
],
context: {
properties: {value: 200},
},
expected: 'biggest',
},
{
name: 'match (string match)',
type: StringType,
Expand Down