This commit is contained in:
130
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/assertion.js
generated
vendored
Normal file
130
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/assertion.js
generated
vendored
Normal file
@@ -0,0 +1,130 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {
|
||||
ObjectType,
|
||||
ValueType,
|
||||
StringType,
|
||||
NumberType,
|
||||
BooleanType,
|
||||
checkSubtype,
|
||||
toString,
|
||||
array
|
||||
} from '../types.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
import {typeOf} from '../values.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
const types = {
|
||||
string: StringType,
|
||||
number: NumberType,
|
||||
boolean: BooleanType,
|
||||
object: ObjectType
|
||||
};
|
||||
|
||||
class Assertion implements Expression {
|
||||
type: Type;
|
||||
args: Array<Expression>;
|
||||
|
||||
constructor(type: Type, args: Array<Expression>) {
|
||||
this.type = type;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length < 2)
|
||||
return context.error(`Expected at least one argument.`);
|
||||
|
||||
let i = 1;
|
||||
let type;
|
||||
|
||||
const name: string = (args[0]: any);
|
||||
if (name === 'array') {
|
||||
let itemType;
|
||||
if (args.length > 2) {
|
||||
const type = args[1];
|
||||
if (typeof type !== 'string' || !(type in types) || type === 'object')
|
||||
return context.error('The item type argument of "array" must be one of string, number, boolean', 1);
|
||||
itemType = types[type];
|
||||
i++;
|
||||
} else {
|
||||
itemType = ValueType;
|
||||
}
|
||||
|
||||
let N;
|
||||
if (args.length > 3) {
|
||||
if (args[2] !== null &&
|
||||
(typeof args[2] !== 'number' ||
|
||||
args[2] < 0 ||
|
||||
args[2] !== Math.floor(args[2]))
|
||||
) {
|
||||
return context.error('The length argument to "array" must be a positive integer literal', 2);
|
||||
}
|
||||
N = args[2];
|
||||
i++;
|
||||
}
|
||||
|
||||
type = array(itemType, N);
|
||||
} else {
|
||||
assert(types[name], name);
|
||||
type = types[name];
|
||||
}
|
||||
|
||||
const parsed = [];
|
||||
for (; i < args.length; i++) {
|
||||
const input = context.parse(args[i], i, ValueType);
|
||||
if (!input) return null;
|
||||
parsed.push(input);
|
||||
}
|
||||
|
||||
return new Assertion(type, parsed);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any | null {
|
||||
for (let i = 0; i < this.args.length; i++) {
|
||||
const value = this.args[i].evaluate(ctx);
|
||||
const error = checkSubtype(this.type, typeOf(value));
|
||||
if (!error) {
|
||||
return value;
|
||||
} else if (i === this.args.length - 1) {
|
||||
throw new RuntimeError(`Expected value to be of type ${toString(this.type)}, but found ${toString(typeOf(value))} instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
assert(false);
|
||||
return null;
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
this.args.forEach(fn);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.args.every(arg => arg.outputDefined());
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const type = this.type;
|
||||
const serialized = [type.kind];
|
||||
if (type.kind === 'array') {
|
||||
const itemType = type.itemType;
|
||||
if (itemType.kind === 'string' ||
|
||||
itemType.kind === 'number' ||
|
||||
itemType.kind === 'boolean') {
|
||||
serialized.push(itemType.kind);
|
||||
const N = type.N;
|
||||
if (typeof N === 'number' || this.args.length > 1) {
|
||||
serialized.push(N);
|
||||
}
|
||||
}
|
||||
}
|
||||
return serialized.concat(this.args.map(arg => arg.serialize()));
|
||||
}
|
||||
}
|
||||
|
||||
export default Assertion;
|
||||
70
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/at.js
generated
vendored
Normal file
70
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/at.js
generated
vendored
Normal file
@@ -0,0 +1,70 @@
|
||||
// @flow
|
||||
|
||||
import {array, ValueType, NumberType} from '../types.js';
|
||||
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type, ArrayType} from '../types.js';
|
||||
import type {Value} from '../values.js';
|
||||
|
||||
class At implements Expression {
|
||||
type: Type;
|
||||
index: Expression;
|
||||
input: Expression;
|
||||
|
||||
constructor(type: Type, index: Expression, input: Expression) {
|
||||
this.type = type;
|
||||
this.index = index;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?At {
|
||||
if (args.length !== 3)
|
||||
return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`);
|
||||
|
||||
const index = context.parse(args[1], 1, NumberType);
|
||||
const input = context.parse(args[2], 2, array(context.expectedType || ValueType));
|
||||
|
||||
if (!index || !input) return null;
|
||||
|
||||
const t: ArrayType = (input.type: any);
|
||||
return new At(t.itemType, index, input);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): Value {
|
||||
const index = ((this.index.evaluate(ctx): any): number);
|
||||
const array = ((this.input.evaluate(ctx): any): Array<Value>);
|
||||
|
||||
if (index < 0) {
|
||||
throw new RuntimeError(`Array index out of bounds: ${index} < 0.`);
|
||||
}
|
||||
|
||||
if (index >= array.length) {
|
||||
throw new RuntimeError(`Array index out of bounds: ${index} > ${array.length - 1}.`);
|
||||
}
|
||||
|
||||
if (index !== Math.floor(index)) {
|
||||
throw new RuntimeError(`Array index must be an integer, but found ${index} instead.`);
|
||||
}
|
||||
|
||||
return array[index];
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.index);
|
||||
fn(this.input);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
return ["at", this.index.serialize(), this.input.serialize()];
|
||||
}
|
||||
}
|
||||
|
||||
export default At;
|
||||
85
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/case.js
generated
vendored
Normal file
85
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/case.js
generated
vendored
Normal file
@@ -0,0 +1,85 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {BooleanType} from '../types.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
type Branches = Array<[Expression, Expression]>;
|
||||
|
||||
class Case implements Expression {
|
||||
type: Type;
|
||||
|
||||
branches: Branches;
|
||||
otherwise: Expression;
|
||||
|
||||
constructor(type: Type, branches: Branches, otherwise: Expression) {
|
||||
this.type = type;
|
||||
this.branches = branches;
|
||||
this.otherwise = otherwise;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Case {
|
||||
if (args.length < 4)
|
||||
return context.error(`Expected at least 3 arguments, but found only ${args.length - 1}.`);
|
||||
if (args.length % 2 !== 0)
|
||||
return context.error(`Expected an odd number of arguments.`);
|
||||
|
||||
let outputType: ?Type;
|
||||
if (context.expectedType && context.expectedType.kind !== 'value') {
|
||||
outputType = context.expectedType;
|
||||
}
|
||||
|
||||
const branches = [];
|
||||
for (let i = 1; i < args.length - 1; i += 2) {
|
||||
const test = context.parse(args[i], i, BooleanType);
|
||||
if (!test) return null;
|
||||
|
||||
const result = context.parse(args[i + 1], i + 1, outputType);
|
||||
if (!result) return null;
|
||||
|
||||
branches.push([test, result]);
|
||||
|
||||
outputType = outputType || result.type;
|
||||
}
|
||||
|
||||
const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType);
|
||||
if (!otherwise) return null;
|
||||
|
||||
assert(outputType);
|
||||
return new Case((outputType: any), branches, otherwise);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
for (const [test, expression] of this.branches) {
|
||||
if (test.evaluate(ctx)) {
|
||||
return expression.evaluate(ctx);
|
||||
}
|
||||
}
|
||||
return this.otherwise.evaluate(ctx);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
for (const [test, expression] of this.branches) {
|
||||
fn(test);
|
||||
fn(expression);
|
||||
}
|
||||
fn(this.otherwise);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined();
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["case"];
|
||||
this.eachChild(child => { serialized.push(child.serialize()); });
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Case;
|
||||
95
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/coalesce.js
generated
vendored
Normal file
95
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/coalesce.js
generated
vendored
Normal file
@@ -0,0 +1,95 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {checkSubtype, ValueType} from '../types.js';
|
||||
import ResolvedImage from '../types/resolved_image.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class Coalesce implements Expression {
|
||||
type: Type;
|
||||
args: Array<Expression>;
|
||||
|
||||
constructor(type: Type, args: Array<Expression>) {
|
||||
this.type = type;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Coalesce {
|
||||
if (args.length < 2) {
|
||||
return context.error("Expectected at least one argument.");
|
||||
}
|
||||
let outputType: Type = (null: any);
|
||||
const expectedType = context.expectedType;
|
||||
if (expectedType && expectedType.kind !== 'value') {
|
||||
outputType = expectedType;
|
||||
}
|
||||
const parsedArgs = [];
|
||||
|
||||
for (const arg of args.slice(1)) {
|
||||
const parsed = context.parse(arg, 1 + parsedArgs.length, outputType, undefined, {typeAnnotation: 'omit'});
|
||||
if (!parsed) return null;
|
||||
outputType = outputType || parsed.type;
|
||||
parsedArgs.push(parsed);
|
||||
}
|
||||
assert(outputType);
|
||||
|
||||
// Above, we parse arguments without inferred type annotation so that
|
||||
// they don't produce a runtime error for `null` input, which would
|
||||
// preempt the desired null-coalescing behavior.
|
||||
// Thus, if any of our arguments would have needed an annotation, we
|
||||
// need to wrap the enclosing coalesce expression with it instead.
|
||||
const needsAnnotation = expectedType &&
|
||||
parsedArgs.some(arg => checkSubtype(expectedType, arg.type));
|
||||
|
||||
return needsAnnotation ?
|
||||
new Coalesce(ValueType, parsedArgs) :
|
||||
new Coalesce((outputType: any), parsedArgs);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any | null {
|
||||
let result = null;
|
||||
let argCount = 0;
|
||||
let firstImage;
|
||||
for (const arg of this.args) {
|
||||
argCount++;
|
||||
result = arg.evaluate(ctx);
|
||||
// we need to keep track of the first requested image in a coalesce statement
|
||||
// if coalesce can't find a valid image, we return the first image so styleimagemissing can fire
|
||||
if (result && result instanceof ResolvedImage && !result.available) {
|
||||
// set to first image
|
||||
if (!firstImage) {
|
||||
firstImage = result;
|
||||
}
|
||||
result = null;
|
||||
// if we reach the end, return the first image
|
||||
if (argCount === this.args.length) {
|
||||
return firstImage;
|
||||
}
|
||||
}
|
||||
|
||||
if (result !== null) break;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
this.args.forEach(fn);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.args.every(arg => arg.outputDefined());
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["coalesce"];
|
||||
this.eachChild(child => { serialized.push(child.serialize()); });
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Coalesce;
|
||||
133
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/coercion.js
generated
vendored
Normal file
133
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/coercion.js
generated
vendored
Normal file
@@ -0,0 +1,133 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {BooleanType, ColorType, NumberType, StringType, ValueType} from '../types.js';
|
||||
import {Color, toString as valueToString, validateRGBA} from '../values.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
import Formatted from '../types/formatted.js';
|
||||
import FormatExpression from '../definitions/format.js';
|
||||
import ImageExpression from '../definitions/image.js';
|
||||
import ResolvedImage from '../types/resolved_image.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
const types = {
|
||||
'to-boolean': BooleanType,
|
||||
'to-color': ColorType,
|
||||
'to-number': NumberType,
|
||||
'to-string': StringType
|
||||
};
|
||||
|
||||
/**
|
||||
* Special form for error-coalescing coercion expressions "to-number",
|
||||
* "to-color". Since these coercions can fail at runtime, they accept multiple
|
||||
* arguments, only evaluating one at a time until one succeeds.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
class Coercion implements Expression {
|
||||
type: Type;
|
||||
args: Array<Expression>;
|
||||
|
||||
constructor(type: Type, args: Array<Expression>) {
|
||||
this.type = type;
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length < 2)
|
||||
return context.error(`Expected at least one argument.`);
|
||||
|
||||
const name: string = (args[0]: any);
|
||||
assert(types[name], name);
|
||||
|
||||
if ((name === 'to-boolean' || name === 'to-string') && args.length !== 2)
|
||||
return context.error(`Expected one argument.`);
|
||||
|
||||
const type = types[name];
|
||||
|
||||
const parsed = [];
|
||||
for (let i = 1; i < args.length; i++) {
|
||||
const input = context.parse(args[i], i, ValueType);
|
||||
if (!input) return null;
|
||||
parsed.push(input);
|
||||
}
|
||||
|
||||
return new Coercion(type, parsed);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): null | boolean | number | string | Color | Formatted | ResolvedImage {
|
||||
if (this.type.kind === 'boolean') {
|
||||
return Boolean(this.args[0].evaluate(ctx));
|
||||
} else if (this.type.kind === 'color') {
|
||||
let input;
|
||||
let error;
|
||||
for (const arg of this.args) {
|
||||
input = arg.evaluate(ctx);
|
||||
error = null;
|
||||
if (input instanceof Color) {
|
||||
return input;
|
||||
} else if (typeof input === 'string') {
|
||||
const c = ctx.parseColor(input);
|
||||
if (c) return c;
|
||||
} else if (Array.isArray(input)) {
|
||||
if (input.length < 3 || input.length > 4) {
|
||||
error = `Invalid rbga value ${JSON.stringify(input)}: expected an array containing either three or four numeric values.`;
|
||||
} else {
|
||||
error = validateRGBA(input[0], input[1], input[2], input[3]);
|
||||
}
|
||||
if (!error) {
|
||||
return new Color((input[0]: any) / 255, (input[1]: any) / 255, (input[2]: any) / 255, (input[3]: any));
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new RuntimeError(error || `Could not parse color from value '${typeof input === 'string' ? input : String(JSON.stringify(input))}'`);
|
||||
} else if (this.type.kind === 'number') {
|
||||
let value = null;
|
||||
for (const arg of this.args) {
|
||||
value = arg.evaluate(ctx);
|
||||
if (value === null) return 0;
|
||||
const num = Number(value);
|
||||
if (isNaN(num)) continue;
|
||||
return num;
|
||||
}
|
||||
throw new RuntimeError(`Could not convert ${JSON.stringify(value)} to number.`);
|
||||
} else if (this.type.kind === 'formatted') {
|
||||
// There is no explicit 'to-formatted' but this coercion can be implicitly
|
||||
// created by properties that expect the 'formatted' type.
|
||||
return Formatted.fromString(valueToString(this.args[0].evaluate(ctx)));
|
||||
} else if (this.type.kind === 'resolvedImage') {
|
||||
return ResolvedImage.fromString(valueToString(this.args[0].evaluate(ctx)));
|
||||
} else {
|
||||
return valueToString(this.args[0].evaluate(ctx));
|
||||
}
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
this.args.forEach(fn);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.args.every(arg => arg.outputDefined());
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
if (this.type.kind === 'formatted') {
|
||||
return new FormatExpression([{content: this.args[0], scale: null, font: null, textColor: null}]).serialize();
|
||||
}
|
||||
|
||||
if (this.type.kind === 'resolvedImage') {
|
||||
return new ImageExpression(this.args[0]).serialize();
|
||||
}
|
||||
|
||||
const serialized = [`to-${this.type.kind}`];
|
||||
this.eachChild(child => { serialized.push(child.serialize()); });
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Coercion;
|
||||
78
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/collator.js
generated
vendored
Normal file
78
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/collator.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
// @flow
|
||||
|
||||
import {StringType, BooleanType, CollatorType} from '../types.js';
|
||||
import Collator from '../types/collator.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
export default class CollatorExpression implements Expression {
|
||||
type: Type;
|
||||
caseSensitive: Expression;
|
||||
diacriticSensitive: Expression;
|
||||
locale: Expression | null;
|
||||
|
||||
constructor(caseSensitive: Expression, diacriticSensitive: Expression, locale: Expression | null) {
|
||||
this.type = CollatorType;
|
||||
this.locale = locale;
|
||||
this.caseSensitive = caseSensitive;
|
||||
this.diacriticSensitive = diacriticSensitive;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length !== 2)
|
||||
return context.error(`Expected one argument.`);
|
||||
|
||||
const options = (args[1]: any);
|
||||
if (typeof options !== "object" || Array.isArray(options))
|
||||
return context.error(`Collator options argument must be an object.`);
|
||||
|
||||
const caseSensitive = context.parse(
|
||||
options['case-sensitive'] === undefined ? false : options['case-sensitive'], 1, BooleanType);
|
||||
if (!caseSensitive) return null;
|
||||
|
||||
const diacriticSensitive = context.parse(
|
||||
options['diacritic-sensitive'] === undefined ? false : options['diacritic-sensitive'], 1, BooleanType);
|
||||
if (!diacriticSensitive) return null;
|
||||
|
||||
let locale = null;
|
||||
if (options['locale']) {
|
||||
locale = context.parse(options['locale'], 1, StringType);
|
||||
if (!locale) return null;
|
||||
}
|
||||
|
||||
return new CollatorExpression(caseSensitive, diacriticSensitive, locale);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): Collator {
|
||||
return new Collator(this.caseSensitive.evaluate(ctx), this.diacriticSensitive.evaluate(ctx), this.locale ? this.locale.evaluate(ctx) : null);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.caseSensitive);
|
||||
fn(this.diacriticSensitive);
|
||||
if (this.locale) {
|
||||
fn(this.locale);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
// Technically the set of possible outputs is the combinatoric set of Collators produced
|
||||
// by all possible outputs of locale/caseSensitive/diacriticSensitive
|
||||
// But for the primary use of Collators in comparison operators, we ignore the Collator's
|
||||
// possible outputs anyway, so we can get away with leaving this false for now.
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const options = {};
|
||||
options['case-sensitive'] = this.caseSensitive.serialize();
|
||||
options['diacritic-sensitive'] = this.diacriticSensitive.serialize();
|
||||
if (this.locale) {
|
||||
options['locale'] = this.locale.serialize();
|
||||
}
|
||||
return ["collator", options];
|
||||
}
|
||||
}
|
||||
184
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/comparison.js
generated
vendored
Normal file
184
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/comparison.js
generated
vendored
Normal file
@@ -0,0 +1,184 @@
|
||||
// @flow
|
||||
|
||||
import {toString, ValueType, BooleanType, CollatorType} from '../types.js';
|
||||
import Assertion from './assertion.js';
|
||||
import {typeOf} from '../values.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
|
||||
import type {Expression, SerializedExpression, ExpressionRegistration} from '../expression.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
type ComparisonOperator = '==' | '!=' | '<' | '>' | '<=' | '>=' ;
|
||||
|
||||
function isComparableType(op: ComparisonOperator, type: Type) {
|
||||
if (op === '==' || op === '!=') {
|
||||
// equality operator
|
||||
return type.kind === 'boolean' ||
|
||||
type.kind === 'string' ||
|
||||
type.kind === 'number' ||
|
||||
type.kind === 'null' ||
|
||||
type.kind === 'value';
|
||||
} else {
|
||||
// ordering operator
|
||||
return type.kind === 'string' ||
|
||||
type.kind === 'number' ||
|
||||
type.kind === 'value';
|
||||
}
|
||||
}
|
||||
|
||||
function eq(ctx: EvaluationContext, a: any, b: any): boolean { return a === b; }
|
||||
function neq(ctx: EvaluationContext, a: any, b: any): boolean { return a !== b; }
|
||||
function lt(ctx: EvaluationContext, a: any, b: any): boolean { return a < b; }
|
||||
function gt(ctx: EvaluationContext, a: any, b: any): boolean { return a > b; }
|
||||
function lteq(ctx: EvaluationContext, a: any, b: any): boolean { return a <= b; }
|
||||
function gteq(ctx: EvaluationContext, a: any, b: any): boolean { return a >= b; }
|
||||
|
||||
function eqCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return c.compare(a, b) === 0; }
|
||||
function neqCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return !eqCollate(ctx, a, b, c); }
|
||||
function ltCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return c.compare(a, b) < 0; }
|
||||
function gtCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return c.compare(a, b) > 0; }
|
||||
function lteqCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return c.compare(a, b) <= 0; }
|
||||
function gteqCollate(ctx: EvaluationContext, a: any, b: any, c: any): boolean { return c.compare(a, b) >= 0; }
|
||||
|
||||
/**
|
||||
* Special form for comparison operators, implementing the signatures:
|
||||
* - (T, T, ?Collator) => boolean
|
||||
* - (T, value, ?Collator) => boolean
|
||||
* - (value, T, ?Collator) => boolean
|
||||
*
|
||||
* For inequalities, T must be either value, string, or number. For ==/!=, it
|
||||
* can also be boolean or null.
|
||||
*
|
||||
* Equality semantics are equivalent to Javascript's strict equality (===/!==)
|
||||
* -- i.e., when the arguments' types don't match, == evaluates to false, != to
|
||||
* true.
|
||||
*
|
||||
* When types don't match in an ordering comparison, a runtime error is thrown.
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function makeComparison(op: ComparisonOperator, compareBasic: (EvaluationContext, any, any) => boolean, compareWithCollator: (EvaluationContext, any, any, any) => boolean): ExpressionRegistration {
|
||||
const isOrderComparison = op !== '==' && op !== '!=';
|
||||
|
||||
return class Comparison implements Expression {
|
||||
type: Type;
|
||||
lhs: Expression;
|
||||
rhs: Expression;
|
||||
collator: ?Expression;
|
||||
hasUntypedArgument: boolean;
|
||||
|
||||
constructor(lhs: Expression, rhs: Expression, collator: ?Expression) {
|
||||
this.type = BooleanType;
|
||||
this.lhs = lhs;
|
||||
this.rhs = rhs;
|
||||
this.collator = collator;
|
||||
this.hasUntypedArgument = lhs.type.kind === 'value' || rhs.type.kind === 'value';
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length !== 3 && args.length !== 4)
|
||||
return context.error(`Expected two or three arguments.`);
|
||||
|
||||
const op: ComparisonOperator = (args[0]: any);
|
||||
|
||||
let lhs = context.parse(args[1], 1, ValueType);
|
||||
if (!lhs) return null;
|
||||
if (!isComparableType(op, lhs.type)) {
|
||||
return context.concat(1).error(`"${op}" comparisons are not supported for type '${toString(lhs.type)}'.`);
|
||||
}
|
||||
let rhs = context.parse(args[2], 2, ValueType);
|
||||
if (!rhs) return null;
|
||||
if (!isComparableType(op, rhs.type)) {
|
||||
return context.concat(2).error(`"${op}" comparisons are not supported for type '${toString(rhs.type)}'.`);
|
||||
}
|
||||
|
||||
if (
|
||||
lhs.type.kind !== rhs.type.kind &&
|
||||
lhs.type.kind !== 'value' &&
|
||||
rhs.type.kind !== 'value'
|
||||
) {
|
||||
return context.error(`Cannot compare types '${toString(lhs.type)}' and '${toString(rhs.type)}'.`);
|
||||
}
|
||||
|
||||
if (isOrderComparison) {
|
||||
// typing rules specific to less/greater than operators
|
||||
if (lhs.type.kind === 'value' && rhs.type.kind !== 'value') {
|
||||
// (value, T)
|
||||
lhs = new Assertion(rhs.type, [lhs]);
|
||||
} else if (lhs.type.kind !== 'value' && rhs.type.kind === 'value') {
|
||||
// (T, value)
|
||||
rhs = new Assertion(lhs.type, [rhs]);
|
||||
}
|
||||
}
|
||||
|
||||
let collator = null;
|
||||
if (args.length === 4) {
|
||||
if (
|
||||
lhs.type.kind !== 'string' &&
|
||||
rhs.type.kind !== 'string' &&
|
||||
lhs.type.kind !== 'value' &&
|
||||
rhs.type.kind !== 'value'
|
||||
) {
|
||||
return context.error(`Cannot use collator to compare non-string types.`);
|
||||
}
|
||||
collator = context.parse(args[3], 3, CollatorType);
|
||||
if (!collator) return null;
|
||||
}
|
||||
|
||||
return new Comparison(lhs, rhs, collator);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): boolean {
|
||||
const lhs = this.lhs.evaluate(ctx);
|
||||
const rhs = this.rhs.evaluate(ctx);
|
||||
|
||||
if (isOrderComparison && this.hasUntypedArgument) {
|
||||
const lt = typeOf(lhs);
|
||||
const rt = typeOf(rhs);
|
||||
// check that type is string or number, and equal
|
||||
if (lt.kind !== rt.kind || !(lt.kind === 'string' || lt.kind === 'number')) {
|
||||
throw new RuntimeError(`Expected arguments for "${op}" to be (string, string) or (number, number), but found (${lt.kind}, ${rt.kind}) instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.collator && !isOrderComparison && this.hasUntypedArgument) {
|
||||
const lt = typeOf(lhs);
|
||||
const rt = typeOf(rhs);
|
||||
if (lt.kind !== 'string' || rt.kind !== 'string') {
|
||||
return compareBasic(ctx, lhs, rhs);
|
||||
}
|
||||
}
|
||||
|
||||
return this.collator ?
|
||||
compareWithCollator(ctx, lhs, rhs, this.collator.evaluate(ctx)) :
|
||||
compareBasic(ctx, lhs, rhs);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.lhs);
|
||||
fn(this.rhs);
|
||||
if (this.collator) {
|
||||
fn(this.collator);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = [op];
|
||||
this.eachChild(child => { serialized.push(child.serialize()); });
|
||||
return serialized;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
export const Equals: $Call<typeof makeComparison, '==', typeof eq, typeof eqCollate> = makeComparison('==', eq, eqCollate);
|
||||
export const NotEquals: $Call<typeof makeComparison, '!=', typeof neq, typeof neqCollate> = makeComparison('!=', neq, neqCollate);
|
||||
export const LessThan: $Call<typeof makeComparison, '<', typeof lt, typeof ltCollate> = makeComparison('<', lt, ltCollate);
|
||||
export const GreaterThan: $Call<typeof makeComparison, '>', typeof gt, typeof gtCollate> = makeComparison('>', gt, gtCollate);
|
||||
export const LessThanOrEqual: $Call<typeof makeComparison, '<=', typeof lteq, typeof lteqCollate> = makeComparison('<=', lteq, lteqCollate);
|
||||
export const GreaterThanOrEqual: $Call<typeof makeComparison, '>=', typeof gteq, typeof gteqCollate> = makeComparison('>=', gteq, gteqCollate);
|
||||
144
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/format.js
generated
vendored
Normal file
144
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/format.js
generated
vendored
Normal file
@@ -0,0 +1,144 @@
|
||||
// @flow
|
||||
|
||||
import {NumberType, ValueType, FormattedType, array, StringType, ColorType, ResolvedImageType} from '../types.js';
|
||||
import Formatted, {FormattedSection} from '../types/formatted.js';
|
||||
import {toString, typeOf} from '../values.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
type FormattedSectionExpression = {
|
||||
// Content of a section may be Image expression or other
|
||||
// type of expression that is coercable to 'string'.
|
||||
content: Expression,
|
||||
scale: Expression | null;
|
||||
font: Expression | null;
|
||||
textColor: Expression | null;
|
||||
}
|
||||
|
||||
export default class FormatExpression implements Expression {
|
||||
type: Type;
|
||||
sections: Array<FormattedSectionExpression>;
|
||||
|
||||
constructor(sections: Array<FormattedSectionExpression>) {
|
||||
this.type = FormattedType;
|
||||
this.sections = sections;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length < 2) {
|
||||
return context.error(`Expected at least one argument.`);
|
||||
}
|
||||
|
||||
const firstArg = args[1];
|
||||
if (!Array.isArray(firstArg) && typeof firstArg === 'object') {
|
||||
return context.error(`First argument must be an image or text section.`);
|
||||
}
|
||||
|
||||
const sections: Array<FormattedSectionExpression> = [];
|
||||
let nextTokenMayBeObject = false;
|
||||
for (let i = 1; i <= args.length - 1; ++i) {
|
||||
const arg = (args[i]: any);
|
||||
|
||||
if (nextTokenMayBeObject && typeof arg === "object" && !Array.isArray(arg)) {
|
||||
nextTokenMayBeObject = false;
|
||||
|
||||
let scale = null;
|
||||
if (arg['font-scale']) {
|
||||
scale = context.parse(arg['font-scale'], 1, NumberType);
|
||||
if (!scale) return null;
|
||||
}
|
||||
|
||||
let font = null;
|
||||
if (arg['text-font']) {
|
||||
font = context.parse(arg['text-font'], 1, array(StringType));
|
||||
if (!font) return null;
|
||||
}
|
||||
|
||||
let textColor = null;
|
||||
if (arg['text-color']) {
|
||||
textColor = context.parse(arg['text-color'], 1, ColorType);
|
||||
if (!textColor) return null;
|
||||
}
|
||||
|
||||
const lastExpression = sections[sections.length - 1];
|
||||
lastExpression.scale = scale;
|
||||
lastExpression.font = font;
|
||||
lastExpression.textColor = textColor;
|
||||
} else {
|
||||
const content = context.parse(args[i], 1, ValueType);
|
||||
if (!content) return null;
|
||||
|
||||
const kind = content.type.kind;
|
||||
if (kind !== 'string' && kind !== 'value' && kind !== 'null' && kind !== 'resolvedImage')
|
||||
return context.error(`Formatted text type must be 'string', 'value', 'image' or 'null'.`);
|
||||
|
||||
nextTokenMayBeObject = true;
|
||||
sections.push({content, scale: null, font: null, textColor: null});
|
||||
}
|
||||
}
|
||||
|
||||
return new FormatExpression(sections);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): Formatted {
|
||||
const evaluateSection = section => {
|
||||
const evaluatedContent = section.content.evaluate(ctx);
|
||||
if (typeOf(evaluatedContent) === ResolvedImageType) {
|
||||
return new FormattedSection('', evaluatedContent, null, null, null);
|
||||
}
|
||||
|
||||
return new FormattedSection(
|
||||
toString(evaluatedContent),
|
||||
null,
|
||||
section.scale ? section.scale.evaluate(ctx) : null,
|
||||
section.font ? section.font.evaluate(ctx).join(',') : null,
|
||||
section.textColor ? section.textColor.evaluate(ctx) : null
|
||||
);
|
||||
};
|
||||
|
||||
return new Formatted(this.sections.map(evaluateSection));
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
for (const section of this.sections) {
|
||||
fn(section.content);
|
||||
if (section.scale) {
|
||||
fn(section.scale);
|
||||
}
|
||||
if (section.font) {
|
||||
fn(section.font);
|
||||
}
|
||||
if (section.textColor) {
|
||||
fn(section.textColor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
// Technically the combinatoric set of all children
|
||||
// Usually, this.text will be undefined anyway
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["format"];
|
||||
for (const section of this.sections) {
|
||||
serialized.push(section.content.serialize());
|
||||
const options = {};
|
||||
if (section.scale) {
|
||||
options['font-scale'] = section.scale.serialize();
|
||||
}
|
||||
if (section.font) {
|
||||
options['text-font'] = section.font.serialize();
|
||||
}
|
||||
if (section.textColor) {
|
||||
options['text-color'] = section.textColor.serialize();
|
||||
}
|
||||
serialized.push(options);
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
52
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/image.js
generated
vendored
Normal file
52
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/image.js
generated
vendored
Normal file
@@ -0,0 +1,52 @@
|
||||
// @flow
|
||||
|
||||
import {ResolvedImageType, StringType} from '../types.js';
|
||||
import ResolvedImage from '../types/resolved_image.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
export default class ImageExpression implements Expression {
|
||||
type: Type;
|
||||
input: Expression;
|
||||
|
||||
constructor(input: Expression) {
|
||||
this.type = ResolvedImageType;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length !== 2) {
|
||||
return context.error(`Expected two arguments.`);
|
||||
}
|
||||
|
||||
const name = context.parse(args[1], 1, StringType);
|
||||
if (!name) return context.error(`No image name provided.`);
|
||||
|
||||
return new ImageExpression(name);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): null | ResolvedImage {
|
||||
const evaluatedImageName = this.input.evaluate(ctx);
|
||||
|
||||
const value = ResolvedImage.fromString(evaluatedImageName);
|
||||
if (value && ctx.availableImages) value.available = ctx.availableImages.indexOf(evaluatedImageName) > -1;
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
// The output of image is determined by the list of available images in the evaluation context
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
return ["image", this.input.serialize()];
|
||||
}
|
||||
}
|
||||
72
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/in.js
generated
vendored
Normal file
72
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/in.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// @flow
|
||||
|
||||
import {BooleanType, StringType, ValueType, NullType, toString, NumberType, isValidType, isValidNativeType} from '../types.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
import {typeOf} from '../values.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class In implements Expression {
|
||||
type: Type;
|
||||
needle: Expression;
|
||||
haystack: Expression;
|
||||
|
||||
constructor(needle: Expression, haystack: Expression) {
|
||||
this.type = BooleanType;
|
||||
this.needle = needle;
|
||||
this.haystack = haystack;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?In {
|
||||
if (args.length !== 3) {
|
||||
return context.error(`Expected 2 arguments, but found ${args.length - 1} instead.`);
|
||||
}
|
||||
|
||||
const needle = context.parse(args[1], 1, ValueType);
|
||||
|
||||
const haystack = context.parse(args[2], 2, ValueType);
|
||||
|
||||
if (!needle || !haystack) return null;
|
||||
|
||||
if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) {
|
||||
return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${toString(needle.type)} instead`);
|
||||
}
|
||||
|
||||
return new In(needle, haystack);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): boolean {
|
||||
const needle = (this.needle.evaluate(ctx): any);
|
||||
const haystack = (this.haystack.evaluate(ctx): any);
|
||||
|
||||
if (haystack == null) return false;
|
||||
|
||||
if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) {
|
||||
throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${toString(typeOf(needle))} instead.`);
|
||||
}
|
||||
|
||||
if (!isValidNativeType(haystack, ['string', 'array'])) {
|
||||
throw new RuntimeError(`Expected second argument to be of type array or string, but found ${toString(typeOf(haystack))} instead.`);
|
||||
}
|
||||
|
||||
return haystack.indexOf(needle) >= 0;
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.needle);
|
||||
fn(this.haystack);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
return ["in", this.needle.serialize(), this.haystack.serialize()];
|
||||
}
|
||||
}
|
||||
|
||||
export default In;
|
||||
580
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/index.js
generated
vendored
Normal file
580
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/index.js
generated
vendored
Normal file
@@ -0,0 +1,580 @@
|
||||
// @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;
|
||||
89
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/index_of.js
generated
vendored
Normal file
89
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/index_of.js
generated
vendored
Normal file
@@ -0,0 +1,89 @@
|
||||
// @flow
|
||||
|
||||
import {BooleanType, StringType, ValueType, NullType, toString, NumberType, isValidType, isValidNativeType} from '../types.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
import {typeOf} from '../values.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class IndexOf implements Expression {
|
||||
type: Type;
|
||||
needle: Expression;
|
||||
haystack: Expression;
|
||||
fromIndex: ?Expression;
|
||||
|
||||
constructor(needle: Expression, haystack: Expression, fromIndex?: Expression) {
|
||||
this.type = NumberType;
|
||||
this.needle = needle;
|
||||
this.haystack = haystack;
|
||||
this.fromIndex = fromIndex;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?IndexOf {
|
||||
if (args.length <= 2 || args.length >= 5) {
|
||||
return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`);
|
||||
}
|
||||
|
||||
const needle = context.parse(args[1], 1, ValueType);
|
||||
|
||||
const haystack = context.parse(args[2], 2, ValueType);
|
||||
|
||||
if (!needle || !haystack) return null;
|
||||
if (!isValidType(needle.type, [BooleanType, StringType, NumberType, NullType, ValueType])) {
|
||||
return context.error(`Expected first argument to be of type boolean, string, number or null, but found ${toString(needle.type)} instead`);
|
||||
}
|
||||
|
||||
if (args.length === 4) {
|
||||
const fromIndex = context.parse(args[3], 3, NumberType);
|
||||
if (!fromIndex) return null;
|
||||
return new IndexOf(needle, haystack, fromIndex);
|
||||
} else {
|
||||
return new IndexOf(needle, haystack);
|
||||
}
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
const needle = (this.needle.evaluate(ctx): any);
|
||||
const haystack = (this.haystack.evaluate(ctx): any);
|
||||
|
||||
if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) {
|
||||
throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${toString(typeOf(needle))} instead.`);
|
||||
}
|
||||
|
||||
if (!isValidNativeType(haystack, ['string', 'array'])) {
|
||||
throw new RuntimeError(`Expected second argument to be of type array or string, but found ${toString(typeOf(haystack))} instead.`);
|
||||
}
|
||||
|
||||
if (this.fromIndex) {
|
||||
const fromIndex = (this.fromIndex.evaluate(ctx): number);
|
||||
return haystack.indexOf(needle, fromIndex);
|
||||
}
|
||||
|
||||
return haystack.indexOf(needle);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.needle);
|
||||
fn(this.haystack);
|
||||
if (this.fromIndex) {
|
||||
fn(this.fromIndex);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
if (this.fromIndex != null && this.fromIndex !== undefined) {
|
||||
const fromIndex = this.fromIndex.serialize();
|
||||
return ["index-of", this.needle.serialize(), this.haystack.serialize(), fromIndex];
|
||||
}
|
||||
return ["index-of", this.needle.serialize(), this.haystack.serialize()];
|
||||
}
|
||||
}
|
||||
|
||||
export default IndexOf;
|
||||
268
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/interpolate.js
generated
vendored
Normal file
268
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/interpolate.js
generated
vendored
Normal file
@@ -0,0 +1,268 @@
|
||||
// @flow
|
||||
|
||||
import UnitBezier from '@mapbox/unitbezier';
|
||||
|
||||
import * as interpolate from '../../util/interpolate.js';
|
||||
import {toString, NumberType, ColorType} from '../types.js';
|
||||
import {findStopLessThanOrEqualTo} from '../stops.js';
|
||||
import {hcl, lab} from '../../util/color_spaces.js';
|
||||
import Color from '../../util/color.js';
|
||||
|
||||
import type {Stops} from '../stops.js';
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
export type InterpolationType =
|
||||
{ name: 'linear' } |
|
||||
{ name: 'exponential', base: number } |
|
||||
{ name: 'cubic-bezier', controlPoints: [number, number, number, number] };
|
||||
|
||||
class Interpolate implements Expression {
|
||||
type: Type;
|
||||
|
||||
operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab';
|
||||
interpolation: InterpolationType;
|
||||
input: Expression;
|
||||
labels: Array<number>;
|
||||
outputs: Array<Expression>;
|
||||
|
||||
constructor(type: Type, operator: 'interpolate' | 'interpolate-hcl' | 'interpolate-lab', interpolation: InterpolationType, input: Expression, stops: Stops) {
|
||||
this.type = type;
|
||||
this.operator = operator;
|
||||
this.interpolation = interpolation;
|
||||
this.input = input;
|
||||
|
||||
this.labels = [];
|
||||
this.outputs = [];
|
||||
for (const [label, expression] of stops) {
|
||||
this.labels.push(label);
|
||||
this.outputs.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
static interpolationFactor(interpolation: InterpolationType, input: number, lower: number, upper: number): number {
|
||||
let t = 0;
|
||||
if (interpolation.name === 'exponential') {
|
||||
t = exponentialInterpolation(input, interpolation.base, lower, upper);
|
||||
} else if (interpolation.name === 'linear') {
|
||||
t = exponentialInterpolation(input, 1, lower, upper);
|
||||
} else if (interpolation.name === 'cubic-bezier') {
|
||||
const c = interpolation.controlPoints;
|
||||
const ub = new UnitBezier(c[0], c[1], c[2], c[3]);
|
||||
t = ub.solve(exponentialInterpolation(input, 1, lower, upper));
|
||||
}
|
||||
return t;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Interpolate {
|
||||
let [operator, interpolation, input, ...rest] = args;
|
||||
|
||||
if (!Array.isArray(interpolation) || interpolation.length === 0) {
|
||||
return context.error(`Expected an interpolation type expression.`, 1);
|
||||
}
|
||||
|
||||
if (interpolation[0] === 'linear') {
|
||||
interpolation = {name: 'linear'};
|
||||
} else if (interpolation[0] === 'exponential') {
|
||||
const base = interpolation[1];
|
||||
if (typeof base !== 'number')
|
||||
return context.error(`Exponential interpolation requires a numeric base.`, 1, 1);
|
||||
interpolation = {
|
||||
name: 'exponential',
|
||||
base
|
||||
};
|
||||
} else if (interpolation[0] === 'cubic-bezier') {
|
||||
const controlPoints = interpolation.slice(1);
|
||||
if (
|
||||
controlPoints.length !== 4 ||
|
||||
controlPoints.some(t => typeof t !== 'number' || t < 0 || t > 1)
|
||||
) {
|
||||
return context.error('Cubic bezier interpolation requires four numeric arguments with values between 0 and 1.', 1);
|
||||
}
|
||||
|
||||
interpolation = {
|
||||
name: 'cubic-bezier',
|
||||
controlPoints: (controlPoints: any)
|
||||
};
|
||||
} else {
|
||||
return context.error(`Unknown interpolation type ${String(interpolation[0])}`, 1, 0);
|
||||
}
|
||||
|
||||
if (args.length - 1 < 4) {
|
||||
return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
|
||||
}
|
||||
|
||||
if ((args.length - 1) % 2 !== 0) {
|
||||
return context.error(`Expected an even number of arguments.`);
|
||||
}
|
||||
|
||||
input = context.parse(input, 2, NumberType);
|
||||
if (!input) return null;
|
||||
|
||||
const stops: Stops = [];
|
||||
|
||||
let outputType: Type = (null: any);
|
||||
if (operator === 'interpolate-hcl' || operator === 'interpolate-lab') {
|
||||
outputType = ColorType;
|
||||
} else if (context.expectedType && context.expectedType.kind !== 'value') {
|
||||
outputType = context.expectedType;
|
||||
}
|
||||
|
||||
for (let i = 0; i < rest.length; i += 2) {
|
||||
const label = rest[i];
|
||||
const value = rest[i + 1];
|
||||
|
||||
const labelKey = i + 3;
|
||||
const valueKey = i + 4;
|
||||
|
||||
if (typeof label !== 'number') {
|
||||
return context.error('Input/output pairs for "interpolate" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey);
|
||||
}
|
||||
|
||||
if (stops.length && stops[stops.length - 1][0] >= label) {
|
||||
return context.error('Input/output pairs for "interpolate" expressions must be arranged with input values in strictly ascending order.', labelKey);
|
||||
}
|
||||
|
||||
const parsed = context.parse(value, valueKey, outputType);
|
||||
if (!parsed) return null;
|
||||
outputType = outputType || parsed.type;
|
||||
stops.push([label, parsed]);
|
||||
}
|
||||
|
||||
if (outputType.kind !== 'number' &&
|
||||
outputType.kind !== 'color' &&
|
||||
!(
|
||||
outputType.kind === 'array' &&
|
||||
outputType.itemType.kind === 'number' &&
|
||||
typeof outputType.N === 'number'
|
||||
)
|
||||
) {
|
||||
return context.error(`Type ${toString(outputType)} is not interpolatable.`);
|
||||
}
|
||||
|
||||
return new Interpolate(outputType, (operator: any), interpolation, input, stops);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): Color {
|
||||
const labels = this.labels;
|
||||
const outputs = this.outputs;
|
||||
|
||||
if (labels.length === 1) {
|
||||
return outputs[0].evaluate(ctx);
|
||||
}
|
||||
|
||||
const value = ((this.input.evaluate(ctx): any): number);
|
||||
if (value <= labels[0]) {
|
||||
return outputs[0].evaluate(ctx);
|
||||
}
|
||||
|
||||
const stopCount = labels.length;
|
||||
if (value >= labels[stopCount - 1]) {
|
||||
return outputs[stopCount - 1].evaluate(ctx);
|
||||
}
|
||||
|
||||
const index = findStopLessThanOrEqualTo(labels, value);
|
||||
const lower = labels[index];
|
||||
const upper = labels[index + 1];
|
||||
const t = Interpolate.interpolationFactor(this.interpolation, value, lower, upper);
|
||||
|
||||
const outputLower = outputs[index].evaluate(ctx);
|
||||
const outputUpper = outputs[index + 1].evaluate(ctx);
|
||||
|
||||
if (this.operator === 'interpolate') {
|
||||
return (interpolate[this.type.kind.toLowerCase()]: any)(outputLower, outputUpper, t); // eslint-disable-line import/namespace
|
||||
} else if (this.operator === 'interpolate-hcl') {
|
||||
return hcl.reverse(hcl.interpolate(hcl.forward(outputLower), hcl.forward(outputUpper), t));
|
||||
} else {
|
||||
return lab.reverse(lab.interpolate(lab.forward(outputLower), lab.forward(outputUpper), t));
|
||||
}
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
for (const expression of this.outputs) {
|
||||
fn(expression);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.outputs.every(out => out.outputDefined());
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
let interpolation;
|
||||
if (this.interpolation.name === 'linear') {
|
||||
interpolation = ["linear"];
|
||||
} else if (this.interpolation.name === 'exponential') {
|
||||
if (this.interpolation.base === 1) {
|
||||
interpolation = ["linear"];
|
||||
} else {
|
||||
interpolation = ["exponential", this.interpolation.base];
|
||||
}
|
||||
} else {
|
||||
interpolation = ["cubic-bezier" ].concat(this.interpolation.controlPoints);
|
||||
}
|
||||
|
||||
const serialized = [this.operator, interpolation, this.input.serialize()];
|
||||
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
serialized.push(
|
||||
this.labels[i],
|
||||
this.outputs[i].serialize()
|
||||
);
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a ratio that can be used to interpolate between exponential function
|
||||
* stops.
|
||||
* How it works: Two consecutive stop values define a (scaled and shifted) exponential function `f(x) = a * base^x + b`, where `base` is the user-specified base,
|
||||
* and `a` and `b` are constants affording sufficient degrees of freedom to fit
|
||||
* the function to the given stops.
|
||||
*
|
||||
* Here's a bit of algebra that lets us compute `f(x)` directly from the stop
|
||||
* values without explicitly solving for `a` and `b`:
|
||||
*
|
||||
* First stop value: `f(x0) = y0 = a * base^x0 + b`
|
||||
* Second stop value: `f(x1) = y1 = a * base^x1 + b`
|
||||
* => `y1 - y0 = a(base^x1 - base^x0)`
|
||||
* => `a = (y1 - y0)/(base^x1 - base^x0)`
|
||||
*
|
||||
* Desired value: `f(x) = y = a * base^x + b`
|
||||
* => `f(x) = y0 + a * (base^x - base^x0)`
|
||||
*
|
||||
* From the above, we can replace the `a` in `a * (base^x - base^x0)` and do a
|
||||
* little algebra:
|
||||
* ```
|
||||
* a * (base^x - base^x0) = (y1 - y0)/(base^x1 - base^x0) * (base^x - base^x0)
|
||||
* = (y1 - y0) * (base^x - base^x0) / (base^x1 - base^x0)
|
||||
* ```
|
||||
*
|
||||
* If we let `(base^x - base^x0) / (base^x1 base^x0)`, then we have
|
||||
* `f(x) = y0 + (y1 - y0) * ratio`. In other words, `ratio` may be treated as
|
||||
* an interpolation factor between the two stops' output values.
|
||||
*
|
||||
* (Note: a slightly different form for `ratio`,
|
||||
* `(base^(x-x0) - 1) / (base^(x1-x0) - 1) `, is equivalent, but requires fewer
|
||||
* expensive `Math.pow()` operations.)
|
||||
*
|
||||
* @private
|
||||
*/
|
||||
function exponentialInterpolation(input, base, lowerValue, upperValue) {
|
||||
const difference = upperValue - lowerValue;
|
||||
const progress = input - lowerValue;
|
||||
|
||||
if (difference === 0) {
|
||||
return 0;
|
||||
} else if (base === 1) {
|
||||
return progress / difference;
|
||||
} else {
|
||||
return (Math.pow(base, progress) - 1) / (Math.pow(base, difference) - 1);
|
||||
}
|
||||
}
|
||||
|
||||
export default Interpolate;
|
||||
61
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/length.js
generated
vendored
Normal file
61
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/length.js
generated
vendored
Normal file
@@ -0,0 +1,61 @@
|
||||
// @flow
|
||||
|
||||
import {NumberType, toString} from '../types.js';
|
||||
|
||||
import {typeOf} from '../values.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class Length implements Expression {
|
||||
type: Type;
|
||||
input: Expression;
|
||||
|
||||
constructor(input: Expression) {
|
||||
this.type = NumberType;
|
||||
this.input = input;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Length {
|
||||
if (args.length !== 2)
|
||||
return context.error(`Expected 1 argument, but found ${args.length - 1} instead.`);
|
||||
|
||||
const input = context.parse(args[1], 1);
|
||||
if (!input) return null;
|
||||
|
||||
if (input.type.kind !== 'array' && input.type.kind !== 'string' && input.type.kind !== 'value')
|
||||
return context.error(`Expected argument of type string or array, but found ${toString(input.type)} instead.`);
|
||||
|
||||
return new Length(input);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any | number {
|
||||
const input = this.input.evaluate(ctx);
|
||||
if (typeof input === 'string') {
|
||||
return input.length;
|
||||
} else if (Array.isArray(input)) {
|
||||
return input.length;
|
||||
} else {
|
||||
throw new RuntimeError(`Expected value to be of type string or array, but found ${toString(typeOf(input))} instead.`);
|
||||
}
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["length"];
|
||||
this.eachChild(child => { serialized.push(child.serialize()); });
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Length;
|
||||
72
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/let.js
generated
vendored
Normal file
72
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/let.js
generated
vendored
Normal file
@@ -0,0 +1,72 @@
|
||||
// @flow
|
||||
|
||||
import type {Type} from '../types.js';
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
|
||||
class Let implements Expression {
|
||||
type: Type;
|
||||
bindings: Array<[string, Expression]>;
|
||||
result: Expression;
|
||||
|
||||
constructor(bindings: Array<[string, Expression]>, result: Expression) {
|
||||
this.type = result.type;
|
||||
this.bindings = [].concat(bindings);
|
||||
this.result = result;
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
return this.result.evaluate(ctx);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
for (const binding of this.bindings) {
|
||||
fn(binding[1]);
|
||||
}
|
||||
fn(this.result);
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Let {
|
||||
if (args.length < 4)
|
||||
return context.error(`Expected at least 3 arguments, but found ${args.length - 1} instead.`);
|
||||
|
||||
const bindings: Array<[string, Expression]> = [];
|
||||
for (let i = 1; i < args.length - 1; i += 2) {
|
||||
const name = args[i];
|
||||
|
||||
if (typeof name !== 'string') {
|
||||
return context.error(`Expected string, but found ${typeof name} instead.`, i);
|
||||
}
|
||||
|
||||
if (/[^a-zA-Z0-9_]/.test(name)) {
|
||||
return context.error(`Variable names must contain only alphanumeric characters or '_'.`, i);
|
||||
}
|
||||
|
||||
const value = context.parse(args[i + 1], i + 1);
|
||||
if (!value) return null;
|
||||
|
||||
bindings.push([name, value]);
|
||||
}
|
||||
|
||||
const result = context.parse(args[args.length - 1], args.length - 1, context.expectedType, bindings);
|
||||
if (!result) return null;
|
||||
|
||||
return new Let(bindings, result);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.result.outputDefined();
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["let"];
|
||||
for (const [name, expr] of this.bindings) {
|
||||
serialized.push(name, expr.serialize());
|
||||
}
|
||||
serialized.push(this.result.serialize());
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Let;
|
||||
77
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/literal.js
generated
vendored
Normal file
77
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/literal.js
generated
vendored
Normal file
@@ -0,0 +1,77 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
import {isValue, typeOf, Color} from '../values.js';
|
||||
import Formatted from '../types/formatted.js';
|
||||
|
||||
import type {Type} from '../types.js';
|
||||
import type {Value} from '../values.js';
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
|
||||
class Literal implements Expression {
|
||||
type: Type;
|
||||
value: Value;
|
||||
|
||||
constructor(type: Type, value: Value) {
|
||||
this.type = type;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): void | Literal {
|
||||
if (args.length !== 2)
|
||||
return context.error(`'literal' expression requires exactly one argument, but found ${args.length - 1} instead.`);
|
||||
|
||||
if (!isValue(args[1]))
|
||||
return context.error(`invalid value`);
|
||||
|
||||
const value = (args[1]: any);
|
||||
let type = typeOf(value);
|
||||
|
||||
// special case: infer the item type if possible for zero-length arrays
|
||||
const expected = context.expectedType;
|
||||
if (
|
||||
type.kind === 'array' &&
|
||||
type.N === 0 &&
|
||||
expected &&
|
||||
expected.kind === 'array' &&
|
||||
(typeof expected.N !== 'number' || expected.N === 0)
|
||||
) {
|
||||
type = expected;
|
||||
}
|
||||
|
||||
return new Literal(type, value);
|
||||
}
|
||||
|
||||
evaluate(): Value {
|
||||
return this.value;
|
||||
}
|
||||
|
||||
eachChild() {}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
if (this.type.kind === 'array' || this.type.kind === 'object') {
|
||||
return ["literal", this.value];
|
||||
} else if (this.value instanceof Color) {
|
||||
// Constant-folding can generate Literal expressions that you
|
||||
// couldn't actually generate with a "literal" expression,
|
||||
// so we have to implement an equivalent serialization here
|
||||
return ["rgba"].concat(this.value.toArray());
|
||||
} else if (this.value instanceof Formatted) {
|
||||
// Same as Color
|
||||
return this.value.serialize();
|
||||
} else {
|
||||
assert(this.value === null ||
|
||||
typeof this.value === 'string' ||
|
||||
typeof this.value === 'number' ||
|
||||
typeof this.value === 'boolean');
|
||||
return (this.value: any);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default Literal;
|
||||
158
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/match.js
generated
vendored
Normal file
158
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/match.js
generated
vendored
Normal file
@@ -0,0 +1,158 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import {typeOf} from '../values.js';
|
||||
import {ValueType, type Type} from '../types.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
|
||||
// Map input label values to output expression index
|
||||
type Cases = {[number | string]: number};
|
||||
|
||||
class Match implements Expression {
|
||||
type: Type;
|
||||
inputType: Type;
|
||||
|
||||
input: Expression;
|
||||
cases: Cases;
|
||||
outputs: Array<Expression>;
|
||||
otherwise: Expression;
|
||||
|
||||
constructor(inputType: Type, outputType: Type, input: Expression, cases: Cases, outputs: Array<Expression>, otherwise: Expression) {
|
||||
this.inputType = inputType;
|
||||
this.type = outputType;
|
||||
this.input = input;
|
||||
this.cases = cases;
|
||||
this.outputs = outputs;
|
||||
this.otherwise = otherwise;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Match {
|
||||
if (args.length < 5)
|
||||
return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
|
||||
if (args.length % 2 !== 1)
|
||||
return context.error(`Expected an even number of arguments.`);
|
||||
|
||||
let inputType;
|
||||
let outputType;
|
||||
if (context.expectedType && context.expectedType.kind !== 'value') {
|
||||
outputType = context.expectedType;
|
||||
}
|
||||
const cases = {};
|
||||
const outputs = [];
|
||||
for (let i = 2; i < args.length - 1; i += 2) {
|
||||
let labels = args[i];
|
||||
const value = args[i + 1];
|
||||
|
||||
if (!Array.isArray(labels)) {
|
||||
labels = [labels];
|
||||
}
|
||||
|
||||
const labelContext = context.concat(i);
|
||||
if (labels.length === 0) {
|
||||
return labelContext.error('Expected at least one branch label.');
|
||||
}
|
||||
|
||||
for (const label of labels) {
|
||||
if (typeof label !== 'number' && typeof label !== 'string') {
|
||||
return labelContext.error(`Branch labels must be numbers or strings.`);
|
||||
} else if (typeof label === 'number' && Math.abs(label) > Number.MAX_SAFE_INTEGER) {
|
||||
return labelContext.error(`Branch labels must be integers no larger than ${Number.MAX_SAFE_INTEGER}.`);
|
||||
|
||||
} else if (typeof label === 'number' && Math.floor(label) !== label) {
|
||||
return labelContext.error(`Numeric branch labels must be integer values.`);
|
||||
|
||||
} else if (!inputType) {
|
||||
inputType = typeOf(label);
|
||||
} else if (labelContext.checkSubtype(inputType, typeOf(label))) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof cases[String(label)] !== 'undefined') {
|
||||
return labelContext.error('Branch labels must be unique.');
|
||||
}
|
||||
|
||||
cases[String(label)] = outputs.length;
|
||||
}
|
||||
|
||||
const result = context.parse(value, i, outputType);
|
||||
if (!result) return null;
|
||||
outputType = outputType || result.type;
|
||||
outputs.push(result);
|
||||
}
|
||||
|
||||
const input = context.parse(args[1], 1, ValueType);
|
||||
if (!input) return null;
|
||||
|
||||
const otherwise = context.parse(args[args.length - 1], args.length - 1, outputType);
|
||||
if (!otherwise) return null;
|
||||
|
||||
assert(inputType && outputType);
|
||||
|
||||
if (input.type.kind !== 'value' && context.concat(1).checkSubtype((inputType: any), input.type)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Match((inputType: any), (outputType: any), input, cases, outputs, otherwise);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
const input = (this.input.evaluate(ctx): any);
|
||||
const output = (typeOf(input) === this.inputType && this.outputs[this.cases[input]]) || this.otherwise;
|
||||
return output.evaluate(ctx);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
this.outputs.forEach(fn);
|
||||
fn(this.otherwise);
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined();
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["match", this.input.serialize()];
|
||||
|
||||
// Sort so serialization has an arbitrary defined order, even though
|
||||
// branch order doesn't affect evaluation
|
||||
const sortedLabels = Object.keys(this.cases).sort();
|
||||
|
||||
// Group branches by unique match expression to support condensed
|
||||
// serializations of the form [case1, case2, ...] -> matchExpression
|
||||
const groupedByOutput: Array<[number, Array<number | string>]> = [];
|
||||
const outputLookup: {[index: number]: number} = {}; // lookup index into groupedByOutput for a given output expression
|
||||
for (const label of sortedLabels) {
|
||||
const outputIndex = outputLookup[this.cases[label]];
|
||||
if (outputIndex === undefined) {
|
||||
// First time seeing this output, add it to the end of the grouped list
|
||||
outputLookup[this.cases[label]] = groupedByOutput.length;
|
||||
groupedByOutput.push([this.cases[label], [label]]);
|
||||
} else {
|
||||
// We've seen this expression before, add the label to that output's group
|
||||
groupedByOutput[outputIndex][1].push(label);
|
||||
}
|
||||
}
|
||||
|
||||
const coerceLabel = (label) => this.inputType.kind === 'number' ? Number(label) : label;
|
||||
|
||||
for (const [outputIndex, labels] of groupedByOutput) {
|
||||
if (labels.length === 1) {
|
||||
// Only a single label matches this output expression
|
||||
serialized.push(coerceLabel(labels[0]));
|
||||
} else {
|
||||
// Array of literal labels pointing to this output expression
|
||||
serialized.push(labels.map(coerceLabel));
|
||||
}
|
||||
serialized.push(this.outputs[outputIndex].serialize());
|
||||
}
|
||||
serialized.push(this.otherwise.serialize());
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Match;
|
||||
162
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/number_format.js
generated
vendored
Normal file
162
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/number_format.js
generated
vendored
Normal file
@@ -0,0 +1,162 @@
|
||||
// @flow
|
||||
|
||||
import {StringType, NumberType} from '../types.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
declare var Intl: {
|
||||
NumberFormat: Class<Intl$NumberFormat>
|
||||
};
|
||||
|
||||
declare class Intl$NumberFormat {
|
||||
constructor (
|
||||
locales?: string | string[],
|
||||
options?: NumberFormatOptions
|
||||
): Intl$NumberFormat;
|
||||
|
||||
static (
|
||||
locales?: string | string[],
|
||||
options?: NumberFormatOptions
|
||||
): Intl$NumberFormat;
|
||||
|
||||
format(a: number): string;
|
||||
|
||||
resolvedOptions(): any;
|
||||
}
|
||||
|
||||
type NumberFormatOptions = {
|
||||
style?: 'decimal' | 'currency' | 'percent' | 'unit';
|
||||
currency?: null | string;
|
||||
unit?: null | string;
|
||||
minimumFractionDigits?: null | string;
|
||||
maximumFractionDigits?: null | string;
|
||||
};
|
||||
|
||||
export default class NumberFormat implements Expression {
|
||||
type: Type;
|
||||
number: Expression;
|
||||
locale: Expression | null; // BCP 47 language tag
|
||||
currency: Expression | null; // ISO 4217 currency code, required if style=currency
|
||||
unit: Expression | null; // Simple units sanctioned for use in ECMAScript, required if style=unit. https://tc39.es/proposal-unified-intl-numberformat/section6/locales-currencies-tz_proposed_out.html#sec-issanctionedsimpleunitidentifier
|
||||
minFractionDigits: Expression | null; // Default 0
|
||||
maxFractionDigits: Expression | null; // Default 3
|
||||
|
||||
constructor(number: Expression,
|
||||
locale: Expression | null,
|
||||
currency: Expression | null,
|
||||
unit: Expression | null,
|
||||
minFractionDigits: Expression | null,
|
||||
maxFractionDigits: Expression | null) {
|
||||
this.type = StringType;
|
||||
this.number = number;
|
||||
this.locale = locale;
|
||||
this.currency = currency;
|
||||
this.unit = unit;
|
||||
this.minFractionDigits = minFractionDigits;
|
||||
this.maxFractionDigits = maxFractionDigits;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Expression {
|
||||
if (args.length !== 3)
|
||||
return context.error(`Expected two arguments.`);
|
||||
|
||||
const number = context.parse(args[1], 1, NumberType);
|
||||
if (!number) return null;
|
||||
|
||||
const options = (args[2]: any);
|
||||
if (typeof options !== "object" || Array.isArray(options))
|
||||
return context.error(`NumberFormat options argument must be an object.`);
|
||||
|
||||
let locale = null;
|
||||
if (options['locale']) {
|
||||
locale = context.parse(options['locale'], 1, StringType);
|
||||
if (!locale) return null;
|
||||
}
|
||||
|
||||
let currency = null;
|
||||
if (options['currency']) {
|
||||
currency = context.parse(options['currency'], 1, StringType);
|
||||
if (!currency) return null;
|
||||
}
|
||||
|
||||
let unit = null;
|
||||
if (options['unit']) {
|
||||
unit = context.parse(options['unit'], 1, StringType);
|
||||
if (!unit) return null;
|
||||
}
|
||||
|
||||
let minFractionDigits = null;
|
||||
if (options['min-fraction-digits']) {
|
||||
minFractionDigits = context.parse(options['min-fraction-digits'], 1, NumberType);
|
||||
if (!minFractionDigits) return null;
|
||||
}
|
||||
|
||||
let maxFractionDigits = null;
|
||||
if (options['max-fraction-digits']) {
|
||||
maxFractionDigits = context.parse(options['max-fraction-digits'], 1, NumberType);
|
||||
if (!maxFractionDigits) return null;
|
||||
}
|
||||
|
||||
return new NumberFormat(number, locale, currency, unit, minFractionDigits, maxFractionDigits);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): string {
|
||||
return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [],
|
||||
{
|
||||
style:
|
||||
(this.currency && "currency") ||
|
||||
(this.unit && "unit") ||
|
||||
"decimal",
|
||||
currency: this.currency ? this.currency.evaluate(ctx) : undefined,
|
||||
unit: this.unit ? this.unit.evaluate(ctx) : undefined,
|
||||
minimumFractionDigits: this.minFractionDigits ? this.minFractionDigits.evaluate(ctx) : undefined,
|
||||
maximumFractionDigits: this.maxFractionDigits ? this.maxFractionDigits.evaluate(ctx) : undefined,
|
||||
}).format(this.number.evaluate(ctx));
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.number);
|
||||
if (this.locale) {
|
||||
fn(this.locale);
|
||||
}
|
||||
if (this.currency) {
|
||||
fn(this.currency);
|
||||
}
|
||||
if (this.unit) {
|
||||
fn(this.unit);
|
||||
}
|
||||
if (this.minFractionDigits) {
|
||||
fn(this.minFractionDigits);
|
||||
}
|
||||
if (this.maxFractionDigits) {
|
||||
fn(this.maxFractionDigits);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const options = {};
|
||||
if (this.locale) {
|
||||
options['locale'] = this.locale.serialize();
|
||||
}
|
||||
if (this.currency) {
|
||||
options['currency'] = this.currency.serialize();
|
||||
}
|
||||
if (this.unit) {
|
||||
options['unit'] = this.unit.serialize();
|
||||
}
|
||||
if (this.minFractionDigits) {
|
||||
options['min-fraction-digits'] = this.minFractionDigits.serialize();
|
||||
}
|
||||
if (this.maxFractionDigits) {
|
||||
options['max-fraction-digits'] = this.maxFractionDigits.serialize();
|
||||
}
|
||||
return ["number-format", this.number.serialize(), options];
|
||||
}
|
||||
}
|
||||
86
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/slice.js
generated
vendored
Normal file
86
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/slice.js
generated
vendored
Normal file
@@ -0,0 +1,86 @@
|
||||
// @flow
|
||||
|
||||
import {ValueType, NumberType, StringType, array, toString, isValidType, isValidNativeType} from '../types.js';
|
||||
import RuntimeError from '../runtime_error.js';
|
||||
import {typeOf} from '../values.js';
|
||||
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class Slice implements Expression {
|
||||
type: Type;
|
||||
input: Expression;
|
||||
beginIndex: Expression;
|
||||
endIndex: ?Expression;
|
||||
|
||||
constructor(type: Type, input: Expression, beginIndex: Expression, endIndex?: Expression) {
|
||||
this.type = type;
|
||||
this.input = input;
|
||||
this.beginIndex = beginIndex;
|
||||
this.endIndex = endIndex;
|
||||
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Slice {
|
||||
if (args.length <= 2 || args.length >= 5) {
|
||||
return context.error(`Expected 3 or 4 arguments, but found ${args.length - 1} instead.`);
|
||||
}
|
||||
|
||||
const input = context.parse(args[1], 1, ValueType);
|
||||
const beginIndex = context.parse(args[2], 2, NumberType);
|
||||
|
||||
if (!input || !beginIndex) return null;
|
||||
|
||||
if (!isValidType(input.type, [array(ValueType), StringType, ValueType])) {
|
||||
return context.error(`Expected first argument to be of type array or string, but found ${toString(input.type)} instead`);
|
||||
}
|
||||
|
||||
if (args.length === 4) {
|
||||
const endIndex = context.parse(args[3], 3, NumberType);
|
||||
if (!endIndex) return null;
|
||||
return new Slice(input.type, input, beginIndex, endIndex);
|
||||
} else {
|
||||
return new Slice(input.type, input, beginIndex);
|
||||
}
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
const input = (this.input.evaluate(ctx): any);
|
||||
const beginIndex = (this.beginIndex.evaluate(ctx): number);
|
||||
|
||||
if (!isValidNativeType(input, ['string', 'array'])) {
|
||||
throw new RuntimeError(`Expected first argument to be of type array or string, but found ${toString(typeOf(input))} instead.`);
|
||||
}
|
||||
|
||||
if (this.endIndex) {
|
||||
const endIndex = (this.endIndex.evaluate(ctx): number);
|
||||
return input.slice(beginIndex, endIndex);
|
||||
}
|
||||
|
||||
return input.slice(beginIndex);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
fn(this.beginIndex);
|
||||
if (this.endIndex) {
|
||||
fn(this.endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
if (this.endIndex != null && this.endIndex !== undefined) {
|
||||
const endIndex = this.endIndex.serialize();
|
||||
return ["slice", this.input.serialize(), this.beginIndex.serialize(), endIndex];
|
||||
}
|
||||
return ["slice", this.input.serialize(), this.beginIndex.serialize()];
|
||||
}
|
||||
}
|
||||
|
||||
export default Slice;
|
||||
120
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/step.js
generated
vendored
Normal file
120
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/step.js
generated
vendored
Normal file
@@ -0,0 +1,120 @@
|
||||
// @flow
|
||||
|
||||
import {NumberType} from '../types.js';
|
||||
|
||||
import {findStopLessThanOrEqualTo} from '../stops.js';
|
||||
|
||||
import type {Stops} from '../stops.js';
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {Type} from '../types.js';
|
||||
|
||||
class Step implements Expression {
|
||||
type: Type;
|
||||
|
||||
input: Expression;
|
||||
labels: Array<number>;
|
||||
outputs: Array<Expression>;
|
||||
|
||||
constructor(type: Type, input: Expression, stops: Stops) {
|
||||
this.type = type;
|
||||
this.input = input;
|
||||
|
||||
this.labels = [];
|
||||
this.outputs = [];
|
||||
for (const [label, expression] of stops) {
|
||||
this.labels.push(label);
|
||||
this.outputs.push(expression);
|
||||
}
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Step {
|
||||
if (args.length - 1 < 4) {
|
||||
return context.error(`Expected at least 4 arguments, but found only ${args.length - 1}.`);
|
||||
}
|
||||
|
||||
if ((args.length - 1) % 2 !== 0) {
|
||||
return context.error(`Expected an even number of arguments.`);
|
||||
}
|
||||
|
||||
const input = context.parse(args[1], 1, NumberType);
|
||||
if (!input) return null;
|
||||
|
||||
const stops: Stops = [];
|
||||
|
||||
let outputType: Type = (null: any);
|
||||
if (context.expectedType && context.expectedType.kind !== 'value') {
|
||||
outputType = context.expectedType;
|
||||
}
|
||||
|
||||
for (let i = 1; i < args.length; i += 2) {
|
||||
const label = i === 1 ? -Infinity : args[i];
|
||||
const value = args[i + 1];
|
||||
|
||||
const labelKey = i;
|
||||
const valueKey = i + 1;
|
||||
|
||||
if (typeof label !== 'number') {
|
||||
return context.error('Input/output pairs for "step" expressions must be defined using literal numeric values (not computed expressions) for the input values.', labelKey);
|
||||
}
|
||||
|
||||
if (stops.length && stops[stops.length - 1][0] >= label) {
|
||||
return context.error('Input/output pairs for "step" expressions must be arranged with input values in strictly ascending order.', labelKey);
|
||||
}
|
||||
|
||||
const parsed = context.parse(value, valueKey, outputType);
|
||||
if (!parsed) return null;
|
||||
outputType = outputType || parsed.type;
|
||||
stops.push([label, parsed]);
|
||||
}
|
||||
|
||||
return new Step(outputType, input, stops);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
const labels = this.labels;
|
||||
const outputs = this.outputs;
|
||||
|
||||
if (labels.length === 1) {
|
||||
return outputs[0].evaluate(ctx);
|
||||
}
|
||||
|
||||
const value = ((this.input.evaluate(ctx): any): number);
|
||||
if (value <= labels[0]) {
|
||||
return outputs[0].evaluate(ctx);
|
||||
}
|
||||
|
||||
const stopCount = labels.length;
|
||||
if (value >= labels[stopCount - 1]) {
|
||||
return outputs[stopCount - 1].evaluate(ctx);
|
||||
}
|
||||
|
||||
const index = findStopLessThanOrEqualTo(labels, value);
|
||||
return outputs[index].evaluate(ctx);
|
||||
}
|
||||
|
||||
eachChild(fn: (_: Expression) => void) {
|
||||
fn(this.input);
|
||||
for (const expression of this.outputs) {
|
||||
fn(expression);
|
||||
}
|
||||
}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return this.outputs.every(out => out.outputDefined());
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
const serialized = ["step", this.input.serialize()];
|
||||
for (let i = 0; i < this.labels.length; i++) {
|
||||
if (i > 0) {
|
||||
serialized.push(this.labels[i]);
|
||||
}
|
||||
serialized.push(this.outputs[i].serialize());
|
||||
}
|
||||
return serialized;
|
||||
}
|
||||
}
|
||||
|
||||
export default Step;
|
||||
46
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/var.js
generated
vendored
Normal file
46
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/var.js
generated
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
// @flow
|
||||
|
||||
import type {Type} from '../types.js';
|
||||
import type {Expression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
|
||||
class Var implements Expression {
|
||||
type: Type;
|
||||
name: string;
|
||||
boundExpression: Expression;
|
||||
|
||||
constructor(name: string, boundExpression: Expression) {
|
||||
this.type = boundExpression.type;
|
||||
this.name = name;
|
||||
this.boundExpression = boundExpression;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): void | Var {
|
||||
if (args.length !== 2 || typeof args[1] !== 'string')
|
||||
return context.error(`'var' expression requires exactly one string literal argument.`);
|
||||
|
||||
const name = args[1];
|
||||
if (!context.scope.has(name)) {
|
||||
return context.error(`Unknown variable "${name}". Make sure "${name}" has been bound in an enclosing "let" expression before using it.`, 1);
|
||||
}
|
||||
|
||||
return new Var(name, context.scope.get(name));
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): any {
|
||||
return this.boundExpression.evaluate(ctx);
|
||||
}
|
||||
|
||||
eachChild() {}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return false;
|
||||
}
|
||||
|
||||
serialize(): Array<string> {
|
||||
return ["var", this.name];
|
||||
}
|
||||
}
|
||||
|
||||
export default Var;
|
||||
349
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/within.js
generated
vendored
Normal file
349
node_modules/@mapbox/mapbox-gl-style-spec/expression/definitions/within.js
generated
vendored
Normal file
@@ -0,0 +1,349 @@
|
||||
// @flow
|
||||
|
||||
import {isValue} from '../values.js';
|
||||
import type {Type} from '../types.js';
|
||||
import {BooleanType} from '../types.js';
|
||||
import type {Expression, SerializedExpression} from '../expression.js';
|
||||
import type ParsingContext from '../parsing_context.js';
|
||||
import type EvaluationContext from '../evaluation_context.js';
|
||||
import type {GeoJSON, GeoJSONPolygon, GeoJSONMultiPolygon} from '@mapbox/geojson-types';
|
||||
import type {CanonicalTileID} from '../../../source/tile_id.js';
|
||||
|
||||
type GeoJSONPolygons =| GeoJSONPolygon | GeoJSONMultiPolygon;
|
||||
|
||||
// minX, minY, maxX, maxY
|
||||
type BBox = [number, number, number, number];
|
||||
const EXTENT = 8192;
|
||||
|
||||
function updateBBox(bbox: BBox, coord: [number, number]) {
|
||||
bbox[0] = Math.min(bbox[0], coord[0]);
|
||||
bbox[1] = Math.min(bbox[1], coord[1]);
|
||||
bbox[2] = Math.max(bbox[2], coord[0]);
|
||||
bbox[3] = Math.max(bbox[3], coord[1]);
|
||||
}
|
||||
|
||||
function mercatorXfromLng(lng: number) {
|
||||
return (180 + lng) / 360;
|
||||
}
|
||||
|
||||
function mercatorYfromLat(lat: number) {
|
||||
return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360;
|
||||
}
|
||||
|
||||
function boxWithinBox(bbox1: BBox, bbox2: BBox) {
|
||||
if (bbox1[0] <= bbox2[0]) return false;
|
||||
if (bbox1[2] >= bbox2[2]) return false;
|
||||
if (bbox1[1] <= bbox2[1]) return false;
|
||||
if (bbox1[3] >= bbox2[3]) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
function getTileCoordinates(p, canonical: CanonicalTileID) {
|
||||
const x = mercatorXfromLng(p[0]);
|
||||
const y = mercatorYfromLat(p[1]);
|
||||
const tilesAtZoom = Math.pow(2, canonical.z);
|
||||
return [Math.round(x * tilesAtZoom * EXTENT), Math.round(y * tilesAtZoom * EXTENT)];
|
||||
}
|
||||
|
||||
function onBoundary(p, p1, p2) {
|
||||
const x1 = p[0] - p1[0];
|
||||
const y1 = p[1] - p1[1];
|
||||
const x2 = p[0] - p2[0];
|
||||
const y2 = p[1] - p2[1];
|
||||
return (x1 * y2 - x2 * y1 === 0) && (x1 * x2 <= 0) && (y1 * y2 <= 0);
|
||||
}
|
||||
|
||||
function rayIntersect(p, p1, p2) {
|
||||
return ((p1[1] > p[1]) !== (p2[1] > p[1])) && (p[0] < (p2[0] - p1[0]) * (p[1] - p1[1]) / (p2[1] - p1[1]) + p1[0]);
|
||||
}
|
||||
|
||||
// ray casting algorithm for detecting if point is in polygon
|
||||
function pointWithinPolygon(point, rings) {
|
||||
let inside = false;
|
||||
for (let i = 0, len = rings.length; i < len; i++) {
|
||||
const ring = rings[i];
|
||||
for (let j = 0, len2 = ring.length; j < len2 - 1; j++) {
|
||||
if (onBoundary(point, ring[j], ring[j + 1])) return false;
|
||||
if (rayIntersect(point, ring[j], ring[j + 1])) inside = !inside;
|
||||
}
|
||||
}
|
||||
return inside;
|
||||
}
|
||||
|
||||
function pointWithinPolygons(point, polygons) {
|
||||
for (let i = 0; i < polygons.length; i++) {
|
||||
if (pointWithinPolygon(point, polygons[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function perp(v1, v2) {
|
||||
return (v1[0] * v2[1] - v1[1] * v2[0]);
|
||||
}
|
||||
|
||||
// check if p1 and p2 are in different sides of line segment q1->q2
|
||||
function twoSided(p1, p2, q1, q2) {
|
||||
// q1->p1 (x1, y1), q1->p2 (x2, y2), q1->q2 (x3, y3)
|
||||
const x1 = p1[0] - q1[0];
|
||||
const y1 = p1[1] - q1[1];
|
||||
const x2 = p2[0] - q1[0];
|
||||
const y2 = p2[1] - q1[1];
|
||||
const x3 = q2[0] - q1[0];
|
||||
const y3 = q2[1] - q1[1];
|
||||
const det1 = (x1 * y3 - x3 * y1);
|
||||
const det2 = (x2 * y3 - x3 * y2);
|
||||
if ((det1 > 0 && det2 < 0) || (det1 < 0 && det2 > 0)) return true;
|
||||
return false;
|
||||
}
|
||||
// a, b are end points for line segment1, c and d are end points for line segment2
|
||||
function lineIntersectLine(a, b, c, d) {
|
||||
// check if two segments are parallel or not
|
||||
// precondition is end point a, b is inside polygon, if line a->b is
|
||||
// parallel to polygon edge c->d, then a->b won't intersect with c->d
|
||||
const vectorP = [b[0] - a[0], b[1] - a[1]];
|
||||
const vectorQ = [d[0] - c[0], d[1] - c[1]];
|
||||
if (perp(vectorQ, vectorP) === 0) return false;
|
||||
|
||||
// If lines are intersecting with each other, the relative location should be:
|
||||
// a and b lie in different sides of segment c->d
|
||||
// c and d lie in different sides of segment a->b
|
||||
if (twoSided(a, b, c, d) && twoSided(c, d, a, b)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
function lineIntersectPolygon(p1, p2, polygon) {
|
||||
for (const ring of polygon) {
|
||||
// loop through every edge of the ring
|
||||
for (let j = 0; j < ring.length - 1; ++j) {
|
||||
if (lineIntersectLine(p1, p2, ring[j], ring[j + 1])) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function lineStringWithinPolygon(line, polygon) {
|
||||
// First, check if geometry points of line segments are all inside polygon
|
||||
for (let i = 0; i < line.length; ++i) {
|
||||
if (!pointWithinPolygon(line[i], polygon)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Second, check if there is line segment intersecting polygon edge
|
||||
for (let i = 0; i < line.length - 1; ++i) {
|
||||
if (lineIntersectPolygon(line[i], line[i + 1], polygon)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function lineStringWithinPolygons(line, polygons) {
|
||||
for (let i = 0; i < polygons.length; i++) {
|
||||
if (lineStringWithinPolygon(line, polygons[i])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
function getTilePolygon(coordinates, bbox: BBox, canonical: CanonicalTileID) {
|
||||
const polygon = [];
|
||||
for (let i = 0; i < coordinates.length; i++) {
|
||||
const ring = [];
|
||||
for (let j = 0; j < coordinates[i].length; j++) {
|
||||
const coord = getTileCoordinates(coordinates[i][j], canonical);
|
||||
updateBBox(bbox, coord);
|
||||
ring.push(coord);
|
||||
}
|
||||
polygon.push(ring);
|
||||
}
|
||||
return polygon;
|
||||
}
|
||||
|
||||
function getTilePolygons(coordinates, bbox, canonical: CanonicalTileID) {
|
||||
const polygons = [];
|
||||
for (let i = 0; i < coordinates.length; i++) {
|
||||
const polygon = getTilePolygon(coordinates[i], bbox, canonical);
|
||||
polygons.push(polygon);
|
||||
}
|
||||
return polygons;
|
||||
}
|
||||
|
||||
function updatePoint(p, bbox, polyBBox, worldSize) {
|
||||
if (p[0] < polyBBox[0] || p[0] > polyBBox[2]) {
|
||||
const halfWorldSize = worldSize * 0.5;
|
||||
let shift = (p[0] - polyBBox[0] > halfWorldSize) ? -worldSize : (polyBBox[0] - p[0] > halfWorldSize) ? worldSize : 0;
|
||||
if (shift === 0) {
|
||||
shift = (p[0] - polyBBox[2] > halfWorldSize) ? -worldSize : (polyBBox[2] - p[0] > halfWorldSize) ? worldSize : 0;
|
||||
}
|
||||
p[0] += shift;
|
||||
}
|
||||
updateBBox(bbox, p);
|
||||
}
|
||||
|
||||
function resetBBox(bbox) {
|
||||
bbox[0] = bbox[1] = Infinity;
|
||||
bbox[2] = bbox[3] = -Infinity;
|
||||
}
|
||||
|
||||
function getTilePoints(geometry, pointBBox, polyBBox, canonical: CanonicalTileID) {
|
||||
const worldSize = Math.pow(2, canonical.z) * EXTENT;
|
||||
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
|
||||
const tilePoints = [];
|
||||
if (!geometry) return tilePoints;
|
||||
for (const points of geometry) {
|
||||
for (const point of points) {
|
||||
const p = [point.x + shifts[0], point.y + shifts[1]];
|
||||
updatePoint(p, pointBBox, polyBBox, worldSize);
|
||||
tilePoints.push(p);
|
||||
}
|
||||
}
|
||||
return tilePoints;
|
||||
}
|
||||
|
||||
function getTileLines(geometry, lineBBox, polyBBox, canonical: CanonicalTileID) {
|
||||
const worldSize = Math.pow(2, canonical.z) * EXTENT;
|
||||
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
|
||||
const tileLines = [];
|
||||
if (!geometry) return tileLines;
|
||||
for (const line of geometry) {
|
||||
const tileLine = [];
|
||||
for (const point of line) {
|
||||
const p = [point.x + shifts[0], point.y + shifts[1]];
|
||||
updateBBox(lineBBox, p);
|
||||
tileLine.push(p);
|
||||
}
|
||||
tileLines.push(tileLine);
|
||||
}
|
||||
if (lineBBox[2] - lineBBox[0] <= worldSize / 2) {
|
||||
resetBBox(lineBBox);
|
||||
for (const line of tileLines) {
|
||||
for (const p of line) {
|
||||
updatePoint(p, lineBBox, polyBBox, worldSize);
|
||||
}
|
||||
}
|
||||
}
|
||||
return tileLines;
|
||||
}
|
||||
|
||||
function pointsWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons) {
|
||||
const pointBBox = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
|
||||
const canonical = ctx.canonicalID();
|
||||
if (!canonical) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (polygonGeometry.type === 'Polygon') {
|
||||
const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical);
|
||||
const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical);
|
||||
if (!boxWithinBox(pointBBox, polyBBox)) return false;
|
||||
|
||||
for (const point of tilePoints) {
|
||||
if (!pointWithinPolygon(point, tilePolygon)) return false;
|
||||
}
|
||||
}
|
||||
if (polygonGeometry.type === 'MultiPolygon') {
|
||||
const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical);
|
||||
const tilePoints = getTilePoints(ctx.geometry(), pointBBox, polyBBox, canonical);
|
||||
if (!boxWithinBox(pointBBox, polyBBox)) return false;
|
||||
|
||||
for (const point of tilePoints) {
|
||||
if (!pointWithinPolygons(point, tilePolygons)) return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
function linesWithinPolygons(ctx: EvaluationContext, polygonGeometry: GeoJSONPolygons) {
|
||||
const lineBBox = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
|
||||
|
||||
const canonical = ctx.canonicalID();
|
||||
if (!canonical) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (polygonGeometry.type === 'Polygon') {
|
||||
const tilePolygon = getTilePolygon(polygonGeometry.coordinates, polyBBox, canonical);
|
||||
const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical);
|
||||
if (!boxWithinBox(lineBBox, polyBBox)) return false;
|
||||
|
||||
for (const line of tileLines) {
|
||||
if (!lineStringWithinPolygon(line, tilePolygon)) return false;
|
||||
}
|
||||
}
|
||||
if (polygonGeometry.type === 'MultiPolygon') {
|
||||
const tilePolygons = getTilePolygons(polygonGeometry.coordinates, polyBBox, canonical);
|
||||
const tileLines = getTileLines(ctx.geometry(), lineBBox, polyBBox, canonical);
|
||||
if (!boxWithinBox(lineBBox, polyBBox)) return false;
|
||||
|
||||
for (const line of tileLines) {
|
||||
if (!lineStringWithinPolygons(line, tilePolygons)) return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
class Within implements Expression {
|
||||
type: Type;
|
||||
geojson: GeoJSON
|
||||
geometries: GeoJSONPolygons;
|
||||
|
||||
constructor(geojson: GeoJSON, geometries: GeoJSONPolygons) {
|
||||
this.type = BooleanType;
|
||||
this.geojson = geojson;
|
||||
this.geometries = geometries;
|
||||
}
|
||||
|
||||
static parse(args: $ReadOnlyArray<mixed>, context: ParsingContext): ?Within {
|
||||
if (args.length !== 2)
|
||||
return context.error(`'within' expression requires exactly one argument, but found ${args.length - 1} instead.`);
|
||||
if (isValue(args[1])) {
|
||||
const geojson = (args[1]: Object);
|
||||
if (geojson.type === 'FeatureCollection') {
|
||||
for (let i = 0; i < geojson.features.length; ++i) {
|
||||
const type = geojson.features[i].geometry.type;
|
||||
if (type === 'Polygon' || type === 'MultiPolygon') {
|
||||
return new Within(geojson, geojson.features[i].geometry);
|
||||
}
|
||||
}
|
||||
} else if (geojson.type === 'Feature') {
|
||||
const type = geojson.geometry.type;
|
||||
if (type === 'Polygon' || type === 'MultiPolygon') {
|
||||
return new Within(geojson, geojson.geometry);
|
||||
}
|
||||
} else if (geojson.type === 'Polygon' || geojson.type === 'MultiPolygon') {
|
||||
return new Within(geojson, geojson);
|
||||
}
|
||||
}
|
||||
return context.error(`'within' expression requires valid geojson object that contains polygon geometry type.`);
|
||||
}
|
||||
|
||||
evaluate(ctx: EvaluationContext): boolean {
|
||||
if (ctx.geometry() != null && ctx.canonicalID() != null) {
|
||||
if (ctx.geometryType() === 'Point') {
|
||||
return pointsWithinPolygons(ctx, this.geometries);
|
||||
} else if (ctx.geometryType() === 'LineString') {
|
||||
return linesWithinPolygons(ctx, this.geometries);
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
eachChild() {}
|
||||
|
||||
outputDefined(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
serialize(): SerializedExpression {
|
||||
return ["within", this.geojson];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default Within;
|
||||
Reference in New Issue
Block a user