@maplibre/maplibre-gl-style-spec
Advanced tools
Comparing version 20.1.1 to 20.2.0
@@ -1,2 +0,2 @@ | ||
// Generated by dts-bundle-generator v9.2.5 | ||
// Generated by dts-bundle-generator v9.5.1 | ||
@@ -222,2 +222,5 @@ declare const _default: any; | ||
] | [ | ||
"distance", | ||
unknown | ExpressionSpecification | ||
] | [ | ||
"interpolate", | ||
@@ -1029,2 +1032,20 @@ InterpolationSpecification, | ||
}; | ||
/** | ||
* Diff two stylesheet | ||
* | ||
* Creates semanticly aware diffs that can easily be applied at runtime. | ||
* Operations produced by the diff closely resemble the maplibre-gl-js API. Any | ||
* error creating the diff will fall back to the 'setStyle' operation. | ||
* | ||
* Example diff: | ||
* [ | ||
* { command: 'setConstant', args: ['@water', '#0000FF'] }, | ||
* { command: 'setPaintProperty', args: ['background', 'background-color', 'black'] } | ||
* ] | ||
* | ||
* @private | ||
* @param {*} [before] stylesheet to compare from | ||
* @param {*} after stylesheet to compare to | ||
* @returns Array list of changes | ||
*/ | ||
declare function diffStyles(before: StyleSpecification, after: StyleSpecification): DiffCommand<DiffOperations>[]; | ||
@@ -1640,2 +1661,11 @@ export declare class ValidationError { | ||
declare function isExpressionFilter(filter: any): filter is ExpressionFilterSpecification; | ||
/** | ||
* Given a filter expressed as nested arrays, return a new function | ||
* that evaluates whether a given feature (with a .properties or .tags property) | ||
* passes its test. | ||
* | ||
* @private | ||
* @param {Array} filter MapLibre filter | ||
* @returns {Function} filter-evaluating function | ||
*/ | ||
declare function createFilter(filter: any): FeatureFilter; | ||
@@ -1883,2 +1913,12 @@ export type ExpectedTypes = { | ||
export function migrate(style: StyleSpecification): StyleSpecification; | ||
export type RingWithArea<T extends Point2D> = T[] & { | ||
area?: number; | ||
}; | ||
/** | ||
* Classifies an array of rings into polygons with outer rings and holes | ||
* @param rings - the rings to classify | ||
* @param maxRings - the maximum number of rings to include in a polygon, use 0 to include all rings | ||
* @returns an array of polygons with internal rings as holes | ||
*/ | ||
export declare function classifyRings<T extends Point2D>(rings: RingWithArea<T>[], maxRings?: number): RingWithArea<T>[][]; | ||
export type ExpressionType = "data-driven" | "cross-faded" | "cross-faded-data-driven" | "color-ramp" | "data-constant" | "constant"; | ||
@@ -1885,0 +1925,0 @@ export type ExpressionParameters = Array<"zoom" | "feature" | "feature-state" | "heatmap-density" | "line-progress">; |
{ | ||
"name": "@maplibre/maplibre-gl-style-spec", | ||
"description": "a specification for maplibre styles", | ||
"version": "20.1.1", | ||
"version": "20.2.0", | ||
"author": "MapLibre", | ||
@@ -23,2 +23,5 @@ "keywords": [ | ||
"generate-typings": "node --no-warnings --loader ts-node/esm build/generate-typings.ts", | ||
"generate-docs": "node ${WATCH+--watch} --no-warnings --loader ts-node/esm build/generate-docs.ts", | ||
"mkdocs": "docker run --rm -it -p 8000:8000 -v ${PWD}:/docs squidfunk/mkdocs-material", | ||
"mkdocs-build": "npm run generate-docs && docker run --rm -v ${PWD}:/docs squidfunk/mkdocs-material build", | ||
"test-build": "jest --selectProjects=build", | ||
@@ -53,4 +56,6 @@ "test-integration": "jest --selectProjects=integration", | ||
"minimist": "^1.2.8", | ||
"quickselect": "^2.0.0", | ||
"rw": "^1.3.3", | ||
"sort-object": "^3.0.3" | ||
"sort-object": "^3.0.3", | ||
"tinyqueue": "^2.0.3" | ||
}, | ||
@@ -66,23 +71,22 @@ "sideEffects": false, | ||
"@rollup/plugin-typescript": "^11.1.6", | ||
"@types/eslint": "^8.56.2", | ||
"@types/geojson": "^7946.0.13", | ||
"@types/jest": "^29.5.11", | ||
"@types/node": "^20.11.10", | ||
"@typescript-eslint/eslint-plugin": "^6.19.1", | ||
"@typescript-eslint/parser": "^6.19.1", | ||
"dts-bundle-generator": "^9.2.5", | ||
"eslint": "^8.56.0", | ||
"@types/eslint": "^8.56.6", | ||
"@types/geojson": "^7946.0.14", | ||
"@types/jest": "^29.5.12", | ||
"@types/node": "^20.12.7", | ||
"@typescript-eslint/eslint-plugin": "^7.7.1", | ||
"@typescript-eslint/parser": "^7.7.1", | ||
"dts-bundle-generator": "^9.5.1", | ||
"eslint": "^8.57.0", | ||
"eslint-config-mourner": "^3.0.0", | ||
"eslint-plugin-html": "^7.1.0", | ||
"eslint-plugin-html": "^8.1.1", | ||
"eslint-plugin-import": "^2.29.1", | ||
"eslint-plugin-jest": "^27.6.3", | ||
"eslint-plugin-jsdoc": "^48.0.4", | ||
"eslint-plugin-react": "^7.33.2", | ||
"glob": "^10.3.10", | ||
"eslint-plugin-jest": "^28.3.0", | ||
"eslint-plugin-jsdoc": "^48.2.3", | ||
"eslint-plugin-react": "^7.34.1", | ||
"glob": "^10.3.12", | ||
"jest": "^29.7.0", | ||
"jest-canvas-mock": "^2.5.2", | ||
"jest-environment-jsdom": "^29.7.0", | ||
"rollup": "^4.9.6", | ||
"rollup": "^4.17.0", | ||
"rollup-plugin-preserve-shebang": "^1.0.1", | ||
"rollup-plugin-sourcemaps": "^0.6.3", | ||
"semver": "^7.5.4", | ||
@@ -92,4 +96,4 @@ "ts-jest": "^29.1.2", | ||
"tslib": "^2.6.2", | ||
"typescript": "^5.3.3" | ||
"typescript": "^5.4.5" | ||
} | ||
} |
@@ -16,3 +16,22 @@ # MapLibre Style Specification & Utilities | ||
## Documentation | ||
The [documentation](https://maplibre.org/maplibre-style-spec) of the style specification also lives in this repository. We use [MkDocs](https://www.mkdocs.org/) with the [Material for MkDocs](https://squidfunk.github.io/mkdocs-material) theme. | ||
To work on the documentation locally, you need to have Docker installed and running. Start MkDocs with | ||
``` | ||
npm run mkdocs | ||
``` | ||
Most of the documentation is generated (from e.g. `v8.json`). In another terminal, run: | ||
``` | ||
WATCH=1 npm run generate-docs | ||
``` | ||
This will re-run the generation script when needed. | ||
Note that generated files should not be checked in and they are excluded in `.gitignore`. Make sure to keep this file up-to-date and ignore generated files while making sure static Markdown files are not ignored. | ||
## NPM Package | ||
@@ -19,0 +38,0 @@ |
@@ -1,2 +0,13 @@ | ||
import {toString} from './types'; | ||
import {toString, | ||
NumberType, | ||
StringType, | ||
BooleanType, | ||
ColorType, | ||
ObjectType, | ||
ValueType, | ||
ErrorType, | ||
CollatorType, | ||
array, | ||
toString as typeToString, | ||
} from './types'; | ||
@@ -6,2 +17,3 @@ import ParsingContext from './parsing_context'; | ||
import {expressions} from './definitions/index'; | ||
import CollatorExpression from './definitions/collator'; | ||
@@ -13,7 +25,11 @@ import Within from './definitions/within'; | ||
import Var from './definitions/var'; | ||
import Distance from './definitions/distance'; | ||
import type {Expression, ExpressionRegistry} from './expression'; | ||
import type {Value} from './values'; | ||
import type {Type} from './types'; | ||
import type {Value} from './values'; | ||
import {typeOf, Color, validateRGBA, toString as valueToString} from './values'; | ||
import RuntimeError from './runtime_error'; | ||
export type Varargs = { | ||
@@ -158,2 +174,477 @@ type: Type; | ||
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, g / 255, b / 255, alpha, false); | ||
} | ||
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]) => { | ||
const [r, g, b, a] = v.evaluate(ctx).rgb; | ||
return [r * 255, g * 255, b * 255, a]; | ||
}, | ||
], | ||
'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 | ||
], | ||
'heatmap-density': [ | ||
NumberType, | ||
[], | ||
(ctx) => ctx.globals.heatmapDensity || 0 | ||
], | ||
'line-progress': [ | ||
NumberType, | ||
[], | ||
(ctx) => ctx.globals.lineProgress || 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 as any).value] === (v as any).value | ||
], | ||
'filter-id-==': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => ctx.id() === (v as any).value | ||
], | ||
'filter-type-==': [ | ||
BooleanType, | ||
[StringType], | ||
(ctx, [v]) => ctx.geometryType() === (v as any).value | ||
], | ||
'filter-<': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a < b; | ||
} | ||
], | ||
'filter-id-<': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a < b; | ||
} | ||
], | ||
'filter->': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a > b; | ||
} | ||
], | ||
'filter-id->': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a > b; | ||
} | ||
], | ||
'filter-<=': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a <= b; | ||
} | ||
], | ||
'filter-id-<=': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a <= b; | ||
} | ||
], | ||
'filter->=': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a >= b; | ||
} | ||
], | ||
'filter-id->=': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a >= b; | ||
} | ||
], | ||
'filter-has': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [k]) => (k as 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 as any).value.indexOf(ctx.geometryType()) >= 0 | ||
], | ||
'filter-id-in': [ | ||
BooleanType, | ||
[array(ValueType)], | ||
(ctx, [v]) => (v as any).value.indexOf(ctx.id()) >= 0 | ||
], | ||
'filter-in-small': [ | ||
BooleanType, | ||
[StringType, array(ValueType)], | ||
// assumes v is an array literal | ||
(ctx, [k, v]) => (v as any).value.indexOf(ctx.properties()[(k as 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 as any).value], (v as any).value, 0, (v as 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() | ||
] | ||
}); | ||
function stringifySignature(signature: Signature): string { | ||
@@ -179,2 +670,4 @@ if (Array.isArray(signature)) { | ||
return false; | ||
} else if (expression instanceof Distance) { | ||
return false; | ||
} | ||
@@ -231,2 +724,5 @@ | ||
} | ||
if (e instanceof Distance) { | ||
return false; | ||
} | ||
@@ -233,0 +729,0 @@ let result = true; |
@@ -1,19 +0,2 @@ | ||
import { | ||
NumberType, | ||
StringType, | ||
BooleanType, | ||
ColorType, | ||
ObjectType, | ||
ValueType, | ||
ErrorType, | ||
CollatorType, | ||
array, | ||
toString as typeToString, | ||
} from '../types'; | ||
import type {Type} from '../types'; | ||
import {typeOf, Color, validateRGBA, toString as valueToString} from '../values'; | ||
import CompoundExpression from '../compound_expression'; | ||
import RuntimeError from '../runtime_error'; | ||
import Let from './let'; | ||
@@ -47,7 +30,7 @@ import Var from './var'; | ||
import Within from './within'; | ||
import Distance from './distance'; | ||
import type {Varargs} from '../compound_expression'; | ||
import type {ExpressionRegistry} from '../expression'; | ||
const expressions: ExpressionRegistry = { | ||
export const expressions: ExpressionRegistry = { | ||
// special forms | ||
@@ -88,480 +71,6 @@ '==': Equals, | ||
'var': Var, | ||
'within': Within | ||
'within': Within, | ||
'distance': Distance | ||
}; | ||
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, g / 255, b / 255, alpha, false); | ||
} | ||
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]) => { | ||
const [r, g, b, a] = v.evaluate(ctx).rgb; | ||
return [r * 255, g * 255, b * 255, a]; | ||
}, | ||
], | ||
'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 | ||
], | ||
'heatmap-density': [ | ||
NumberType, | ||
[], | ||
(ctx) => ctx.globals.heatmapDensity || 0 | ||
], | ||
'line-progress': [ | ||
NumberType, | ||
[], | ||
(ctx) => ctx.globals.lineProgress || 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 as any).value] === (v as any).value | ||
], | ||
'filter-id-==': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => ctx.id() === (v as any).value | ||
], | ||
'filter-type-==': [ | ||
BooleanType, | ||
[StringType], | ||
(ctx, [v]) => ctx.geometryType() === (v as any).value | ||
], | ||
'filter-<': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a < b; | ||
} | ||
], | ||
'filter-id-<': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a < b; | ||
} | ||
], | ||
'filter->': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a > b; | ||
} | ||
], | ||
'filter-id->': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a > b; | ||
} | ||
], | ||
'filter-<=': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a <= b; | ||
} | ||
], | ||
'filter-id-<=': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a <= b; | ||
} | ||
], | ||
'filter->=': [ | ||
BooleanType, | ||
[StringType, ValueType], | ||
(ctx, [k, v]) => { | ||
const a = ctx.properties()[(k as any).value]; | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a >= b; | ||
} | ||
], | ||
'filter-id->=': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [v]) => { | ||
const a = ctx.id(); | ||
const b = (v as any).value; | ||
return typeof a === typeof b && a >= b; | ||
} | ||
], | ||
'filter-has': [ | ||
BooleanType, | ||
[ValueType], | ||
(ctx, [k]) => (k as 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 as any).value.indexOf(ctx.geometryType()) >= 0 | ||
], | ||
'filter-id-in': [ | ||
BooleanType, | ||
[array(ValueType)], | ||
(ctx, [v]) => (v as any).value.indexOf(ctx.id()) >= 0 | ||
], | ||
'filter-in-small': [ | ||
BooleanType, | ||
[StringType, array(ValueType)], | ||
// assumes v is an array literal | ||
(ctx, [k, v]) => (v as any).value.indexOf(ctx.properties()[(k as 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 as any).value], (v as any).value, 0, (v as 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; |
@@ -8,144 +8,8 @@ import {isValue} from '../values'; | ||
import {ICanonicalTileID} from '../../tiles_and_coordinates'; | ||
import {Position} from 'geojson'; | ||
import {BBox, EXTENT, boxWithinBox, getTileCoordinates, lineStringWithinPolygon, lineStringWithinPolygons, pointWithinPolygon, pointWithinPolygons, updateBBox} from '../../util/geometry_util'; | ||
import {Point2D} from '../../point2d'; | ||
type GeoJSONPolygons = GeoJSON.Polygon | GeoJSON.MultiPolygon; | ||
// 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: ICanonicalTileID): [number, number] { | ||
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, canonical) { | ||
function getTilePolygon(coordinates: GeoJSON.Position[][], bbox: BBox, canonical: ICanonicalTileID) { | ||
const polygon = []; | ||
@@ -164,3 +28,3 @@ for (let i = 0; i < coordinates.length; i++) { | ||
function getTilePolygons(coordinates, bbox, canonical) { | ||
function getTilePolygons(coordinates: GeoJSON.Position[][][], bbox: BBox, canonical: ICanonicalTileID) { | ||
const polygons = []; | ||
@@ -174,3 +38,3 @@ for (let i = 0; i < coordinates.length; i++) { | ||
function updatePoint(p, bbox, polyBBox, worldSize) { | ||
function updatePoint(p: GeoJSON.Position, bbox: BBox, polyBBox: BBox, worldSize: number) { | ||
if (p[0] < polyBBox[0] || p[0] > polyBBox[2]) { | ||
@@ -187,3 +51,3 @@ const halfWorldSize = worldSize * 0.5; | ||
function resetBBox(bbox) { | ||
function resetBBox(bbox: BBox) { | ||
bbox[0] = bbox[1] = Infinity; | ||
@@ -193,9 +57,9 @@ bbox[2] = bbox[3] = -Infinity; | ||
function getTilePoints(geometry, pointBBox, polyBBox, canonical) { | ||
function getTilePoints(geometry: Point2D[][], pointBBox: BBox, polyBBox: BBox, canonical: ICanonicalTileID): [number, number][] { | ||
const worldSize = Math.pow(2, canonical.z) * EXTENT; | ||
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; | ||
const tilePoints = []; | ||
const tilePoints: [number, number][] = []; | ||
for (const points of geometry) { | ||
for (const point of points) { | ||
const p = [point.x + shifts[0], point.y + shifts[1]]; | ||
const p: [number, number] = [point.x + shifts[0], point.y + shifts[1]]; | ||
updatePoint(p, pointBBox, polyBBox, worldSize); | ||
@@ -208,10 +72,10 @@ tilePoints.push(p); | ||
function getTileLines(geometry, lineBBox, polyBBox, canonical) { | ||
function getTileLines(geometry: Point2D[][], lineBBox: BBox, polyBBox: BBox, canonical: ICanonicalTileID): [number, number][][] { | ||
const worldSize = Math.pow(2, canonical.z) * EXTENT; | ||
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT]; | ||
const tileLines = []; | ||
const tileLines: [number, number][][] = []; | ||
for (const line of geometry) { | ||
const tileLine = []; | ||
const tileLine:[number, number][] = []; | ||
for (const point of line) { | ||
const p = [point.x + shifts[0], point.y + shifts[1]] as [number, number]; | ||
const p: [number, number] = [point.x + shifts[0], point.y + shifts[1]]; | ||
updateBBox(lineBBox, p); | ||
@@ -305,3 +169,3 @@ tileLine.push(p); | ||
if (geojson.type === 'FeatureCollection') { | ||
const polygonsCoords: Position[][][] = []; | ||
const polygonsCoords: GeoJSON.Position[][][] = []; | ||
for (const polygon of geojson.features) { | ||
@@ -308,0 +172,0 @@ const {type, coordinates} = polygon.geometry; |
import {createPropertyExpression, Feature, GlobalProperties, StylePropertyExpression} from '../expression'; | ||
import definitions from './definitions'; | ||
import v8 from '../reference/v8.json' assert {type: 'json'}; | ||
import {StylePropertySpecification} from '..'; | ||
import {createExpression, ICanonicalTileID, StyleExpression, StylePropertySpecification} from '..'; | ||
import ParsingError from './parsing_error'; | ||
import {VariableAnchorOffsetCollection} from './values'; | ||
import {getGeometry} from '../../test/lib/geometry'; | ||
@@ -87,3 +88,304 @@ // filter out interal "error" and "filter-*" expressions from definition list | ||
}); | ||
}); | ||
describe('Distance expression', () => { | ||
describe('Invalid expression', () => { | ||
test('missing geometry', () => { | ||
const response = createExpression(['distance']); | ||
expect(response.result).toBe('error'); | ||
}); | ||
test('invalid geometry', () => { | ||
const response = createExpression(['distance', {type: 'Nope!'}]); | ||
expect(response.result).toBe('error'); | ||
}); | ||
}); | ||
describe('valid expression', () => { | ||
test('multi point geometry', () => { | ||
const response = createExpression(['distance', {type: 'MultiPoint', coordinates: [[3, 3], [3, 4]]}]); | ||
expect(response.result).toBe('success'); | ||
}); | ||
test('multi line geometry', () => { | ||
const response = createExpression(['distance', {type: 'MultiLineString', coordinates: [[[3, 3], [3, 4]]]}]); | ||
expect(response.result).toBe('success'); | ||
}); | ||
test('multi polygon geometry', () => { | ||
const response = createExpression(['distance', {type: 'MultiPolygon', coordinates: [[[[3, 3], [3, 4], [4, 4], [4, 3], [3, 3]]]]}]); | ||
expect(response.result).toBe('success'); | ||
}); | ||
}); | ||
describe('Distance from point', () => { | ||
const featureInTile = {} as Feature; | ||
const canonical = {z: 20, x: 3, y: 3} as ICanonicalTileID; | ||
let value: StyleExpression; | ||
beforeEach(() => { | ||
const response = createExpression( | ||
['distance', {type: 'Point', coordinates: [3, 3]}], | ||
{ | ||
type: 'number', | ||
'property-type': 'data-constant', | ||
expression: { | ||
interpolated: false, | ||
parameters: ['zoom'] | ||
} | ||
} as StylePropertySpecification); | ||
value = response.value as StyleExpression; | ||
}); | ||
test('point to point in the same location', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('point to point in different location', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3.001]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('point to line', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3.001, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('point to line that passes through the point', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2, 3], [4, 3]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('point to line that pass through the point vertically', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[3, 2], [3, 4]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('point to containing polygon', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('point to near by polygon', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 2.999], [3, 2.999], [3, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('point to polygon with hole around the point', () => { | ||
// polygon inner ring direction is important! | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]], [[2.999, 2.999], [3.001, 2.999], [3.001, 3.001], [2.999, 3.001], [2.999, 2.999]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
}); | ||
describe('Distance from line', () => { | ||
const canonical = {z: 20, x: 3, y: 3} as ICanonicalTileID; | ||
const featureInTile = {} as Feature; | ||
let value: StyleExpression; | ||
beforeEach(() => { | ||
const response = createExpression( | ||
['distance', {type: 'LineString', coordinates: [[3, 3], [3, 4]]}], | ||
{ | ||
type: 'number', | ||
'property-type': 'data-constant', | ||
expression: { | ||
interpolated: false, | ||
parameters: ['zoom'] | ||
} | ||
} as StylePropertySpecification); | ||
value = response.value as StyleExpression; | ||
}); | ||
test('line to point that is on the line', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('line to point that is on the middle of the line', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3.5]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('line to point that is close by horizontally', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 2.999]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('line to point that is close by vertically', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3.001, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('line to line that are parallel', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[3.001, 3], [3.001, 4]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('line to line that are perpendicular', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2.5, 3.5], [3.5, 3.5]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('line to polygon that contains the line', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('line to polygon that is close by', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 2.999], [3, 2.999], [3, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('line to polygon with hole around the line', () => { | ||
// polygon inner ring direction is important! | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]], [[2.999, 2.999], [3.001, 2.999], [3.001, 4.001], [2.999, 4.001], [2.999, 2.999]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
}); | ||
describe('Distance from long line', () => { | ||
const canonical = {z: 20, x: 3, y: 3} as ICanonicalTileID; | ||
const featureInTile = {} as Feature; | ||
let value: StyleExpression; | ||
beforeEach(() => { | ||
const coordinates = []; | ||
for (let i = 0; i < 200; i++) { | ||
coordinates.push([3, 3 + i / 200.0]); | ||
} | ||
const response = createExpression( | ||
['distance', { | ||
type: 'FeatureCollection', | ||
features: [{ | ||
type: 'Feature', | ||
geometry: { | ||
type: 'LineString', coordinates | ||
} | ||
}] | ||
}], | ||
{ | ||
type: 'number', | ||
'property-type': 'data-constant', | ||
expression: { | ||
interpolated: false, | ||
parameters: ['zoom'] | ||
} | ||
} as StylePropertySpecification); | ||
value = response.value as StyleExpression; | ||
}); | ||
test('long line to multiple points close by', () => { | ||
const coordinates = []; | ||
for (let i = 0; i < 200; i++) { | ||
coordinates.push([2.999, 3 + i / 200.0]); | ||
} | ||
getGeometry(featureInTile, {type: 'MultiPoint', coordinates}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
}); | ||
describe('Distance from simple polygon', () => { | ||
const canonical = {z: 20, x: 3, y: 3} as ICanonicalTileID; | ||
const featureInTile = {} as Feature; | ||
let value: StyleExpression; | ||
let response: any; | ||
beforeEach(() => { | ||
response = createExpression( | ||
['distance', { | ||
type: 'Feature', | ||
geometry: { | ||
type: 'Polygon', | ||
coordinates: [[[3, 3], [3, 4], [4, 4], [4, 3], [3, 3]]] | ||
}, | ||
}], | ||
{ | ||
type: 'number', | ||
'property-type': 'data-constant', | ||
expression: { | ||
interpolated: false, | ||
parameters: ['zoom'] | ||
} | ||
} as StylePropertySpecification); | ||
value = response.value as StyleExpression; | ||
}); | ||
test('polygon to point that is on the edge of the the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0, 2); | ||
}); | ||
test('polygon to point that is inside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3.001, 3.001]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to point that is outside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [2.999, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('polygon to multiple points outside the polygon that require a better algorithm', () => { | ||
const coordinates = []; | ||
for (let i = 0; i < 200; i++) { | ||
coordinates.push([2.999, 3 - i / 1000.0]); | ||
} | ||
getGeometry(featureInTile, {type: 'MultiPoint', coordinates}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('polygon to multiple points outside and inside the polygon that require a better algorithm', () => { | ||
const coordinates = []; | ||
for (let i = 0; i < 200; i++) { | ||
coordinates.push([2.999, 3 - i / 1000.0]); | ||
} | ||
coordinates.push([3.001, 3.001]); | ||
getGeometry(featureInTile, {type: 'MultiPoint', coordinates}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to line that is outside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2.999, 3], [2.999, 4]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(111.1, 0); | ||
}); | ||
test('polygon to line that is passes through the polygon', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2.5, 3.5], [3.5, 3.5]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to polygon that is inside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to polygon that is outside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 2.999], [3, 2.999], [3, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('polygon to polygon that contains the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[3.5, 3.5], [3.5, 3.6], [3.6, 3.6], [3.6, 3.5], [3.5, 3.5]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to polygon with hole around the polygon', () => { | ||
// polygon with hole - direction is important! | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]], [[2.999, 2.999], [4.001, 2.999,], [4.001, 4.001], [2.999, 4.001], [2.999, 2.999]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
}); | ||
describe('Distance from polygon with hole', () => { | ||
const canonical = {z: 20, x: 3, y: 3} as ICanonicalTileID; | ||
const featureInTile = {} as Feature; | ||
let value: StyleExpression; | ||
beforeEach(() => { | ||
const response = createExpression( | ||
['distance', {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]], [[2.999, 2.999], [2.999, 4.001], [4.001, 4.001], [4.001, 2.999], [2.999, 2.999]]]}], | ||
{ | ||
type: 'number', | ||
'property-type': 'data-constant', | ||
expression: { | ||
interpolated: false, | ||
parameters: ['zoom'] | ||
} | ||
} as StylePropertySpecification); | ||
value = response.value as StyleExpression; | ||
}); | ||
test('polygon to point inside the hole', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [3, 3]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('polygon to point inside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [2.999, 2.999]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(0); | ||
}); | ||
test('polygon to line inside the hole', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[3, 3], [3, 4]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBeCloseTo(110.5, 0); | ||
}); | ||
test('polygon to line inside the polygon', () => { | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2.5, 3.5], [3.5, 3.5]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
test('polygon to polygon containing the polygon with the hole', () => { | ||
getGeometry(featureInTile, {type: 'Polygon', coordinates: [[[0, 0], [0, 5], [5, 5], [5, 0], [0, 0]]]}, canonical); | ||
expect(value.evaluate({zoom: 20}, featureInTile, {}, canonical)).toBe(0); | ||
}); | ||
}); | ||
}); |
@@ -7,3 +7,3 @@ import {default as createFilter, isExpressionFilter} from '.'; | ||
import {Feature} from '../expression'; | ||
import {getPointFromLngLat} from '../../test/lib/util'; | ||
import {getGeometry} from '../../test/lib/geometry'; | ||
@@ -157,24 +157,18 @@ describe('filter', () => { | ||
test('expression, within', () => { | ||
const withinFilter = createFilter(['within', {'type': 'Polygon', 'coordinates': [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]]}]); | ||
const withinFilter = createFilter(['within', {'type': 'Polygon', 'coordinates': [[[0, 0], [5, 0], [5, 5], [0, 5], [0, 0]]]}]); | ||
expect(withinFilter.needGeometry).toBe(true); | ||
const canonical = {z: 3, x: 3, y: 3} as ICanonicalTileID; | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(2, 2, canonical)]]} as Feature, canonical) | ||
).toBe(true); | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(6, 6, canonical)]]} as Feature, canonical) | ||
).toBe(false); | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 1, geometry: [[getPointFromLngLat(5, 5, canonical)]]} as Feature, canonical) | ||
).toBe(false); | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(2, 2, canonical), getPointFromLngLat(3, 3, canonical)]]} as Feature, canonical) | ||
).toBe(true); | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(6, 6, canonical), getPointFromLngLat(2, 2, canonical)]]} as Feature, canonical) | ||
).toBe(false); | ||
expect( | ||
withinFilter.filter({zoom: 3}, {type: 2, geometry: [[getPointFromLngLat(5, 5, canonical), getPointFromLngLat(2, 2, canonical)]]} as Feature, canonical) | ||
).toBe(false); | ||
const featureInTile = {} as Feature; | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [2, 2]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(true); | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [6, 6]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); | ||
getGeometry(featureInTile, {type: 'Point', coordinates: [5, 5]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[2, 2], [3, 3]]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(true); | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[6, 6], [2, 2]]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); | ||
getGeometry(featureInTile, {type: 'LineString', coordinates: [[5, 5], [2, 2]]}, canonical); | ||
expect(withinFilter.filter({zoom: 3}, featureInTile, canonical)).toBe(false); | ||
}); | ||
@@ -181,0 +175,0 @@ |
@@ -109,3 +109,3 @@ import {createExpression} from '../expression'; | ||
if (!Array.isArray(filter)) return false; | ||
if (filter[0] === 'within') return true; | ||
if (filter[0] === 'within' || filter[0] === 'distance') return true; | ||
for (let index = 1; index < filter.length; index++) { | ||
@@ -135,4 +135,3 @@ if (geometryNeeded(filter[index])) return true; | ||
op === '!has' ? convertNegation(convertHasOp(filter[1])) : | ||
op === 'within' ? filter : | ||
true; | ||
true; | ||
return converted; | ||
@@ -139,0 +138,0 @@ } |
@@ -97,3 +97,3 @@ type ExpressionType = 'data-driven' | 'cross-faded' | 'cross-faded-data-driven' | 'color-ramp' | 'data-constant' | 'constant'; | ||
import interpolates, {interpolateFactory} from './util/interpolate'; | ||
import expressions from './expression/definitions'; | ||
import {expressions} from './expression/definitions'; | ||
import Interpolate from './expression/definitions/interpolate'; | ||
@@ -114,2 +114,3 @@ import type {InterpolationType} from './expression/definitions/interpolate'; | ||
import migrate from './migrate'; | ||
import {classifyRings} from './util/classify_rings'; | ||
@@ -192,2 +193,3 @@ const expression = { | ||
migrate, | ||
classifyRings, | ||
@@ -194,0 +196,0 @@ ColorType, |
import spec from './v8.json' assert {type: 'json'}; | ||
import spec from './v8.json' assert { type: 'json' }; | ||
export default spec as any; |
@@ -83,2 +83,3 @@ // Generated code; do not edit. Edit build/generate-style-spec.ts instead. | ||
| ['within', unknown | ExpressionSpecification] | ||
| ['distance', unknown | ExpressionSpecification] | ||
// Ramps, scales, curves | ||
@@ -85,0 +86,0 @@ | ['interpolate', InterpolationSpecification, number | ExpressionSpecification, |
@@ -107,12 +107,4 @@ | ||
break; | ||
case 'within': | ||
type = getType(value[1]); | ||
if (value.length !== 2) { | ||
errors.push(new ValidationError(key, value, `filter array for "${value[0]}" operator must have 2 elements`)); | ||
} else if (type !== 'object') { | ||
errors.push(new ValidationError(`${key}[1]`, value[1], `object expected, ${type} found`)); | ||
} | ||
break; | ||
} | ||
return errors; | ||
} |
import ValidationError from '../error/validation_error'; | ||
import getType from '../util/get_type'; | ||
import validate from './validate'; | ||
import v8 from '../reference/v8.json' assert {type: 'json'}; | ||
@@ -31,3 +30,3 @@ import {SkySpecification, StyleSpecification} from '../types.g'; | ||
if (skySpec[key]) { | ||
errors = errors.concat(validate({ | ||
errors = errors.concat(options.validateSpec({ | ||
key, | ||
@@ -34,0 +33,0 @@ value: sky[key], |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9049200
32
159
95482
82
8
+ Addedquickselect@^2.0.0
+ Addedtinyqueue@^2.0.3
+ Addedquickselect@2.0.0(transitive)
+ Addedtinyqueue@2.0.3(transitive)