You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
581 lines
14 KiB
581 lines
14 KiB
// @flow
|
|
|
|
import {
|
|
type Type,
|
|
NumberType,
|
|
StringType,
|
|
BooleanType,
|
|
ColorType,
|
|
ObjectType,
|
|
ValueType,
|
|
ErrorType,
|
|
CollatorType,
|
|
array,
|
|
toString as typeToString
|
|
} from '../types.js';
|
|
|
|
import {typeOf, Color, validateRGBA, toString as valueToString} from '../values.js';
|
|
import CompoundExpression from '../compound_expression.js';
|
|
import RuntimeError from '../runtime_error.js';
|
|
import Let from './let.js';
|
|
import Var from './var.js';
|
|
import Literal from './literal.js';
|
|
import Assertion from './assertion.js';
|
|
import Coercion from './coercion.js';
|
|
import At from './at.js';
|
|
import In from './in.js';
|
|
import IndexOf from './index_of.js';
|
|
import Match from './match.js';
|
|
import Case from './case.js';
|
|
import Slice from './slice.js';
|
|
import Step from './step.js';
|
|
import Interpolate from './interpolate.js';
|
|
import Coalesce from './coalesce.js';
|
|
import {
|
|
Equals,
|
|
NotEquals,
|
|
LessThan,
|
|
GreaterThan,
|
|
LessThanOrEqual,
|
|
GreaterThanOrEqual
|
|
} from './comparison.js';
|
|
import CollatorExpression from './collator.js';
|
|
import NumberFormat from './number_format.js';
|
|
import FormatExpression from './format.js';
|
|
import ImageExpression from './image.js';
|
|
import Length from './length.js';
|
|
import Within from './within.js';
|
|
|
|
import type {Varargs} from '../compound_expression.js';
|
|
import type {ExpressionRegistry} from '../expression.js';
|
|
|
|
const expressions: ExpressionRegistry = {
|
|
// special forms
|
|
'==': Equals,
|
|
'!=': NotEquals,
|
|
'>': GreaterThan,
|
|
'<': LessThan,
|
|
'>=': GreaterThanOrEqual,
|
|
'<=': LessThanOrEqual,
|
|
'array': Assertion,
|
|
'at': At,
|
|
'boolean': Assertion,
|
|
'case': Case,
|
|
'coalesce': Coalesce,
|
|
'collator': CollatorExpression,
|
|
'format': FormatExpression,
|
|
'image': ImageExpression,
|
|
'in': In,
|
|
'index-of': IndexOf,
|
|
'interpolate': Interpolate,
|
|
'interpolate-hcl': Interpolate,
|
|
'interpolate-lab': Interpolate,
|
|
'length': Length,
|
|
'let': Let,
|
|
'literal': Literal,
|
|
'match': Match,
|
|
'number': Assertion,
|
|
'number-format': NumberFormat,
|
|
'object': Assertion,
|
|
'slice': Slice,
|
|
'step': Step,
|
|
'string': Assertion,
|
|
'to-boolean': Coercion,
|
|
'to-color': Coercion,
|
|
'to-number': Coercion,
|
|
'to-string': Coercion,
|
|
'var': Var,
|
|
'within': Within
|
|
};
|
|
|
|
function rgba(ctx, [r, g, b, a]) {
|
|
r = r.evaluate(ctx);
|
|
g = g.evaluate(ctx);
|
|
b = b.evaluate(ctx);
|
|
const alpha = a ? a.evaluate(ctx) : 1;
|
|
const error = validateRGBA(r, g, b, alpha);
|
|
if (error) throw new RuntimeError(error);
|
|
return new Color(r / 255 * alpha, g / 255 * alpha, b / 255 * alpha, alpha);
|
|
}
|
|
|
|
function has(key, obj) {
|
|
return key in obj;
|
|
}
|
|
|
|
function get(key, obj) {
|
|
const v = obj[key];
|
|
return typeof v === 'undefined' ? null : v;
|
|
}
|
|
|
|
function binarySearch(v, a, i, j) {
|
|
while (i <= j) {
|
|
const m = (i + j) >> 1;
|
|
if (a[m] === v)
|
|
return true;
|
|
if (a[m] > v)
|
|
j = m - 1;
|
|
else
|
|
i = m + 1;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function varargs(type: Type): Varargs {
|
|
return {type};
|
|
}
|
|
|
|
CompoundExpression.register(expressions, {
|
|
'error': [
|
|
ErrorType,
|
|
[StringType],
|
|
(ctx, [v]) => { throw new RuntimeError(v.evaluate(ctx)); }
|
|
],
|
|
'typeof': [
|
|
StringType,
|
|
[ValueType],
|
|
(ctx, [v]) => typeToString(typeOf(v.evaluate(ctx)))
|
|
],
|
|
'to-rgba': [
|
|
array(NumberType, 4),
|
|
[ColorType],
|
|
(ctx, [v]) => {
|
|
return v.evaluate(ctx).toArray();
|
|
}
|
|
],
|
|
'rgb': [
|
|
ColorType,
|
|
[NumberType, NumberType, NumberType],
|
|
rgba
|
|
],
|
|
'rgba': [
|
|
ColorType,
|
|
[NumberType, NumberType, NumberType, NumberType],
|
|
rgba
|
|
],
|
|
'has': {
|
|
type: BooleanType,
|
|
overloads: [
|
|
[
|
|
[StringType],
|
|
(ctx, [key]) => has(key.evaluate(ctx), ctx.properties())
|
|
], [
|
|
[StringType, ObjectType],
|
|
(ctx, [key, obj]) => has(key.evaluate(ctx), obj.evaluate(ctx))
|
|
]
|
|
]
|
|
},
|
|
'get': {
|
|
type: ValueType,
|
|
overloads: [
|
|
[
|
|
[StringType],
|
|
(ctx, [key]) => get(key.evaluate(ctx), ctx.properties())
|
|
], [
|
|
[StringType, ObjectType],
|
|
(ctx, [key, obj]) => get(key.evaluate(ctx), obj.evaluate(ctx))
|
|
]
|
|
]
|
|
},
|
|
'feature-state': [
|
|
ValueType,
|
|
[StringType],
|
|
(ctx, [key]) => get(key.evaluate(ctx), ctx.featureState || {})
|
|
],
|
|
'properties': [
|
|
ObjectType,
|
|
[],
|
|
(ctx) => ctx.properties()
|
|
],
|
|
'geometry-type': [
|
|
StringType,
|
|
[],
|
|
(ctx) => ctx.geometryType()
|
|
],
|
|
'id': [
|
|
ValueType,
|
|
[],
|
|
(ctx) => ctx.id()
|
|
],
|
|
'zoom': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.globals.zoom
|
|
],
|
|
'pitch': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.globals.pitch || 0
|
|
],
|
|
'distance-from-center': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.distanceFromCenter()
|
|
],
|
|
'heatmap-density': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.globals.heatmapDensity || 0
|
|
],
|
|
'line-progress': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.globals.lineProgress || 0
|
|
],
|
|
'sky-radial-progress': [
|
|
NumberType,
|
|
[],
|
|
(ctx) => ctx.globals.skyRadialProgress || 0
|
|
],
|
|
'accumulated': [
|
|
ValueType,
|
|
[],
|
|
(ctx) => ctx.globals.accumulated === undefined ? null : ctx.globals.accumulated
|
|
],
|
|
'+': [
|
|
NumberType,
|
|
varargs(NumberType),
|
|
(ctx, args) => {
|
|
let result = 0;
|
|
for (const arg of args) {
|
|
result += arg.evaluate(ctx);
|
|
}
|
|
return result;
|
|
}
|
|
],
|
|
'*': [
|
|
NumberType,
|
|
varargs(NumberType),
|
|
(ctx, args) => {
|
|
let result = 1;
|
|
for (const arg of args) {
|
|
result *= arg.evaluate(ctx);
|
|
}
|
|
return result;
|
|
}
|
|
],
|
|
'-': {
|
|
type: NumberType,
|
|
overloads: [
|
|
[
|
|
[NumberType, NumberType],
|
|
(ctx, [a, b]) => a.evaluate(ctx) - b.evaluate(ctx)
|
|
], [
|
|
[NumberType],
|
|
(ctx, [a]) => -a.evaluate(ctx)
|
|
]
|
|
]
|
|
},
|
|
'/': [
|
|
NumberType,
|
|
[NumberType, NumberType],
|
|
(ctx, [a, b]) => a.evaluate(ctx) / b.evaluate(ctx)
|
|
],
|
|
'%': [
|
|
NumberType,
|
|
[NumberType, NumberType],
|
|
(ctx, [a, b]) => a.evaluate(ctx) % b.evaluate(ctx)
|
|
],
|
|
'ln2': [
|
|
NumberType,
|
|
[],
|
|
() => Math.LN2
|
|
],
|
|
'pi': [
|
|
NumberType,
|
|
[],
|
|
() => Math.PI
|
|
],
|
|
'e': [
|
|
NumberType,
|
|
[],
|
|
() => Math.E
|
|
],
|
|
'^': [
|
|
NumberType,
|
|
[NumberType, NumberType],
|
|
(ctx, [b, e]) => Math.pow(b.evaluate(ctx), e.evaluate(ctx))
|
|
],
|
|
'sqrt': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [x]) => Math.sqrt(x.evaluate(ctx))
|
|
],
|
|
'log10': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN10
|
|
],
|
|
'ln': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.log(n.evaluate(ctx))
|
|
],
|
|
'log2': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.log(n.evaluate(ctx)) / Math.LN2
|
|
],
|
|
'sin': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.sin(n.evaluate(ctx))
|
|
],
|
|
'cos': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.cos(n.evaluate(ctx))
|
|
],
|
|
'tan': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.tan(n.evaluate(ctx))
|
|
],
|
|
'asin': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.asin(n.evaluate(ctx))
|
|
],
|
|
'acos': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.acos(n.evaluate(ctx))
|
|
],
|
|
'atan': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.atan(n.evaluate(ctx))
|
|
],
|
|
'min': [
|
|
NumberType,
|
|
varargs(NumberType),
|
|
(ctx, args) => Math.min(...args.map(arg => arg.evaluate(ctx)))
|
|
],
|
|
'max': [
|
|
NumberType,
|
|
varargs(NumberType),
|
|
(ctx, args) => Math.max(...args.map(arg => arg.evaluate(ctx)))
|
|
],
|
|
'abs': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.abs(n.evaluate(ctx))
|
|
],
|
|
'round': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => {
|
|
const v = n.evaluate(ctx);
|
|
// Javascript's Math.round() rounds towards +Infinity for halfway
|
|
// values, even when they're negative. It's more common to round
|
|
// away from 0 (e.g., this is what python and C++ do)
|
|
return v < 0 ? -Math.round(-v) : Math.round(v);
|
|
}
|
|
],
|
|
'floor': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.floor(n.evaluate(ctx))
|
|
],
|
|
'ceil': [
|
|
NumberType,
|
|
[NumberType],
|
|
(ctx, [n]) => Math.ceil(n.evaluate(ctx))
|
|
],
|
|
'filter-==': [
|
|
BooleanType,
|
|
[StringType, ValueType],
|
|
(ctx, [k, v]) => ctx.properties()[(k: any).value] === (v: any).value
|
|
],
|
|
'filter-id-==': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [v]) => ctx.id() === (v: any).value
|
|
],
|
|
'filter-type-==': [
|
|
BooleanType,
|
|
[StringType],
|
|
(ctx, [v]) => ctx.geometryType() === (v: any).value
|
|
],
|
|
'filter-<': [
|
|
BooleanType,
|
|
[StringType, ValueType],
|
|
(ctx, [k, v]) => {
|
|
const a = ctx.properties()[(k: any).value];
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a < b;
|
|
}
|
|
],
|
|
'filter-id-<': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [v]) => {
|
|
const a = ctx.id();
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a < b;
|
|
}
|
|
],
|
|
'filter->': [
|
|
BooleanType,
|
|
[StringType, ValueType],
|
|
(ctx, [k, v]) => {
|
|
const a = ctx.properties()[(k: any).value];
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a > b;
|
|
}
|
|
],
|
|
'filter-id->': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [v]) => {
|
|
const a = ctx.id();
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a > b;
|
|
}
|
|
],
|
|
'filter-<=': [
|
|
BooleanType,
|
|
[StringType, ValueType],
|
|
(ctx, [k, v]) => {
|
|
const a = ctx.properties()[(k: any).value];
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a <= b;
|
|
}
|
|
],
|
|
'filter-id-<=': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [v]) => {
|
|
const a = ctx.id();
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a <= b;
|
|
}
|
|
],
|
|
'filter->=': [
|
|
BooleanType,
|
|
[StringType, ValueType],
|
|
(ctx, [k, v]) => {
|
|
const a = ctx.properties()[(k: any).value];
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a >= b;
|
|
}
|
|
],
|
|
'filter-id->=': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [v]) => {
|
|
const a = ctx.id();
|
|
const b = (v: any).value;
|
|
return typeof a === typeof b && a >= b;
|
|
}
|
|
],
|
|
'filter-has': [
|
|
BooleanType,
|
|
[ValueType],
|
|
(ctx, [k]) => (k: any).value in ctx.properties()
|
|
],
|
|
'filter-has-id': [
|
|
BooleanType,
|
|
[],
|
|
(ctx) => (ctx.id() !== null && ctx.id() !== undefined)
|
|
],
|
|
'filter-type-in': [
|
|
BooleanType,
|
|
[array(StringType)],
|
|
(ctx, [v]) => (v: any).value.indexOf(ctx.geometryType()) >= 0
|
|
],
|
|
'filter-id-in': [
|
|
BooleanType,
|
|
[array(ValueType)],
|
|
(ctx, [v]) => (v: any).value.indexOf(ctx.id()) >= 0
|
|
],
|
|
'filter-in-small': [
|
|
BooleanType,
|
|
[StringType, array(ValueType)],
|
|
// assumes v is an array literal
|
|
(ctx, [k, v]) => (v: any).value.indexOf(ctx.properties()[(k: any).value]) >= 0
|
|
],
|
|
'filter-in-large': [
|
|
BooleanType,
|
|
[StringType, array(ValueType)],
|
|
// assumes v is a array literal with values sorted in ascending order and of a single type
|
|
(ctx, [k, v]) => binarySearch(ctx.properties()[(k: any).value], (v: any).value, 0, (v: any).value.length - 1)
|
|
],
|
|
'all': {
|
|
type: BooleanType,
|
|
overloads: [
|
|
[
|
|
[BooleanType, BooleanType],
|
|
(ctx, [a, b]) => a.evaluate(ctx) && b.evaluate(ctx)
|
|
],
|
|
[
|
|
varargs(BooleanType),
|
|
(ctx, args) => {
|
|
for (const arg of args) {
|
|
if (!arg.evaluate(ctx))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
]
|
|
]
|
|
},
|
|
'any': {
|
|
type: BooleanType,
|
|
overloads: [
|
|
[
|
|
[BooleanType, BooleanType],
|
|
(ctx, [a, b]) => a.evaluate(ctx) || b.evaluate(ctx)
|
|
],
|
|
[
|
|
varargs(BooleanType),
|
|
(ctx, args) => {
|
|
for (const arg of args) {
|
|
if (arg.evaluate(ctx))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
]
|
|
]
|
|
},
|
|
'!': [
|
|
BooleanType,
|
|
[BooleanType],
|
|
(ctx, [b]) => !b.evaluate(ctx)
|
|
],
|
|
'is-supported-script': [
|
|
BooleanType,
|
|
[StringType],
|
|
// At parse time this will always return true, so we need to exclude this expression with isGlobalPropertyConstant
|
|
(ctx, [s]) => {
|
|
const isSupportedScript = ctx.globals && ctx.globals.isSupportedScript;
|
|
if (isSupportedScript) {
|
|
return isSupportedScript(s.evaluate(ctx));
|
|
}
|
|
return true;
|
|
}
|
|
],
|
|
'upcase': [
|
|
StringType,
|
|
[StringType],
|
|
(ctx, [s]) => s.evaluate(ctx).toUpperCase()
|
|
],
|
|
'downcase': [
|
|
StringType,
|
|
[StringType],
|
|
(ctx, [s]) => s.evaluate(ctx).toLowerCase()
|
|
],
|
|
'concat': [
|
|
StringType,
|
|
varargs(ValueType),
|
|
(ctx, args) => args.map(arg => valueToString(arg.evaluate(ctx))).join('')
|
|
],
|
|
'resolved-locale': [
|
|
StringType,
|
|
[CollatorType],
|
|
(ctx, [collator]) => collator.evaluate(ctx).resolvedLocale()
|
|
]
|
|
});
|
|
|
|
export default expressions;
|