ngx-open-map-wrapper/node_modules/@maplibre/maplibre-gl-style-spec/dist/gl-style-validate.mjs

10977 lines
313 KiB
JavaScript
Executable File

#!/usr/bin/env node
import require$$0 from 'fs';
function getDefaultExportFromCjs (x) {
return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
}
var minimist$1;
var hasRequiredMinimist;
function requireMinimist () {
if (hasRequiredMinimist) return minimist$1;
hasRequiredMinimist = 1;
function hasKey(obj, keys) {
var o = obj;
keys.slice(0, -1).forEach(function (key) {
o = o[key] || {};
});
var key = keys[keys.length - 1];
return key in o;
}
function isNumber(x) {
if (typeof x === 'number') { return true; }
if ((/^0x[0-9a-f]+$/i).test(x)) { return true; }
return (/^[-+]?(?:\d+(?:\.\d*)?|\.\d+)(e[-+]?\d+)?$/).test(x);
}
function isConstructorOrProto(obj, key) {
return (key === 'constructor' && typeof obj[key] === 'function') || key === '__proto__';
}
minimist$1 = function (args, opts) {
if (!opts) { opts = {}; }
var flags = {
bools: {},
strings: {},
unknownFn: null,
};
if (typeof opts.unknown === 'function') {
flags.unknownFn = opts.unknown;
}
if (typeof opts.boolean === 'boolean' && opts.boolean) {
flags.allBools = true;
} else {
[].concat(opts.boolean).filter(Boolean).forEach(function (key) {
flags.bools[key] = true;
});
}
var aliases = {};
function aliasIsBoolean(key) {
return aliases[key].some(function (x) {
return flags.bools[x];
});
}
Object.keys(opts.alias || {}).forEach(function (key) {
aliases[key] = [].concat(opts.alias[key]);
aliases[key].forEach(function (x) {
aliases[x] = [key].concat(aliases[key].filter(function (y) {
return x !== y;
}));
});
});
[].concat(opts.string).filter(Boolean).forEach(function (key) {
flags.strings[key] = true;
if (aliases[key]) {
[].concat(aliases[key]).forEach(function (k) {
flags.strings[k] = true;
});
}
});
var defaults = opts.default || {};
var argv = { _: [] };
function argDefined(key, arg) {
return (flags.allBools && (/^--[^=]+$/).test(arg))
|| flags.strings[key]
|| flags.bools[key]
|| aliases[key];
}
function setKey(obj, keys, value) {
var o = obj;
for (var i = 0; i < keys.length - 1; i++) {
var key = keys[i];
if (isConstructorOrProto(o, key)) { return; }
if (o[key] === undefined) { o[key] = {}; }
if (
o[key] === Object.prototype
|| o[key] === Number.prototype
|| o[key] === String.prototype
) {
o[key] = {};
}
if (o[key] === Array.prototype) { o[key] = []; }
o = o[key];
}
var lastKey = keys[keys.length - 1];
if (isConstructorOrProto(o, lastKey)) { return; }
if (
o === Object.prototype
|| o === Number.prototype
|| o === String.prototype
) {
o = {};
}
if (o === Array.prototype) { o = []; }
if (o[lastKey] === undefined || flags.bools[lastKey] || typeof o[lastKey] === 'boolean') {
o[lastKey] = value;
} else if (Array.isArray(o[lastKey])) {
o[lastKey].push(value);
} else {
o[lastKey] = [o[lastKey], value];
}
}
function setArg(key, val, arg) {
if (arg && flags.unknownFn && !argDefined(key, arg)) {
if (flags.unknownFn(arg) === false) { return; }
}
var value = !flags.strings[key] && isNumber(val)
? Number(val)
: val;
setKey(argv, key.split('.'), value);
(aliases[key] || []).forEach(function (x) {
setKey(argv, x.split('.'), value);
});
}
Object.keys(flags.bools).forEach(function (key) {
setArg(key, defaults[key] === undefined ? false : defaults[key]);
});
var notFlags = [];
if (args.indexOf('--') !== -1) {
notFlags = args.slice(args.indexOf('--') + 1);
args = args.slice(0, args.indexOf('--'));
}
for (var i = 0; i < args.length; i++) {
var arg = args[i];
var key;
var next;
if ((/^--.+=/).test(arg)) {
// Using [\s\S] instead of . because js doesn't support the
// 'dotall' regex modifier. See:
// http://stackoverflow.com/a/1068308/13216
var m = arg.match(/^--([^=]+)=([\s\S]*)$/);
key = m[1];
var value = m[2];
if (flags.bools[key]) {
value = value !== 'false';
}
setArg(key, value, arg);
} else if ((/^--no-.+/).test(arg)) {
key = arg.match(/^--no-(.+)/)[1];
setArg(key, false, arg);
} else if ((/^--.+/).test(arg)) {
key = arg.match(/^--(.+)/)[1];
next = args[i + 1];
if (
next !== undefined
&& !(/^(-|--)[^-]/).test(next)
&& !flags.bools[key]
&& !flags.allBools
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, next, arg);
i += 1;
} else if ((/^(true|false)$/).test(next)) {
setArg(key, next === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
} else if ((/^-[^-]+/).test(arg)) {
var letters = arg.slice(1, -1).split('');
var broken = false;
for (var j = 0; j < letters.length; j++) {
next = arg.slice(j + 2);
if (next === '-') {
setArg(letters[j], next, arg);
continue;
}
if ((/[A-Za-z]/).test(letters[j]) && next[0] === '=') {
setArg(letters[j], next.slice(1), arg);
broken = true;
break;
}
if (
(/[A-Za-z]/).test(letters[j])
&& (/-?\d+(\.\d*)?(e-?\d+)?$/).test(next)
) {
setArg(letters[j], next, arg);
broken = true;
break;
}
if (letters[j + 1] && letters[j + 1].match(/\W/)) {
setArg(letters[j], arg.slice(j + 2), arg);
broken = true;
break;
} else {
setArg(letters[j], flags.strings[letters[j]] ? '' : true, arg);
}
}
key = arg.slice(-1)[0];
if (!broken && key !== '-') {
if (
args[i + 1]
&& !(/^(-|--)[^-]/).test(args[i + 1])
&& !flags.bools[key]
&& (aliases[key] ? !aliasIsBoolean(key) : true)
) {
setArg(key, args[i + 1], arg);
i += 1;
} else if (args[i + 1] && (/^(true|false)$/).test(args[i + 1])) {
setArg(key, args[i + 1] === 'true', arg);
i += 1;
} else {
setArg(key, flags.strings[key] ? '' : true, arg);
}
}
} else {
if (!flags.unknownFn || flags.unknownFn(arg) !== false) {
argv._.push(flags.strings._ || !isNumber(arg) ? arg : Number(arg));
}
if (opts.stopEarly) {
argv._.push.apply(argv._, args.slice(i + 1));
break;
}
}
}
Object.keys(defaults).forEach(function (k) {
if (!hasKey(argv, k.split('.'))) {
setKey(argv, k.split('.'), defaults[k]);
(aliases[k] || []).forEach(function (x) {
setKey(argv, x.split('.'), defaults[k]);
});
}
});
if (opts['--']) {
argv['--'] = notFlags.slice();
} else {
notFlags.forEach(function (k) {
argv._.push(k);
});
}
return argv;
};
return minimist$1;
}
var minimistExports = requireMinimist();
var minimist = /*@__PURE__*/getDefaultExportFromCjs(minimistExports);
var rw$1 = {};
var dash = {};
var decode;
var hasRequiredDecode;
function requireDecode () {
if (hasRequiredDecode) return decode;
hasRequiredDecode = 1;
decode = function(options) {
if (options) {
if (typeof options === "string") return encoding(options);
if (options.encoding !== null) return encoding(options.encoding);
}
return identity();
};
function identity() {
var chunks = [];
return {
push: function(chunk) { chunks.push(chunk); },
value: function() { return Buffer.concat(chunks); }
};
}
function encoding(encoding) {
var chunks = [];
return {
push: function(chunk) { chunks.push(chunk); },
value: function() { return Buffer.concat(chunks).toString(encoding); }
};
}
return decode;
}
var readFile;
var hasRequiredReadFile;
function requireReadFile () {
if (hasRequiredReadFile) return readFile;
hasRequiredReadFile = 1;
var fs = require$$0,
decode = requireDecode();
readFile = function(path, options, callback) {
if (arguments.length < 3) callback = options, options = null;
switch (path) {
case "/dev/stdin": return readStream(process.stdin, options, callback);
}
fs.stat(path, function(error, stat) {
if (error) return callback(error);
if (stat.isFile()) return fs.readFile(path, options, callback);
readStream(fs.createReadStream(path, options ? {flags: options.flag || "r"} : {}), options, callback); // N.B. flag / flags
});
};
function readStream(stream, options, callback) {
var decoder = decode(options);
stream.on("error", callback);
stream.on("data", function(d) { decoder.push(d); });
stream.on("end", function() { callback(null, decoder.value()); });
}
return readFile;
}
var readFileSync;
var hasRequiredReadFileSync;
function requireReadFileSync () {
if (hasRequiredReadFileSync) return readFileSync;
hasRequiredReadFileSync = 1;
var fs = require$$0,
decode = requireDecode();
readFileSync = function(filename, options) {
if (fs.statSync(filename).isFile()) {
return fs.readFileSync(filename, options);
} else {
var fd = fs.openSync(filename, options && options.flag || "r"),
decoder = decode(options);
while (true) { // eslint-disable-line no-constant-condition
try {
var buffer = new Buffer(bufferSize),
bytesRead = fs.readSync(fd, buffer, 0, bufferSize);
} catch (e) {
if (e.code === "EOF") break;
fs.closeSync(fd);
throw e;
}
if (bytesRead === 0) break;
decoder.push(buffer.slice(0, bytesRead));
}
fs.closeSync(fd);
return decoder.value();
}
};
var bufferSize = 1 << 16;
return readFileSync;
}
var encode;
var hasRequiredEncode;
function requireEncode () {
if (hasRequiredEncode) return encode;
hasRequiredEncode = 1;
encode = function(data, options) {
return typeof data === "string"
? new Buffer(data, typeof options === "string" ? options
: options && options.encoding !== null ? options.encoding
: "utf8")
: data;
};
return encode;
}
var writeFile;
var hasRequiredWriteFile;
function requireWriteFile () {
if (hasRequiredWriteFile) return writeFile;
hasRequiredWriteFile = 1;
var fs = require$$0,
encode = requireEncode();
writeFile = function(path, data, options, callback) {
if (arguments.length < 4) callback = options, options = null;
switch (path) {
case "/dev/stdout": return writeStream(process.stdout, "write", data, options, callback);
case "/dev/stderr": return writeStream(process.stderr, "write", data, options, callback);
}
fs.stat(path, function(error, stat) {
if (error && error.code !== "ENOENT") return callback(error);
if (stat && stat.isFile()) return fs.writeFile(path, data, options, callback);
writeStream(fs.createWriteStream(path, options ? {flags: options.flag || "w"} : {}), "end", data, options, callback); // N.B. flag / flags
});
};
function writeStream(stream, send, data, options, callback) {
stream.on("error", function(error) { callback(error.code === "EPIPE" ? null : error); }); // ignore broken pipe, e.g., | head
stream[send](encode(data, options), function(error) { callback(error && error.code === "EPIPE" ? null : error); });
}
return writeFile;
}
var writeFileSync;
var hasRequiredWriteFileSync;
function requireWriteFileSync () {
if (hasRequiredWriteFileSync) return writeFileSync;
hasRequiredWriteFileSync = 1;
var fs = require$$0,
encode = requireEncode();
writeFileSync = function(filename, data, options) {
var stat;
try {
stat = fs.statSync(filename);
} catch (error) {
if (error.code !== "ENOENT") throw error;
}
if (!stat || stat.isFile()) {
fs.writeFileSync(filename, data, options);
} else {
var fd = fs.openSync(filename, options && options.flag || "w"),
bytesWritten = 0,
bytesTotal = (data = encode(data, options)).length;
while (bytesWritten < bytesTotal) {
try {
bytesWritten += fs.writeSync(fd, data, bytesWritten, bytesTotal - bytesWritten, null);
} catch (error) {
if (error.code === "EPIPE") break; // ignore broken pipe, e.g., | head
fs.closeSync(fd);
throw error;
}
}
fs.closeSync(fd);
}
};
return writeFileSync;
}
var hasRequiredDash;
function requireDash () {
if (hasRequiredDash) return dash;
hasRequiredDash = 1;
var slice = Array.prototype.slice;
function dashify(method, file) {
return function(path) {
var argv = arguments;
if (path == "-") (argv = slice.call(argv)).splice(0, 1, file);
return method.apply(null, argv);
};
}
dash.readFile = dashify(requireReadFile(), "/dev/stdin");
dash.readFileSync = dashify(requireReadFileSync(), "/dev/stdin");
dash.writeFile = dashify(requireWriteFile(), "/dev/stdout");
dash.writeFileSync = dashify(requireWriteFileSync(), "/dev/stdout");
return dash;
}
var hasRequiredRw;
function requireRw () {
if (hasRequiredRw) return rw$1;
hasRequiredRw = 1;
rw$1.dash = requireDash();
rw$1.readFile = requireReadFile();
rw$1.readFileSync = requireReadFileSync();
rw$1.writeFile = requireWriteFile();
rw$1.writeFileSync = requireWriteFileSync();
return rw$1;
}
var rwExports = requireRw();
var rw = /*@__PURE__*/getDefaultExportFromCjs(rwExports);
// Note: Do not inherit from Error. It breaks when transpiling to ES5.
class ValidationError {
constructor(key, value, message, identifier) {
this.message = (key ? `${key}: ` : '') + message;
if (identifier)
this.identifier = identifier;
if (value !== null && value !== undefined && value.__line__) {
this.line = value.__line__;
}
}
}
function validateConstants(options) {
const key = options.key;
const constants = options.value;
if (constants) {
return [new ValidationError(key, constants, 'constants have been deprecated as of v8')];
}
else {
return [];
}
}
function extendBy(output, ...inputs) {
for (const input of inputs) {
for (const k in input) {
output[k] = input[k];
}
}
return output;
}
// Turn jsonlint-lines-primitives objects into primitive objects
function unbundle(value) {
if (value instanceof Number || value instanceof String || value instanceof Boolean) {
return value.valueOf();
}
else {
return value;
}
}
function deepUnbundle(value) {
if (Array.isArray(value)) {
return value.map(deepUnbundle);
}
else if (value instanceof Object && !(value instanceof Number || value instanceof String || value instanceof Boolean)) {
const unbundledValue = {};
for (const key in value) {
unbundledValue[key] = deepUnbundle(value[key]);
}
return unbundledValue;
}
return unbundle(value);
}
class ExpressionParsingError extends Error {
constructor(key, message) {
super(message);
this.message = message;
this.key = key;
}
}
/**
* Tracks `let` bindings during expression parsing.
* @private
*/
class Scope {
constructor(parent, bindings = []) {
this.parent = parent;
this.bindings = {};
for (const [name, expression] of bindings) {
this.bindings[name] = expression;
}
}
concat(bindings) {
return new Scope(this, bindings);
}
get(name) {
if (this.bindings[name]) {
return this.bindings[name];
}
if (this.parent) {
return this.parent.get(name);
}
throw new Error(`${name} not found in scope.`);
}
has(name) {
if (this.bindings[name])
return true;
return this.parent ? this.parent.has(name) : false;
}
}
const NullType = { kind: 'null' };
const NumberType = { kind: 'number' };
const StringType = { kind: 'string' };
const BooleanType = { kind: 'boolean' };
const ColorType = { kind: 'color' };
const ProjectionDefinitionType = { kind: 'projectionDefinition' };
const ObjectType = { kind: 'object' };
const ValueType = { kind: 'value' };
const ErrorType = { kind: 'error' };
const CollatorType = { kind: 'collator' };
const FormattedType = { kind: 'formatted' };
const PaddingType = { kind: 'padding' };
const ColorArrayType = { kind: 'colorArray' };
const NumberArrayType = { kind: 'numberArray' };
const ResolvedImageType = { kind: 'resolvedImage' };
const VariableAnchorOffsetCollectionType = { kind: 'variableAnchorOffsetCollection' };
function array(itemType, N) {
return {
kind: 'array',
itemType,
N
};
}
function typeToString(type) {
if (type.kind === 'array') {
const itemType = typeToString(type.itemType);
return typeof type.N === 'number' ?
`array<${itemType}, ${type.N}>` :
type.itemType.kind === 'value' ? 'array' : `array<${itemType}>`;
}
else {
return type.kind;
}
}
const valueMemberTypes = [
NullType,
NumberType,
StringType,
BooleanType,
ColorType,
ProjectionDefinitionType,
FormattedType,
ObjectType,
array(ValueType),
PaddingType,
NumberArrayType,
ColorArrayType,
ResolvedImageType,
VariableAnchorOffsetCollectionType
];
/**
* Returns null if `t` is a subtype of `expected`; otherwise returns an
* error message.
* @private
*/
function checkSubtype(expected, t) {
if (t.kind === 'error') {
// Error is a subtype of every type
return null;
}
else if (expected.kind === 'array') {
if (t.kind === 'array' &&
((t.N === 0 && t.itemType.kind === 'value') || !checkSubtype(expected.itemType, t.itemType)) &&
(typeof expected.N !== 'number' || expected.N === t.N)) {
return null;
}
}
else if (expected.kind === t.kind) {
return null;
}
else if (expected.kind === 'value') {
for (const memberType of valueMemberTypes) {
if (!checkSubtype(memberType, t)) {
return null;
}
}
}
return `Expected ${typeToString(expected)} but found ${typeToString(t)} instead.`;
}
function isValidType(provided, allowedTypes) {
return allowedTypes.some(t => t.kind === provided.kind);
}
function isValidNativeType(provided, allowedTypes) {
return allowedTypes.some(t => {
if (t === 'null') {
return provided === null;
}
else if (t === 'array') {
return Array.isArray(provided);
}
else if (t === 'object') {
return provided && !Array.isArray(provided) && typeof provided === 'object';
}
else {
return t === typeof provided;
}
});
}
/**
* Verify whether the specified type is of the same type as the specified sample.
*
* @param provided Type to verify
* @param sample Sample type to reference
* @returns `true` if both objects are of the same type, `false` otherwise
* @example basic types
* if (verifyType(outputType, ValueType)) {
* // type narrowed to:
* outputType.kind; // 'value'
* }
* @example array types
* if (verifyType(outputType, array(NumberType))) {
* // type narrowed to:
* outputType.kind; // 'array'
* outputType.itemType; // NumberTypeT
* outputType.itemType.kind; // 'number'
* }
*/
function verifyType(provided, sample) {
if (provided.kind === 'array' && sample.kind === 'array') {
return provided.itemType.kind === sample.itemType.kind && typeof provided.N === 'number';
}
return provided.kind === sample.kind;
}
// See https://observablehq.com/@mbostock/lab-and-rgb
const Xn = 0.96422, Yn = 1, Zn = 0.82521, t0 = 4 / 29, t1 = 6 / 29, t2 = 3 * t1 * t1, t3 = t1 * t1 * t1, deg2rad = Math.PI / 180, rad2deg = 180 / Math.PI;
function constrainAngle(angle) {
angle = angle % 360;
if (angle < 0) {
angle += 360;
}
return angle;
}
function rgbToLab([r, g, b, alpha]) {
r = rgb2xyz(r);
g = rgb2xyz(g);
b = rgb2xyz(b);
let x, z;
const y = xyz2lab((0.2225045 * r + 0.7168786 * g + 0.0606169 * b) / Yn);
if (r === g && g === b) {
x = z = y;
}
else {
x = xyz2lab((0.4360747 * r + 0.3850649 * g + 0.1430804 * b) / Xn);
z = xyz2lab((0.0139322 * r + 0.0971045 * g + 0.7141733 * b) / Zn);
}
const l = 116 * y - 16;
return [(l < 0) ? 0 : l, 500 * (x - y), 200 * (y - z), alpha];
}
function rgb2xyz(x) {
return (x <= 0.04045) ? x / 12.92 : Math.pow((x + 0.055) / 1.055, 2.4);
}
function xyz2lab(t) {
return (t > t3) ? Math.pow(t, 1 / 3) : t / t2 + t0;
}
function labToRgb([l, a, b, alpha]) {
let y = (l + 16) / 116, x = isNaN(a) ? y : y + a / 500, z = isNaN(b) ? y : y - b / 200;
y = Yn * lab2xyz(y);
x = Xn * lab2xyz(x);
z = Zn * lab2xyz(z);
return [
xyz2rgb(3.1338561 * x - 1.6168667 * y - 0.4906146 * z), // D50 -> sRGB
xyz2rgb(-0.9787684 * x + 1.9161415 * y + 0.0334540 * z),
xyz2rgb(0.0719453 * x - 0.2289914 * y + 1.4052427 * z),
alpha,
];
}
function xyz2rgb(x) {
x = (x <= 0.00304) ? 12.92 * x : 1.055 * Math.pow(x, 1 / 2.4) - 0.055;
return (x < 0) ? 0 : (x > 1) ? 1 : x; // clip to 0..1 range
}
function lab2xyz(t) {
return (t > t1) ? t * t * t : t2 * (t - t0);
}
function rgbToHcl(rgbColor) {
const [l, a, b, alpha] = rgbToLab(rgbColor);
const c = Math.sqrt(a * a + b * b);
const h = Math.round(c * 10000) ? constrainAngle(Math.atan2(b, a) * rad2deg) : NaN;
return [h, c, l, alpha];
}
function hclToRgb([h, c, l, alpha]) {
h = isNaN(h) ? 0 : h * deg2rad;
return labToRgb([l, Math.cos(h) * c, Math.sin(h) * c, alpha]);
}
// https://drafts.csswg.org/css-color-4/#hsl-to-rgb
function hslToRgb([h, s, l, alpha]) {
h = constrainAngle(h);
s /= 100;
l /= 100;
function f(n) {
const k = (n + h / 30) % 12;
const a = s * Math.min(l, 1 - l);
return l - a * Math.max(-1, Math.min(k - 3, 9 - k, 1));
}
return [f(0), f(8), f(4), alpha];
}
// polyfill for Object.hasOwn
const hasOwnProperty = Object.hasOwn ||
function hasOwnProperty(object, key) {
return Object.prototype.hasOwnProperty.call(object, key);
};
function getOwn(object, key) {
return hasOwnProperty(object, key) ? object[key] : undefined;
}
/**
* CSS color parser compliant with CSS Color 4 Specification.
* Supports: named colors, `transparent` keyword, all rgb hex notations,
* rgb(), rgba(), hsl() and hsla() functions.
* Does not round the parsed values to integers from the range 0..255.
*
* Syntax:
*
* <alpha-value> = <number> | <percentage>
* <hue> = <number> | <angle>
*
* rgb() = rgb( <percentage>{3} [ / <alpha-value> ]? ) | rgb( <number>{3} [ / <alpha-value> ]? )
* rgb() = rgb( <percentage>#{3} , <alpha-value>? ) | rgb( <number>#{3} , <alpha-value>? )
*
* hsl() = hsl( <hue> <percentage> <percentage> [ / <alpha-value> ]? )
* hsl() = hsl( <hue>, <percentage>, <percentage>, <alpha-value>? )
*
* Caveats:
* - <angle> - <number> with optional `deg` suffix; `grad`, `rad`, `turn` are not supported
* - `none` keyword is not supported
* - comments inside rgb()/hsl() are not supported
* - legacy color syntax rgba() is supported with an identical grammar and behavior to rgb()
* - legacy color syntax hsla() is supported with an identical grammar and behavior to hsl()
*
* @param input CSS color string to parse.
* @returns Color in sRGB color space, with `red`, `green`, `blue`
* and `alpha` channels normalized to the range 0..1,
* or `undefined` if the input is not a valid color string.
*/
function parseCssColor(input) {
input = input.toLowerCase().trim();
if (input === 'transparent') {
return [0, 0, 0, 0];
}
// 'white', 'black', 'blue'
const namedColorsMatch = getOwn(namedColors, input);
if (namedColorsMatch) {
const [r, g, b] = namedColorsMatch;
return [r / 255, g / 255, b / 255, 1];
}
// #f0c, #f0cf, #ff00cc, #ff00ccff
if (input.startsWith('#')) {
const hexRegexp = /^#(?:[0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/;
if (hexRegexp.test(input)) {
const step = input.length < 6 ? 1 : 2;
let i = 1;
return [
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i += step)),
parseHex(input.slice(i, i + step) || 'ff'),
];
}
}
// rgb(128 0 0), rgb(50% 0% 0%), rgba(255,0,255,0.6), rgb(255 0 255 / 60%), rgb(100% 0% 100% /.6)
if (input.startsWith('rgb')) {
const rgbRegExp = /^rgba?\(\s*([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s+|\s*(,)\s*)([\de.+-]+)(%)?(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
const rgbMatch = input.match(rgbRegExp);
if (rgbMatch) {
const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
r, // <numeric>
rp, // % (optional)
f1, // , (optional)
g, // <numeric>
gp, // % (optional)
f2, // , (optional)
b, // <numeric>
bp, // % (optional)
f3, // ,|/ (optional)
a, // <numeric> (optional)
ap, // % (optional)
] = rgbMatch;
const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
if (argFormat === ' ' ||
argFormat === ' /' ||
argFormat === ',,' ||
argFormat === ',,,') {
const valFormat = [rp, gp, bp].join('');
const maxValue = (valFormat === '%%%') ? 100 :
(valFormat === '') ? 255 : 0;
if (maxValue) {
const rgba = [
clamp(+r / maxValue, 0, 1),
clamp(+g / maxValue, 0, 1),
clamp(+b / maxValue, 0, 1),
a ? parseAlpha(+a, ap) : 1,
];
if (validateNumbers(rgba)) {
return rgba;
}
// invalid numbers
}
// values must be all numbers or all percentages
}
return; // comma optional syntax requires no commas at all
}
}
// hsl(120 50% 80%), hsla(120deg,50%,80%,.9), hsl(12e1 50% 80% / 90%)
const hslRegExp = /^hsla?\(\s*([\de.+-]+)(?:deg)?(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s+|\s*(,)\s*)([\de.+-]+)%(?:\s*([,\/])\s*([\de.+-]+)(%)?)?\s*\)$/;
const hslMatch = input.match(hslRegExp);
if (hslMatch) {
const [_, // eslint-disable-line @typescript-eslint/no-unused-vars
h, // <numeric>
f1, // , (optional)
s, // <numeric>
f2, // , (optional)
l, // <numeric>
f3, // ,|/ (optional)
a, // <numeric> (optional)
ap, // % (optional)
] = hslMatch;
const argFormat = [f1 || ' ', f2 || ' ', f3].join('');
if (argFormat === ' ' ||
argFormat === ' /' ||
argFormat === ',,' ||
argFormat === ',,,') {
const hsla = [
+h,
clamp(+s, 0, 100),
clamp(+l, 0, 100),
a ? parseAlpha(+a, ap) : 1,
];
if (validateNumbers(hsla)) {
return hslToRgb(hsla);
}
// invalid numbers
}
// comma optional syntax requires no commas at all
}
}
function parseHex(hex) {
return parseInt(hex.padEnd(2, hex), 16) / 255;
}
function parseAlpha(a, asPercentage) {
return clamp(asPercentage ? (a / 100) : a, 0, 1);
}
function clamp(n, min, max) {
return Math.min(Math.max(min, n), max);
}
/**
* The regular expression for numeric values is not super specific, and it may
* happen that it will accept a value that is not a valid number. In order to
* detect and eliminate such values this function exists.
*
* @param array Array of uncertain numbers.
* @returns `true` if the specified array contains only valid numbers, `false` otherwise.
*/
function validateNumbers(array) {
return !array.some(Number.isNaN);
}
/**
* To generate:
* - visit {@link https://www.w3.org/TR/css-color-4/#named-colors}
* - run in the console:
* @example
* copy(`{\n${[...document.querySelector('.named-color-table tbody').children].map((tr) => `${tr.cells[2].textContent.trim()}: [${tr.cells[4].textContent.trim().split(/\s+/).join(', ')}],`).join('\n')}\n}`);
*/
const namedColors = {
aliceblue: [240, 248, 255],
antiquewhite: [250, 235, 215],
aqua: [0, 255, 255],
aquamarine: [127, 255, 212],
azure: [240, 255, 255],
beige: [245, 245, 220],
bisque: [255, 228, 196],
black: [0, 0, 0],
blanchedalmond: [255, 235, 205],
blue: [0, 0, 255],
blueviolet: [138, 43, 226],
brown: [165, 42, 42],
burlywood: [222, 184, 135],
cadetblue: [95, 158, 160],
chartreuse: [127, 255, 0],
chocolate: [210, 105, 30],
coral: [255, 127, 80],
cornflowerblue: [100, 149, 237],
cornsilk: [255, 248, 220],
crimson: [220, 20, 60],
cyan: [0, 255, 255],
darkblue: [0, 0, 139],
darkcyan: [0, 139, 139],
darkgoldenrod: [184, 134, 11],
darkgray: [169, 169, 169],
darkgreen: [0, 100, 0],
darkgrey: [169, 169, 169],
darkkhaki: [189, 183, 107],
darkmagenta: [139, 0, 139],
darkolivegreen: [85, 107, 47],
darkorange: [255, 140, 0],
darkorchid: [153, 50, 204],
darkred: [139, 0, 0],
darksalmon: [233, 150, 122],
darkseagreen: [143, 188, 143],
darkslateblue: [72, 61, 139],
darkslategray: [47, 79, 79],
darkslategrey: [47, 79, 79],
darkturquoise: [0, 206, 209],
darkviolet: [148, 0, 211],
deeppink: [255, 20, 147],
deepskyblue: [0, 191, 255],
dimgray: [105, 105, 105],
dimgrey: [105, 105, 105],
dodgerblue: [30, 144, 255],
firebrick: [178, 34, 34],
floralwhite: [255, 250, 240],
forestgreen: [34, 139, 34],
fuchsia: [255, 0, 255],
gainsboro: [220, 220, 220],
ghostwhite: [248, 248, 255],
gold: [255, 215, 0],
goldenrod: [218, 165, 32],
gray: [128, 128, 128],
green: [0, 128, 0],
greenyellow: [173, 255, 47],
grey: [128, 128, 128],
honeydew: [240, 255, 240],
hotpink: [255, 105, 180],
indianred: [205, 92, 92],
indigo: [75, 0, 130],
ivory: [255, 255, 240],
khaki: [240, 230, 140],
lavender: [230, 230, 250],
lavenderblush: [255, 240, 245],
lawngreen: [124, 252, 0],
lemonchiffon: [255, 250, 205],
lightblue: [173, 216, 230],
lightcoral: [240, 128, 128],
lightcyan: [224, 255, 255],
lightgoldenrodyellow: [250, 250, 210],
lightgray: [211, 211, 211],
lightgreen: [144, 238, 144],
lightgrey: [211, 211, 211],
lightpink: [255, 182, 193],
lightsalmon: [255, 160, 122],
lightseagreen: [32, 178, 170],
lightskyblue: [135, 206, 250],
lightslategray: [119, 136, 153],
lightslategrey: [119, 136, 153],
lightsteelblue: [176, 196, 222],
lightyellow: [255, 255, 224],
lime: [0, 255, 0],
limegreen: [50, 205, 50],
linen: [250, 240, 230],
magenta: [255, 0, 255],
maroon: [128, 0, 0],
mediumaquamarine: [102, 205, 170],
mediumblue: [0, 0, 205],
mediumorchid: [186, 85, 211],
mediumpurple: [147, 112, 219],
mediumseagreen: [60, 179, 113],
mediumslateblue: [123, 104, 238],
mediumspringgreen: [0, 250, 154],
mediumturquoise: [72, 209, 204],
mediumvioletred: [199, 21, 133],
midnightblue: [25, 25, 112],
mintcream: [245, 255, 250],
mistyrose: [255, 228, 225],
moccasin: [255, 228, 181],
navajowhite: [255, 222, 173],
navy: [0, 0, 128],
oldlace: [253, 245, 230],
olive: [128, 128, 0],
olivedrab: [107, 142, 35],
orange: [255, 165, 0],
orangered: [255, 69, 0],
orchid: [218, 112, 214],
palegoldenrod: [238, 232, 170],
palegreen: [152, 251, 152],
paleturquoise: [175, 238, 238],
palevioletred: [219, 112, 147],
papayawhip: [255, 239, 213],
peachpuff: [255, 218, 185],
peru: [205, 133, 63],
pink: [255, 192, 203],
plum: [221, 160, 221],
powderblue: [176, 224, 230],
purple: [128, 0, 128],
rebeccapurple: [102, 51, 153],
red: [255, 0, 0],
rosybrown: [188, 143, 143],
royalblue: [65, 105, 225],
saddlebrown: [139, 69, 19],
salmon: [250, 128, 114],
sandybrown: [244, 164, 96],
seagreen: [46, 139, 87],
seashell: [255, 245, 238],
sienna: [160, 82, 45],
silver: [192, 192, 192],
skyblue: [135, 206, 235],
slateblue: [106, 90, 205],
slategray: [112, 128, 144],
slategrey: [112, 128, 144],
snow: [255, 250, 250],
springgreen: [0, 255, 127],
steelblue: [70, 130, 180],
tan: [210, 180, 140],
teal: [0, 128, 128],
thistle: [216, 191, 216],
tomato: [255, 99, 71],
turquoise: [64, 224, 208],
violet: [238, 130, 238],
wheat: [245, 222, 179],
white: [255, 255, 255],
whitesmoke: [245, 245, 245],
yellow: [255, 255, 0],
yellowgreen: [154, 205, 50],
};
function interpolateNumber(from, to, t) {
return from + t * (to - from);
}
function interpolateArray(from, to, t) {
return from.map((d, i) => {
return interpolateNumber(d, to[i], t);
});
}
/**
* Color representation used by WebGL.
* Defined in sRGB color space and pre-blended with alpha.
* @private
*/
class Color {
/**
* @param r Red component premultiplied by `alpha` 0..1
* @param g Green component premultiplied by `alpha` 0..1
* @param b Blue component premultiplied by `alpha` 0..1
* @param [alpha=1] Alpha component 0..1
* @param [premultiplied=true] Whether the `r`, `g` and `b` values have already
* been multiplied by alpha. If `true` nothing happens if `false` then they will
* be multiplied automatically.
*/
constructor(r, g, b, alpha = 1, premultiplied = true) {
this.r = r;
this.g = g;
this.b = b;
this.a = alpha;
if (!premultiplied) {
this.r *= alpha;
this.g *= alpha;
this.b *= alpha;
if (!alpha) {
// alpha = 0 erases completely rgb channels. This behavior is not desirable
// if this particular color is later used in color interpolation.
// Because of that, a reference to original color is saved.
this.overwriteGetter('rgb', [r, g, b, alpha]);
}
}
}
/**
* Parses CSS color strings and converts colors to sRGB color space if needed.
* Officially supported color formats:
* - keyword, e.g. 'aquamarine' or 'steelblue'
* - hex (with 3, 4, 6 or 8 digits), e.g. '#f0f' or '#e9bebea9'
* - rgb and rgba, e.g. 'rgb(0,240,120)' or 'rgba(0%,94%,47%,0.1)' or 'rgb(0 240 120 / .3)'
* - hsl and hsla, e.g. 'hsl(0,0%,83%)' or 'hsla(0,0%,83%,.5)' or 'hsl(0 0% 83% / 20%)'
*
* @param input CSS color string to parse.
* @returns A `Color` instance, or `undefined` if the input is not a valid color string.
*/
static parse(input) {
// in zoom-and-property function input could be an instance of Color class
if (input instanceof Color) {
return input;
}
if (typeof input !== 'string') {
return;
}
const rgba = parseCssColor(input);
if (rgba) {
return new Color(...rgba, false);
}
}
/**
* Used in color interpolation and by 'to-rgba' expression.
*
* @returns Gien color, with reversed alpha blending, in sRGB color space.
*/
get rgb() {
const { r, g, b, a } = this;
const f = a || Infinity; // reverse alpha blending factor
return this.overwriteGetter('rgb', [r / f, g / f, b / f, a]);
}
/**
* Used in color interpolation.
*
* @returns Gien color, with reversed alpha blending, in HCL color space.
*/
get hcl() {
return this.overwriteGetter('hcl', rgbToHcl(this.rgb));
}
/**
* Used in color interpolation.
*
* @returns Gien color, with reversed alpha blending, in LAB color space.
*/
get lab() {
return this.overwriteGetter('lab', rgbToLab(this.rgb));
}
/**
* Lazy getter pattern. When getter is called for the first time lazy value
* is calculated and then overwrites getter function in given object instance.
*
* @example:
* const redColor = Color.parse('red');
* let x = redColor.hcl; // this will invoke `get hcl()`, which will calculate
* // the value of red in HCL space and invoke this `overwriteGetter` function
* // which in turn will set a field with a key 'hcl' in the `redColor` object.
* // In other words it will override `get hcl()` from its `Color` prototype
* // with its own property: hcl = [calculated red value in hcl].
* let y = redColor.hcl; // next call will no longer invoke getter but simply
* // return the previously calculated value
* x === y; // true - `x` is exactly the same object as `y`
*
* @param getterKey Getter key
* @param lazyValue Lazily calculated value to be memoized by current instance
* @private
*/
overwriteGetter(getterKey, lazyValue) {
Object.defineProperty(this, getterKey, { value: lazyValue });
return lazyValue;
}
/**
* Used by 'to-string' expression.
*
* @returns Serialized color in format `rgba(r,g,b,a)`
* where r,g,b are numbers within 0..255 and alpha is number within 1..0
*
* @example
* var purple = new Color.parse('purple');
* purple.toString; // = "rgba(128,0,128,1)"
* var translucentGreen = new Color.parse('rgba(26, 207, 26, .73)');
* translucentGreen.toString(); // = "rgba(26,207,26,0.73)"
*/
toString() {
const [r, g, b, a] = this.rgb;
return `rgba(${[r, g, b].map(n => Math.round(n * 255)).join(',')},${a})`;
}
static interpolate(from, to, t, spaceKey = 'rgb') {
switch (spaceKey) {
case 'rgb': {
const [r, g, b, alpha] = interpolateArray(from.rgb, to.rgb, t);
return new Color(r, g, b, alpha, false);
}
case 'hcl': {
const [hue0, chroma0, light0, alphaF] = from.hcl;
const [hue1, chroma1, light1, alphaT] = to.hcl;
// https://github.com/gka/chroma.js/blob/cd1b3c0926c7a85cbdc3b1453b3a94006de91a92/src/interpolator/_hsx.js
let hue, chroma;
if (!isNaN(hue0) && !isNaN(hue1)) {
let dh = hue1 - hue0;
if (hue1 > hue0 && dh > 180) {
dh -= 360;
}
else if (hue1 < hue0 && hue0 - hue1 > 180) {
dh += 360;
}
hue = hue0 + t * dh;
}
else if (!isNaN(hue0)) {
hue = hue0;
if (light1 === 1 || light1 === 0)
chroma = chroma0;
}
else if (!isNaN(hue1)) {
hue = hue1;
if (light0 === 1 || light0 === 0)
chroma = chroma1;
}
else {
hue = NaN;
}
const [r, g, b, alpha] = hclToRgb([
hue,
chroma !== null && chroma !== void 0 ? chroma : interpolateNumber(chroma0, chroma1, t),
interpolateNumber(light0, light1, t),
interpolateNumber(alphaF, alphaT, t),
]);
return new Color(r, g, b, alpha, false);
}
case 'lab': {
const [r, g, b, alpha] = labToRgb(interpolateArray(from.lab, to.lab, t));
return new Color(r, g, b, alpha, false);
}
}
}
}
Color.black = new Color(0, 0, 0, 1);
Color.white = new Color(1, 1, 1, 1);
Color.transparent = new Color(0, 0, 0, 0);
Color.red = new Color(1, 0, 0, 1);
class Collator {
constructor(caseSensitive, diacriticSensitive, locale) {
if (caseSensitive)
this.sensitivity = diacriticSensitive ? 'variant' : 'case';
else
this.sensitivity = diacriticSensitive ? 'accent' : 'base';
this.locale = locale;
this.collator = new Intl.Collator(this.locale ? this.locale : [], { sensitivity: this.sensitivity, usage: 'search' });
}
compare(lhs, rhs) {
return this.collator.compare(lhs, rhs);
}
resolvedLocale() {
// We create a Collator without "usage: search" because we don't want
// the search options encoded in our result (e.g. "en-u-co-search")
return new Intl.Collator(this.locale ? this.locale : [])
.resolvedOptions().locale;
}
}
const VERTICAL_ALIGN_OPTIONS = ['bottom', 'center', 'top'];
class FormattedSection {
constructor(text, image, scale, fontStack, textColor, verticalAlign) {
this.text = text;
this.image = image;
this.scale = scale;
this.fontStack = fontStack;
this.textColor = textColor;
this.verticalAlign = verticalAlign;
}
}
class Formatted {
constructor(sections) {
this.sections = sections;
}
static fromString(unformatted) {
return new Formatted([new FormattedSection(unformatted, null, null, null, null, null)]);
}
isEmpty() {
if (this.sections.length === 0)
return true;
return !this.sections.some(section => section.text.length !== 0 ||
(section.image && section.image.name.length !== 0));
}
static factory(text) {
if (text instanceof Formatted) {
return text;
}
else {
return Formatted.fromString(text);
}
}
toString() {
if (this.sections.length === 0)
return '';
return this.sections.map(section => section.text).join('');
}
}
/**
* A set of four numbers representing padding around a box. Create instances from
* bare arrays or numeric values using the static method `Padding.parse`.
* @private
*/
class Padding {
constructor(values) {
this.values = values.slice();
}
/**
* Numeric padding values
* @param input A padding value
* @returns A `Padding` instance, or `undefined` if the input is not a valid padding value.
*/
static parse(input) {
if (input instanceof Padding) {
return input;
}
// Backwards compatibility: bare number is treated the same as array with single value.
// Padding applies to all four sides.
if (typeof input === 'number') {
return new Padding([input, input, input, input]);
}
if (!Array.isArray(input)) {
return undefined;
}
if (input.length < 1 || input.length > 4) {
return undefined;
}
for (const val of input) {
if (typeof val !== 'number') {
return undefined;
}
}
// Expand shortcut properties into explicit 4-sided values
switch (input.length) {
case 1:
input = [input[0], input[0], input[0], input[0]];
break;
case 2:
input = [input[0], input[1], input[0], input[1]];
break;
case 3:
input = [input[0], input[1], input[2], input[1]];
break;
}
return new Padding(input);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t) {
return new Padding(interpolateArray(from.values, to.values, t));
}
}
/**
* An array of numbers. Create instances from
* bare arrays or numeric values using the static method `NumberArray.parse`.
* @private
*/
class NumberArray {
constructor(values) {
this.values = values.slice();
}
/**
* Numeric NumberArray values
* @param input A NumberArray value
* @returns A `NumberArray` instance, or `undefined` if the input is not a valid NumberArray value.
*/
static parse(input) {
if (input instanceof NumberArray) {
return input;
}
// Backwards compatibility (e.g. hillshade-illumination-direction): bare number is treated the same as array with single value.
if (typeof input === 'number') {
return new NumberArray([input]);
}
if (!Array.isArray(input)) {
return undefined;
}
for (const val of input) {
if (typeof val !== 'number') {
return undefined;
}
}
return new NumberArray(input);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t) {
return new NumberArray(interpolateArray(from.values, to.values, t));
}
}
/**
* An array of colors. Create instances from
* bare arrays or strings using the static method `ColorArray.parse`.
* @private
*/
class ColorArray {
constructor(values) {
this.values = values.slice();
}
/**
* ColorArray values
* @param input A ColorArray value
* @returns A `ColorArray` instance, or `undefined` if the input is not a valid ColorArray value.
*/
static parse(input) {
if (input instanceof ColorArray) {
return input;
}
// Backwards compatibility (e.g. hillshade-shadow-color): bare Color is treated the same as array with single value.
if (typeof input === 'string') {
const parsed_val = Color.parse(input);
if (!parsed_val) {
return undefined;
}
return new ColorArray([parsed_val]);
}
if (!Array.isArray(input)) {
return undefined;
}
const colors = [];
for (const val of input) {
if (typeof val !== 'string') {
return undefined;
}
const parsed_val = Color.parse(val);
if (!parsed_val) {
return undefined;
}
colors.push(parsed_val);
}
return new ColorArray(colors);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t, spaceKey = 'rgb') {
const colors = [];
if (from.values.length != to.values.length) {
throw new Error(`colorArray: Arrays have mismatched length (${from.values.length} vs. ${to.values.length}), cannot interpolate.`);
}
for (let i = 0; i < from.values.length; i++) {
colors.push(Color.interpolate(from.values[i], to.values[i], t, spaceKey));
}
return new ColorArray(colors);
}
}
class RuntimeError extends Error {
constructor(message) {
super(message);
this.name = 'RuntimeError';
}
toJSON() {
return this.message;
}
}
/** Set of valid anchor positions, as a set for validation */
const anchors = new Set(['center', 'left', 'right', 'top', 'bottom', 'top-left', 'top-right', 'bottom-left', 'bottom-right']);
/**
* Utility class to assist managing values for text-variable-anchor-offset property. Create instances from
* bare arrays using the static method `VariableAnchorOffsetCollection.parse`.
* @private
*/
class VariableAnchorOffsetCollection {
constructor(values) {
this.values = values.slice();
}
static parse(input) {
if (input instanceof VariableAnchorOffsetCollection) {
return input;
}
if (!Array.isArray(input) ||
input.length < 1 ||
input.length % 2 !== 0) {
return undefined;
}
for (let i = 0; i < input.length; i += 2) {
// Elements in even positions should be anchor positions; Elements in odd positions should be offset values
const anchorValue = input[i];
const offsetValue = input[i + 1];
if (typeof anchorValue !== 'string' || !anchors.has(anchorValue)) {
return undefined;
}
if (!Array.isArray(offsetValue) || offsetValue.length !== 2 || typeof offsetValue[0] !== 'number' || typeof offsetValue[1] !== 'number') {
return undefined;
}
}
return new VariableAnchorOffsetCollection(input);
}
toString() {
return JSON.stringify(this.values);
}
static interpolate(from, to, t) {
const fromValues = from.values;
const toValues = to.values;
if (fromValues.length !== toValues.length) {
throw new RuntimeError(`Cannot interpolate values of different length. from: ${from.toString()}, to: ${to.toString()}`);
}
const output = [];
for (let i = 0; i < fromValues.length; i += 2) {
// Anchor entries must match
if (fromValues[i] !== toValues[i]) {
throw new RuntimeError(`Cannot interpolate values containing mismatched anchors. from[${i}]: ${fromValues[i]}, to[${i}]: ${toValues[i]}`);
}
output.push(fromValues[i]);
// Interpolate the offset values for each anchor
const [fx, fy] = fromValues[i + 1];
const [tx, ty] = toValues[i + 1];
output.push([interpolateNumber(fx, tx, t), interpolateNumber(fy, ty, t)]);
}
return new VariableAnchorOffsetCollection(output);
}
}
class ResolvedImage {
constructor(options) {
this.name = options.name;
this.available = options.available;
}
toString() {
return this.name;
}
static fromString(name) {
if (!name)
return null; // treat empty values as no image
return new ResolvedImage({ name, available: false });
}
}
class ProjectionDefinition {
constructor(from, to, transition) {
this.from = from;
this.to = to;
this.transition = transition;
}
static interpolate(from, to, t) {
return new ProjectionDefinition(from, to, t);
}
static parse(input) {
if (input instanceof ProjectionDefinition) {
return input;
}
if (Array.isArray(input) && input.length === 3 && typeof input[0] === 'string' && typeof input[1] === 'string' && typeof input[2] === 'number') {
return new ProjectionDefinition(input[0], input[1], input[2]);
}
if (typeof input === 'object' && typeof input.from === 'string' && typeof input.to === 'string' && typeof input.transition === 'number') {
return new ProjectionDefinition(input.from, input.to, input.transition);
}
if (typeof input === 'string') {
return new ProjectionDefinition(input, input, 1);
}
return undefined;
}
}
function validateRGBA(r, g, b, a) {
if (!(typeof r === 'number' && r >= 0 && r <= 255 &&
typeof g === 'number' && g >= 0 && g <= 255 &&
typeof b === 'number' && b >= 0 && b <= 255)) {
const value = typeof a === 'number' ? [r, g, b, a] : [r, g, b];
return `Invalid rgba value [${value.join(', ')}]: 'r', 'g', and 'b' must be between 0 and 255.`;
}
if (!(typeof a === 'undefined' || (typeof a === 'number' && a >= 0 && a <= 1))) {
return `Invalid rgba value [${[r, g, b, a].join(', ')}]: 'a' must be between 0 and 1.`;
}
return null;
}
function isValue(mixed) {
if (mixed === null ||
typeof mixed === 'string' ||
typeof mixed === 'boolean' ||
typeof mixed === 'number' ||
mixed instanceof ProjectionDefinition ||
mixed instanceof Color ||
mixed instanceof Collator ||
mixed instanceof Formatted ||
mixed instanceof Padding ||
mixed instanceof NumberArray ||
mixed instanceof ColorArray ||
mixed instanceof VariableAnchorOffsetCollection ||
mixed instanceof ResolvedImage) {
return true;
}
else if (Array.isArray(mixed)) {
for (const item of mixed) {
if (!isValue(item)) {
return false;
}
}
return true;
}
else if (typeof mixed === 'object') {
for (const key in mixed) {
if (!isValue(mixed[key])) {
return false;
}
}
return true;
}
else {
return false;
}
}
function typeOf(value) {
if (value === null) {
return NullType;
}
else if (typeof value === 'string') {
return StringType;
}
else if (typeof value === 'boolean') {
return BooleanType;
}
else if (typeof value === 'number') {
return NumberType;
}
else if (value instanceof Color) {
return ColorType;
}
else if (value instanceof ProjectionDefinition) {
return ProjectionDefinitionType;
}
else if (value instanceof Collator) {
return CollatorType;
}
else if (value instanceof Formatted) {
return FormattedType;
}
else if (value instanceof Padding) {
return PaddingType;
}
else if (value instanceof NumberArray) {
return NumberArrayType;
}
else if (value instanceof ColorArray) {
return ColorArrayType;
}
else if (value instanceof VariableAnchorOffsetCollection) {
return VariableAnchorOffsetCollectionType;
}
else if (value instanceof ResolvedImage) {
return ResolvedImageType;
}
else if (Array.isArray(value)) {
const length = value.length;
let itemType;
for (const item of value) {
const t = typeOf(item);
if (!itemType) {
itemType = t;
}
else if (itemType === t) {
continue;
}
else {
itemType = ValueType;
break;
}
}
return array(itemType || ValueType, length);
}
else {
return ObjectType;
}
}
function valueToString(value) {
const type = typeof value;
if (value === null) {
return '';
}
else if (type === 'string' || type === 'number' || type === 'boolean') {
return String(value);
}
else if (value instanceof Color || value instanceof ProjectionDefinition || value instanceof Formatted || value instanceof Padding || value instanceof NumberArray || value instanceof ColorArray || value instanceof VariableAnchorOffsetCollection || value instanceof ResolvedImage) {
return value.toString();
}
else {
return JSON.stringify(value);
}
}
class Literal {
constructor(type, value) {
this.type = type;
this.value = value;
}
static parse(args, context) {
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];
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() {
return this.value;
}
eachChild() { }
outputDefined() {
return true;
}
}
const types$1 = {
string: StringType,
number: NumberType,
boolean: BooleanType,
object: ObjectType
};
class Assertion {
constructor(type, args) {
this.type = type;
this.args = args;
}
static parse(args, context) {
if (args.length < 2)
return context.error('Expected at least one argument.');
let i = 1;
let type;
const name = args[0];
if (name === 'array') {
let itemType;
if (args.length > 2) {
const type = args[1];
if (typeof type !== 'string' || !(type in types$1) || type === 'object')
return context.error('The item type argument of "array" must be one of string, number, boolean', 1);
itemType = types$1[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 {
if (!types$1[name])
throw new Error(`Types doesn't contain name = ${name}`);
type = types$1[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) {
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 ${typeToString(this.type)}, but found ${typeToString(typeOf(value))} instead.`);
}
}
throw new Error();
}
eachChild(fn) {
this.args.forEach(fn);
}
outputDefined() {
return this.args.every(arg => arg.outputDefined());
}
}
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 {
constructor(type, args) {
this.type = type;
this.args = args;
}
static parse(args, context) {
if (args.length < 2)
return context.error('Expected at least one argument.');
const name = args[0];
if (!types[name])
throw new Error(`Can't parse ${name} as it is not part of the known types`);
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) {
switch (this.type.kind) {
case 'boolean':
return Boolean(this.args[0].evaluate(ctx));
case '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 rgba 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] / 255, input[1] / 255, input[2] / 255, input[3]);
}
}
}
throw new RuntimeError(error || `Could not parse color from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
}
case 'padding': {
let input;
for (const arg of this.args) {
input = arg.evaluate(ctx);
const pad = Padding.parse(input);
if (pad) {
return pad;
}
}
throw new RuntimeError(`Could not parse padding from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
}
case 'numberArray': {
let input;
for (const arg of this.args) {
input = arg.evaluate(ctx);
const val = NumberArray.parse(input);
if (val) {
return val;
}
}
throw new RuntimeError(`Could not parse numberArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
}
case 'colorArray': {
let input;
for (const arg of this.args) {
input = arg.evaluate(ctx);
const val = ColorArray.parse(input);
if (val) {
return val;
}
}
throw new RuntimeError(`Could not parse colorArray from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
}
case 'variableAnchorOffsetCollection': {
let input;
for (const arg of this.args) {
input = arg.evaluate(ctx);
const coll = VariableAnchorOffsetCollection.parse(input);
if (coll) {
return coll;
}
}
throw new RuntimeError(`Could not parse variableAnchorOffsetCollection from value '${typeof input === 'string' ? input : JSON.stringify(input)}'`);
}
case '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.`);
}
case '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)));
case 'resolvedImage':
return ResolvedImage.fromString(valueToString(this.args[0].evaluate(ctx)));
case 'projectionDefinition':
return this.args[0].evaluate(ctx);
default:
return valueToString(this.args[0].evaluate(ctx));
}
}
eachChild(fn) {
this.args.forEach(fn);
}
outputDefined() {
return this.args.every(arg => arg.outputDefined());
}
}
const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
class EvaluationContext {
constructor() {
this.globals = null;
this.feature = null;
this.featureState = null;
this.formattedSection = null;
this._parseColorCache = new Map();
this.availableImages = null;
this.canonical = null;
}
id() {
return this.feature && 'id' in this.feature ? this.feature.id : null;
}
geometryType() {
return this.feature ? typeof this.feature.type === 'number' ? geometryTypes[this.feature.type] : this.feature.type : null;
}
geometry() {
return this.feature && 'geometry' in this.feature ? this.feature.geometry : null;
}
canonicalID() {
return this.canonical;
}
properties() {
return this.feature && this.feature.properties || {};
}
parseColor(input) {
let cached = this._parseColorCache.get(input);
if (!cached) {
cached = Color.parse(input);
this._parseColorCache.set(input, cached);
}
return cached;
}
}
/**
* State associated parsing at a given point in an expression tree.
* @private
*/
class ParsingContext {
constructor(registry, isConstantFunc, path = [], expectedType, scope = new Scope(), errors = []) {
this.registry = registry;
this.path = path;
this.key = path.map(part => `[${part}]`).join('');
this.scope = scope;
this.errors = errors;
this.expectedType = expectedType;
this._isConstant = isConstantFunc;
}
/**
* @param expr the JSON expression to parse
* @param index the optional argument index if this expression is an argument of a parent expression that's being parsed
* @param options
* @param options.omitTypeAnnotations set true to omit inferred type annotations. Caller beware: with this option set, the parsed expression's type will NOT satisfy `expectedType` if it would normally be wrapped in an inferred annotation.
* @private
*/
parse(expr, index, expectedType, bindings, options = {}) {
if (index) {
return this.concat(index, expectedType, bindings)._parse(expr, options);
}
return this._parse(expr, options);
}
_parse(expr, options) {
if (expr === null || typeof expr === 'string' || typeof expr === 'boolean' || typeof expr === 'number') {
expr = ['literal', expr];
}
function annotate(parsed, type, typeAnnotation) {
if (typeAnnotation === 'assert') {
return new Assertion(type, [parsed]);
}
else if (typeAnnotation === 'coerce') {
return new Coercion(type, [parsed]);
}
else {
return parsed;
}
}
if (Array.isArray(expr)) {
if (expr.length === 0) {
return this.error('Expected an array with at least one element. If you wanted a literal array, use ["literal", []].');
}
const op = expr[0];
if (typeof op !== 'string') {
this.error(`Expression name must be a string, but found ${typeof op} instead. If you wanted a literal array, use ["literal", [...]].`, 0);
return null;
}
const Expr = this.registry[op];
if (Expr) {
let parsed = Expr.parse(expr, this);
if (!parsed)
return null;
if (this.expectedType) {
const expected = this.expectedType;
const actual = parsed.type;
// When we expect a number, string, boolean, or array but have a value, wrap it in an assertion.
// When we expect a color or formatted string, but have a string or value, wrap it in a coercion.
// Otherwise, we do static type-checking.
//
// These behaviors are overridable for:
// * The "coalesce" operator, which needs to omit type annotations.
// * String-valued properties (e.g. `text-field`), where coercion is more convenient than assertion.
//
if ((expected.kind === 'string' || expected.kind === 'number' || expected.kind === 'boolean' || expected.kind === 'object' || expected.kind === 'array') && actual.kind === 'value') {
parsed = annotate(parsed, expected, options.typeAnnotation || 'assert');
}
else if (('projectionDefinition' === expected.kind && ['string', 'array'].includes(actual.kind)) ||
((['color', 'formatted', 'resolvedImage'].includes(expected.kind)) && ['value', 'string'].includes(actual.kind)) ||
((['padding', 'numberArray'].includes(expected.kind)) && ['value', 'number', 'array'].includes(actual.kind)) ||
('colorArray' === expected.kind && ['value', 'string', 'array'].includes(actual.kind)) ||
('variableAnchorOffsetCollection' === expected.kind && ['value', 'array'].includes(actual.kind))) {
parsed = annotate(parsed, expected, options.typeAnnotation || 'coerce');
}
else if (this.checkSubtype(expected, actual)) {
return null;
}
}
// If an expression's arguments are all literals, we can evaluate
// it immediately and replace it with a literal value in the
// parsed/compiled result. Expressions that expect an image should
// not be resolved here so we can later get the available images.
if (!(parsed instanceof Literal) && (parsed.type.kind !== 'resolvedImage') && this._isConstant(parsed)) {
const ec = new EvaluationContext();
try {
parsed = new Literal(parsed.type, parsed.evaluate(ec));
}
catch (e) {
this.error(e.message);
return null;
}
}
return parsed;
}
return this.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0);
}
else if (typeof expr === 'undefined') {
return this.error('\'undefined\' value invalid. Use null instead.');
}
else if (typeof expr === 'object') {
return this.error('Bare objects invalid. Use ["literal", {...}] instead.');
}
else {
return this.error(`Expected an array, but found ${typeof expr} instead.`);
}
}
/**
* Returns a copy of this context suitable for parsing the subexpression at
* index `index`, optionally appending to 'let' binding map.
*
* Note that `errors` property, intended for collecting errors while
* parsing, is copied by reference rather than cloned.
* @private
*/
concat(index, expectedType, bindings) {
const path = typeof index === 'number' ? this.path.concat(index) : this.path;
const scope = bindings ? this.scope.concat(bindings) : this.scope;
return new ParsingContext(this.registry, this._isConstant, path, expectedType || null, scope, this.errors);
}
/**
* Push a parsing (or type checking) error into the `this.errors`
* @param error The message
* @param keys Optionally specify the source of the error at a child
* of the current expression at `this.key`.
* @private
*/
error(error, ...keys) {
const key = `${this.key}${keys.map(k => `[${k}]`).join('')}`;
this.errors.push(new ExpressionParsingError(key, error));
}
/**
* Returns null if `t` is a subtype of `expected`; otherwise returns an
* error message and also pushes it to `this.errors`.
* @param expected The expected type
* @param t The actual type
* @returns null if `t` is a subtype of `expected`; otherwise returns an error message
*/
checkSubtype(expected, t) {
const error = checkSubtype(expected, t);
if (error)
this.error(error);
return error;
}
}
class Let {
constructor(bindings, result) {
this.type = result.type;
this.bindings = [].concat(bindings);
this.result = result;
}
evaluate(ctx) {
return this.result.evaluate(ctx);
}
eachChild(fn) {
for (const binding of this.bindings) {
fn(binding[1]);
}
fn(this.result);
}
static parse(args, context) {
if (args.length < 4)
return context.error(`Expected at least 3 arguments, but found ${args.length - 1} instead.`);
const bindings = [];
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() {
return this.result.outputDefined();
}
}
class Var {
constructor(name, boundExpression) {
this.type = boundExpression.type;
this.name = name;
this.boundExpression = boundExpression;
}
static parse(args, context) {
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) {
return this.boundExpression.evaluate(ctx);
}
eachChild() { }
outputDefined() {
return false;
}
}
class At {
constructor(type, index, input) {
this.type = type;
this.index = index;
this.input = input;
}
static parse(args, context) {
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 = input.type;
return new At(t.itemType, index, input);
}
evaluate(ctx) {
const index = this.index.evaluate(ctx);
const array = this.input.evaluate(ctx);
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) {
fn(this.index);
fn(this.input);
}
outputDefined() {
return false;
}
}
class In {
constructor(needle, haystack) {
this.type = BooleanType;
this.needle = needle;
this.haystack = haystack;
}
static parse(args, context) {
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 ${typeToString(needle.type)} instead`);
}
return new In(needle, haystack);
}
evaluate(ctx) {
const needle = this.needle.evaluate(ctx);
const haystack = this.haystack.evaluate(ctx);
if (!haystack)
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 ${typeToString(typeOf(needle))} instead.`);
}
if (!isValidNativeType(haystack, ['string', 'array'])) {
throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`);
}
return haystack.indexOf(needle) >= 0;
}
eachChild(fn) {
fn(this.needle);
fn(this.haystack);
}
outputDefined() {
return true;
}
}
class IndexOf {
constructor(needle, haystack, fromIndex) {
this.type = NumberType;
this.needle = needle;
this.haystack = haystack;
this.fromIndex = fromIndex;
}
static parse(args, context) {
if (args.length <= 2 || args.length >= 5) {
return context.error(`Expected 2 or 3 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 ${typeToString(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) {
const needle = this.needle.evaluate(ctx);
const haystack = this.haystack.evaluate(ctx);
if (!isValidNativeType(needle, ['boolean', 'string', 'number', 'null'])) {
throw new RuntimeError(`Expected first argument to be of type boolean, string, number or null, but found ${typeToString(typeOf(needle))} instead.`);
}
let fromIndex;
if (this.fromIndex) {
fromIndex = this.fromIndex.evaluate(ctx);
}
if (isValidNativeType(haystack, ['string'])) {
const rawIndex = haystack.indexOf(needle, fromIndex);
if (rawIndex === -1) {
return -1;
}
else {
// The index may be affected by surrogate pairs, so get the length of the preceding substring.
return [...haystack.slice(0, rawIndex)].length;
}
}
else if (isValidNativeType(haystack, ['array'])) {
return haystack.indexOf(needle, fromIndex);
}
else {
throw new RuntimeError(`Expected second argument to be of type array or string, but found ${typeToString(typeOf(haystack))} instead.`);
}
}
eachChild(fn) {
fn(this.needle);
fn(this.haystack);
if (this.fromIndex) {
fn(this.fromIndex);
}
}
outputDefined() {
return false;
}
}
class Match {
constructor(inputType, outputType, input, cases, outputs, otherwise) {
this.inputType = inputType;
this.type = outputType;
this.input = input;
this.cases = cases;
this.outputs = outputs;
this.otherwise = otherwise;
}
static parse(args, context) {
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;
if (input.type.kind !== 'value' && context.concat(1).checkSubtype(inputType, input.type)) {
return null;
}
return new Match(inputType, outputType, input, cases, outputs, otherwise);
}
evaluate(ctx) {
const input = this.input.evaluate(ctx);
const output = (typeOf(input) === this.inputType && this.outputs[this.cases[input]]) || this.otherwise;
return output.evaluate(ctx);
}
eachChild(fn) {
fn(this.input);
this.outputs.forEach(fn);
fn(this.otherwise);
}
outputDefined() {
return this.outputs.every(out => out.outputDefined()) && this.otherwise.outputDefined();
}
}
class Case {
constructor(type, branches, otherwise) {
this.type = type;
this.branches = branches;
this.otherwise = otherwise;
}
static parse(args, context) {
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;
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;
if (!outputType)
throw new Error('Can\'t infer output type');
return new Case(outputType, branches, otherwise);
}
evaluate(ctx) {
for (const [test, expression] of this.branches) {
if (test.evaluate(ctx)) {
return expression.evaluate(ctx);
}
}
return this.otherwise.evaluate(ctx);
}
eachChild(fn) {
for (const [test, expression] of this.branches) {
fn(test);
fn(expression);
}
fn(this.otherwise);
}
outputDefined() {
return this.branches.every(([_, out]) => out.outputDefined()) && this.otherwise.outputDefined();
}
}
class Slice {
constructor(type, input, beginIndex, endIndex) {
this.type = type;
this.input = input;
this.beginIndex = beginIndex;
this.endIndex = endIndex;
}
static parse(args, context) {
if (args.length <= 2 || args.length >= 5) {
return context.error(`Expected 2 or 3 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 ${typeToString(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) {
const input = this.input.evaluate(ctx);
const beginIndex = this.beginIndex.evaluate(ctx);
let endIndex;
if (this.endIndex) {
endIndex = this.endIndex.evaluate(ctx);
}
if (isValidNativeType(input, ['string'])) {
// Indices may be affected by surrogate pairs.
return [...input].slice(beginIndex, endIndex).join('');
}
else if (isValidNativeType(input, ['array'])) {
return input.slice(beginIndex, endIndex);
}
else {
throw new RuntimeError(`Expected first argument to be of type array or string, but found ${typeToString(typeOf(input))} instead.`);
}
}
eachChild(fn) {
fn(this.input);
fn(this.beginIndex);
if (this.endIndex) {
fn(this.endIndex);
}
}
outputDefined() {
return false;
}
}
/**
* Returns the index of the last stop <= input, or 0 if it doesn't exist.
* @private
*/
function findStopLessThanOrEqualTo(stops, input) {
const lastIndex = stops.length - 1;
let lowerIndex = 0;
let upperIndex = lastIndex;
let currentIndex = 0;
let currentValue, nextValue;
while (lowerIndex <= upperIndex) {
currentIndex = Math.floor((lowerIndex + upperIndex) / 2);
currentValue = stops[currentIndex];
nextValue = stops[currentIndex + 1];
if (currentValue <= input) {
if (currentIndex === lastIndex || input < nextValue) { // Search complete
return currentIndex;
}
lowerIndex = currentIndex + 1;
}
else if (currentValue > input) {
upperIndex = currentIndex - 1;
}
else {
throw new RuntimeError('Input is not a number.');
}
}
return 0;
}
class Step {
constructor(type, input, 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, context) {
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 = [];
let outputType = null;
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) {
const labels = this.labels;
const outputs = this.outputs;
if (labels.length === 1) {
return outputs[0].evaluate(ctx);
}
const value = this.input.evaluate(ctx);
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) {
fn(this.input);
for (const expression of this.outputs) {
fn(expression);
}
}
outputDefined() {
return this.outputs.every(out => out.outputDefined());
}
}
var unitbezier;
var hasRequiredUnitbezier;
function requireUnitbezier () {
if (hasRequiredUnitbezier) return unitbezier;
hasRequiredUnitbezier = 1;
unitbezier = UnitBezier;
function UnitBezier(p1x, p1y, p2x, p2y) {
// Calculate the polynomial coefficients, implicit first and last control points are (0,0) and (1,1).
this.cx = 3.0 * p1x;
this.bx = 3.0 * (p2x - p1x) - this.cx;
this.ax = 1.0 - this.cx - this.bx;
this.cy = 3.0 * p1y;
this.by = 3.0 * (p2y - p1y) - this.cy;
this.ay = 1.0 - this.cy - this.by;
this.p1x = p1x;
this.p1y = p1y;
this.p2x = p2x;
this.p2y = p2y;
}
UnitBezier.prototype = {
sampleCurveX: function (t) {
// `ax t^3 + bx t^2 + cx t' expanded using Horner's rule.
return ((this.ax * t + this.bx) * t + this.cx) * t;
},
sampleCurveY: function (t) {
return ((this.ay * t + this.by) * t + this.cy) * t;
},
sampleCurveDerivativeX: function (t) {
return (3.0 * this.ax * t + 2.0 * this.bx) * t + this.cx;
},
solveCurveX: function (x, epsilon) {
if (epsilon === undefined) epsilon = 1e-6;
if (x < 0.0) return 0.0;
if (x > 1.0) return 1.0;
var t = x;
// First try a few iterations of Newton's method - normally very fast.
for (var i = 0; i < 8; i++) {
var x2 = this.sampleCurveX(t) - x;
if (Math.abs(x2) < epsilon) return t;
var d2 = this.sampleCurveDerivativeX(t);
if (Math.abs(d2) < 1e-6) break;
t = t - x2 / d2;
}
// Fall back to the bisection method for reliability.
var t0 = 0.0;
var t1 = 1.0;
t = x;
for (i = 0; i < 20; i++) {
x2 = this.sampleCurveX(t);
if (Math.abs(x2 - x) < epsilon) break;
if (x > x2) {
t0 = t;
} else {
t1 = t;
}
t = (t1 - t0) * 0.5 + t0;
}
return t;
},
solve: function (x, epsilon) {
return this.sampleCurveY(this.solveCurveX(x, epsilon));
}
};
return unitbezier;
}
var unitbezierExports = requireUnitbezier();
var UnitBezier = /*@__PURE__*/getDefaultExportFromCjs(unitbezierExports);
class Interpolate {
constructor(type, operator, interpolation, input, 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, input, lower, upper) {
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, context) {
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
};
}
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 = [];
let outputType = null;
if ((operator === 'interpolate-hcl' || operator === 'interpolate-lab') && context.expectedType != ColorArrayType) {
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 (!verifyType(outputType, NumberType) &&
!verifyType(outputType, ProjectionDefinitionType) &&
!verifyType(outputType, ColorType) &&
!verifyType(outputType, PaddingType) &&
!verifyType(outputType, NumberArrayType) &&
!verifyType(outputType, ColorArrayType) &&
!verifyType(outputType, VariableAnchorOffsetCollectionType) &&
!verifyType(outputType, array(NumberType))) {
return context.error(`Type ${typeToString(outputType)} is not interpolatable.`);
}
return new Interpolate(outputType, operator, interpolation, input, stops);
}
evaluate(ctx) {
const labels = this.labels;
const outputs = this.outputs;
if (labels.length === 1) {
return outputs[0].evaluate(ctx);
}
const value = this.input.evaluate(ctx);
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);
switch (this.operator) {
case 'interpolate':
switch (this.type.kind) {
case 'number':
return interpolateNumber(outputLower, outputUpper, t);
case 'color':
return Color.interpolate(outputLower, outputUpper, t);
case 'padding':
return Padding.interpolate(outputLower, outputUpper, t);
case 'colorArray':
return ColorArray.interpolate(outputLower, outputUpper, t);
case 'numberArray':
return NumberArray.interpolate(outputLower, outputUpper, t);
case 'variableAnchorOffsetCollection':
return VariableAnchorOffsetCollection.interpolate(outputLower, outputUpper, t);
case 'array':
return interpolateArray(outputLower, outputUpper, t);
case 'projectionDefinition':
return ProjectionDefinition.interpolate(outputLower, outputUpper, t);
}
case 'interpolate-hcl':
switch (this.type.kind) {
case 'color':
return Color.interpolate(outputLower, outputUpper, t, 'hcl');
case 'colorArray':
return ColorArray.interpolate(outputLower, outputUpper, t, 'hcl');
}
case 'interpolate-lab':
switch (this.type.kind) {
case 'color':
return Color.interpolate(outputLower, outputUpper, t, 'lab');
case 'colorArray':
return ColorArray.interpolate(outputLower, outputUpper, t, 'lab');
}
}
}
eachChild(fn) {
fn(this.input);
for (const expression of this.outputs) {
fn(expression);
}
}
outputDefined() {
return this.outputs.every(out => out.outputDefined());
}
}
/**
* 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);
}
}
class Coalesce {
constructor(type, args) {
this.type = type;
this.args = args;
}
static parse(args, context) {
if (args.length < 2) {
return context.error('Expected at least one argument.');
}
let outputType = null;
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);
}
if (!outputType)
throw new Error('No output type');
// 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, parsedArgs);
}
evaluate(ctx) {
let result = null;
let argCount = 0;
let requestedImageName;
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 name so styleimagemissing can fire
if (result && result instanceof ResolvedImage && !result.available) {
if (!requestedImageName) {
requestedImageName = result.name;
}
result = null;
if (argCount === this.args.length) {
result = requestedImageName;
}
}
if (result !== null)
break;
}
return result;
}
eachChild(fn) {
this.args.forEach(fn);
}
outputDefined() {
return this.args.every(arg => arg.outputDefined());
}
}
function isComparableType(op, 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, a, b) { return a === b; }
function neq(ctx, a, b) { return a !== b; }
function lt(ctx, a, b) { return a < b; }
function gt(ctx, a, b) { return a > b; }
function lteq(ctx, a, b) { return a <= b; }
function gteq(ctx, a, b) { return a >= b; }
function eqCollate(ctx, a, b, c) { return c.compare(a, b) === 0; }
function neqCollate(ctx, a, b, c) { return !eqCollate(ctx, a, b, c); }
function ltCollate(ctx, a, b, c) { return c.compare(a, b) < 0; }
function gtCollate(ctx, a, b, c) { return c.compare(a, b) > 0; }
function lteqCollate(ctx, a, b, c) { return c.compare(a, b) <= 0; }
function gteqCollate(ctx, a, b, c) { 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, compareBasic, compareWithCollator) {
const isOrderComparison = op !== '==' && op !== '!=';
return class Comparison {
constructor(lhs, rhs, collator) {
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, context) {
if (args.length !== 3 && args.length !== 4)
return context.error('Expected two or three arguments.');
const op = args[0];
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 '${typeToString(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 '${typeToString(rhs.type)}'.`);
}
if (lhs.type.kind !== rhs.type.kind &&
lhs.type.kind !== 'value' &&
rhs.type.kind !== 'value') {
return context.error(`Cannot compare types '${typeToString(lhs.type)}' and '${typeToString(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) {
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) {
fn(this.lhs);
fn(this.rhs);
if (this.collator) {
fn(this.collator);
}
}
outputDefined() {
return true;
}
};
}
const Equals = makeComparison('==', eq, eqCollate);
const NotEquals = makeComparison('!=', neq, neqCollate);
const LessThan = makeComparison('<', lt, ltCollate);
const GreaterThan = makeComparison('>', gt, gtCollate);
const LessThanOrEqual = makeComparison('<=', lteq, lteqCollate);
const GreaterThanOrEqual = makeComparison('>=', gteq, gteqCollate);
class CollatorExpression {
constructor(caseSensitive, diacriticSensitive, locale) {
this.type = CollatorType;
this.locale = locale;
this.caseSensitive = caseSensitive;
this.diacriticSensitive = diacriticSensitive;
}
static parse(args, context) {
if (args.length !== 2)
return context.error('Expected one argument.');
const options = args[1];
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) {
return new Collator(this.caseSensitive.evaluate(ctx), this.diacriticSensitive.evaluate(ctx), this.locale ? this.locale.evaluate(ctx) : null);
}
eachChild(fn) {
fn(this.caseSensitive);
fn(this.diacriticSensitive);
if (this.locale) {
fn(this.locale);
}
}
outputDefined() {
// 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;
}
}
class NumberFormat {
constructor(number, locale, currency, minFractionDigits, maxFractionDigits) {
this.type = StringType;
this.number = number;
this.locale = locale;
this.currency = currency;
this.minFractionDigits = minFractionDigits;
this.maxFractionDigits = maxFractionDigits;
}
static parse(args, context) {
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];
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 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, minFractionDigits, maxFractionDigits);
}
evaluate(ctx) {
return new Intl.NumberFormat(this.locale ? this.locale.evaluate(ctx) : [], {
style: this.currency ? 'currency' : 'decimal',
currency: this.currency ? this.currency.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) {
fn(this.number);
if (this.locale) {
fn(this.locale);
}
if (this.currency) {
fn(this.currency);
}
if (this.minFractionDigits) {
fn(this.minFractionDigits);
}
if (this.maxFractionDigits) {
fn(this.maxFractionDigits);
}
}
outputDefined() {
return false;
}
}
class FormatExpression {
constructor(sections) {
this.type = FormattedType;
this.sections = sections;
}
static parse(args, context) {
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 = [];
let nextTokenMayBeObject = false;
for (let i = 1; i <= args.length - 1; ++i) {
const arg = args[i];
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;
}
let verticalAlign = null;
if (arg['vertical-align']) {
if (typeof arg['vertical-align'] === 'string' && !VERTICAL_ALIGN_OPTIONS.includes(arg['vertical-align'])) {
return context.error(`'vertical-align' must be one of: 'bottom', 'center', 'top' but found '${arg['vertical-align']}' instead.`);
}
verticalAlign = context.parse(arg['vertical-align'], 1, StringType);
if (!verticalAlign)
return null;
}
const lastExpression = sections[sections.length - 1];
lastExpression.scale = scale;
lastExpression.font = font;
lastExpression.textColor = textColor;
lastExpression.verticalAlign = verticalAlign;
}
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, verticalAlign: null });
}
}
return new FormatExpression(sections);
}
evaluate(ctx) {
const evaluateSection = section => {
const evaluatedContent = section.content.evaluate(ctx);
if (typeOf(evaluatedContent) === ResolvedImageType) {
return new FormattedSection('', evaluatedContent, null, null, null, section.verticalAlign ? section.verticalAlign.evaluate(ctx) : null);
}
return new FormattedSection(valueToString(evaluatedContent), null, section.scale ? section.scale.evaluate(ctx) : null, section.font ? section.font.evaluate(ctx).join(',') : null, section.textColor ? section.textColor.evaluate(ctx) : null, section.verticalAlign ? section.verticalAlign.evaluate(ctx) : null);
};
return new Formatted(this.sections.map(evaluateSection));
}
eachChild(fn) {
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);
}
if (section.verticalAlign) {
fn(section.verticalAlign);
}
}
}
outputDefined() {
// Technically the combinatoric set of all children
// Usually, this.text will be undefined anyway
return false;
}
}
class ImageExpression {
constructor(input) {
this.type = ResolvedImageType;
this.input = input;
}
static parse(args, context) {
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) {
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) {
fn(this.input);
}
outputDefined() {
// The output of image is determined by the list of available images in the evaluation context
return false;
}
}
class Length {
constructor(input) {
this.type = NumberType;
this.input = input;
}
static parse(args, context) {
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 ${typeToString(input.type)} instead.`);
return new Length(input);
}
evaluate(ctx) {
const input = this.input.evaluate(ctx);
if (typeof input === 'string') {
// The length may be affected by surrogate pairs.
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 ${typeToString(typeOf(input))} instead.`);
}
}
eachChild(fn) {
fn(this.input);
}
outputDefined() {
return false;
}
}
const EXTENT = 8192;
function getTileCoordinates(p, canonical) {
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 getLngLatFromTileCoord(coord, canonical) {
const tilesAtZoom = Math.pow(2, canonical.z);
const x = (coord[0] / EXTENT + canonical.x) / tilesAtZoom;
const y = (coord[1] / EXTENT + canonical.y) / tilesAtZoom;
return [lngFromMercatorXfromLng(x), latFromMercatorY(y)];
}
function mercatorXfromLng(lng) {
return (180 + lng) / 360;
}
function lngFromMercatorXfromLng(mercatorX) {
return mercatorX * 360 - 180;
}
function mercatorYfromLat(lat) {
return (180 - (180 / Math.PI * Math.log(Math.tan(Math.PI / 4 + lat * Math.PI / 360)))) / 360;
}
function latFromMercatorY(mercatorY) {
return 360 / Math.PI * Math.atan(Math.exp((180 - mercatorY * 360) * Math.PI / 180)) - 90;
}
function updateBBox(bbox, coord) {
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 boxWithinBox(bbox1, bbox2) {
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 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]);
}
function pointOnBoundary(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);
}
// a, b are end points for line segment1, c and d are end points for line segment2
function segmentIntersectSegment(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 (segmentIntersectSegment(p1, p2, ring[j], ring[j + 1])) {
return true;
}
}
}
return false;
}
// ray casting algorithm for detecting if point is in polygon
function pointWithinPolygon(point, rings, trueIfOnBoundary = false) {
let inside = false;
for (const ring of rings) {
for (let j = 0; j < ring.length - 1; j++) {
if (pointOnBoundary(point, ring[j], ring[j + 1]))
return trueIfOnBoundary;
if (rayIntersect(point, ring[j], ring[j + 1]))
inside = !inside;
}
}
return inside;
}
function pointWithinPolygons(point, polygons) {
for (const polygon of polygons) {
if (pointWithinPolygon(point, polygon))
return true;
}
return false;
}
function lineStringWithinPolygon(line, polygon) {
// First, check if geometry points of line segments are all inside polygon
for (const point of line) {
if (!pointWithinPolygon(point, 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 (const polygon of polygons) {
if (lineStringWithinPolygon(line, polygon))
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;
}
function getTilePolygon(coordinates, bbox, canonical) {
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) {
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) {
const worldSize = Math.pow(2, canonical.z) * EXTENT;
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
const 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) {
const worldSize = Math.pow(2, canonical.z) * EXTENT;
const shifts = [canonical.x * EXTENT, canonical.y * EXTENT];
const 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, polygonGeometry) {
const pointBBox = [Infinity, Infinity, -Infinity, -Infinity];
const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
const canonical = ctx.canonicalID();
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, polygonGeometry) {
const lineBBox = [Infinity, Infinity, -Infinity, -Infinity];
const polyBBox = [Infinity, Infinity, -Infinity, -Infinity];
const canonical = ctx.canonicalID();
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 {
constructor(geojson, geometries) {
this.type = BooleanType;
this.geojson = geojson;
this.geometries = geometries;
}
static parse(args, context) {
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];
if (geojson.type === 'FeatureCollection') {
const polygonsCoords = [];
for (const polygon of geojson.features) {
const { type, coordinates } = polygon.geometry;
if (type === 'Polygon') {
polygonsCoords.push(coordinates);
}
if (type === 'MultiPolygon') {
polygonsCoords.push(...coordinates);
}
}
if (polygonsCoords.length) {
const multipolygonWrapper = {
type: 'MultiPolygon',
coordinates: polygonsCoords
};
return new Within(geojson, multipolygonWrapper);
}
}
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) {
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() {
return true;
}
}
class TinyQueue {
constructor(data = [], compare = (a, b) => (a < b ? -1 : a > b ? 1 : 0)) {
this.data = data;
this.length = this.data.length;
this.compare = compare;
if (this.length > 0) {
for (let i = (this.length >> 1) - 1; i >= 0; i--) this._down(i);
}
}
push(item) {
this.data.push(item);
this._up(this.length++);
}
pop() {
if (this.length === 0) return undefined;
const top = this.data[0];
const bottom = this.data.pop();
if (--this.length > 0) {
this.data[0] = bottom;
this._down(0);
}
return top;
}
peek() {
return this.data[0];
}
_up(pos) {
const {data, compare} = this;
const item = data[pos];
while (pos > 0) {
const parent = (pos - 1) >> 1;
const current = data[parent];
if (compare(item, current) >= 0) break;
data[pos] = current;
pos = parent;
}
data[pos] = item;
}
_down(pos) {
const {data, compare} = this;
const halfLength = this.length >> 1;
const item = data[pos];
while (pos < halfLength) {
let bestChild = (pos << 1) + 1; // initially it is the left child
const right = bestChild + 1;
if (right < this.length && compare(data[right], data[bestChild]) < 0) {
bestChild = right;
}
if (compare(data[bestChild], item) >= 0) break;
data[pos] = data[bestChild];
pos = bestChild;
}
data[pos] = item;
}
}
/**
* 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
*/
function classifyRings(rings, maxRings) {
const len = rings.length;
if (len <= 1)
return [rings];
const polygons = [];
let polygon;
let ccw;
for (const ring of rings) {
const area = calculateSignedArea(ring);
if (area === 0)
continue;
ring.area = Math.abs(area);
if (ccw === undefined)
ccw = area < 0;
if (ccw === area < 0) {
if (polygon)
polygons.push(polygon);
polygon = [ring];
}
else {
polygon.push(ring);
}
}
if (polygon)
polygons.push(polygon);
return polygons;
}
/**
* Returns the signed area for the polygon ring. Positive areas are exterior rings and
* have a clockwise winding. Negative areas are interior rings and have a counter clockwise
* ordering.
*
* @param ring - Exterior or interior ring
* @returns Signed area
*/
function calculateSignedArea(ring) {
let sum = 0;
for (let i = 0, len = ring.length, j = len - 1, p1, p2; i < len; j = i++) {
p1 = ring[i];
p2 = ring[j];
sum += (p2.x - p1.x) * (p1.y + p2.y);
}
return sum;
}
// This is taken from https://github.com/mapbox/cheap-ruler/ in order to take only the relevant parts
// Values that define WGS84 ellipsoid model of the Earth
const RE = 6378.137; // equatorial radius
const FE = 1 / 298.257223563; // flattening
const E2 = FE * (2 - FE);
const RAD = Math.PI / 180;
class CheapRuler {
constructor(lat) {
// Curvature formulas from https://en.wikipedia.org/wiki/Earth_radius#Meridional
const m = RAD * RE * 1000;
const coslat = Math.cos(lat * RAD);
const w2 = 1 / (1 - E2 * (1 - coslat * coslat));
const w = Math.sqrt(w2);
// multipliers for converting longitude and latitude degrees into distance
this.kx = m * w * coslat; // based on normal radius of curvature
this.ky = m * w * w2 * (1 - E2); // based on meridional radius of curvature
}
/**
* Given two points of the form [longitude, latitude], returns the distance.
*
* @param a - point [longitude, latitude]
* @param b - point [longitude, latitude]
* @returns distance
* @example
* const distance = ruler.distance([30.5, 50.5], [30.51, 50.49]);
* //=distance
*/
distance(a, b) {
const dx = this.wrap(a[0] - b[0]) * this.kx;
const dy = (a[1] - b[1]) * this.ky;
return Math.sqrt(dx * dx + dy * dy);
}
/**
* Returns an object of the form {point, index, t}, where point is closest point on the line
* from the given point, index is the start index of the segment with the closest point,
* and t is a parameter from 0 to 1 that indicates where the closest point is on that segment.
*
* @param line - an array of points that form the line
* @param p - point [longitude, latitude]
* @returns the nearest point, its index in the array and the proportion along the line
* @example
* const point = ruler.pointOnLine(line, [-67.04, 50.5]).point;
* //=point
*/
pointOnLine(line, p) {
let minDist = Infinity;
let minX, minY, minI, minT;
for (let i = 0; i < line.length - 1; i++) {
let x = line[i][0];
let y = line[i][1];
let dx = this.wrap(line[i + 1][0] - x) * this.kx;
let dy = (line[i + 1][1] - y) * this.ky;
let t = 0;
if (dx !== 0 || dy !== 0) {
t = (this.wrap(p[0] - x) * this.kx * dx + (p[1] - y) * this.ky * dy) / (dx * dx + dy * dy);
if (t > 1) {
x = line[i + 1][0];
y = line[i + 1][1];
}
else if (t > 0) {
x += (dx / this.kx) * t;
y += (dy / this.ky) * t;
}
}
dx = this.wrap(p[0] - x) * this.kx;
dy = (p[1] - y) * this.ky;
const sqDist = dx * dx + dy * dy;
if (sqDist < minDist) {
minDist = sqDist;
minX = x;
minY = y;
minI = i;
minT = t;
}
}
return {
point: [minX, minY],
index: minI,
t: Math.max(0, Math.min(1, minT))
};
}
wrap(deg) {
while (deg < -180)
deg += 360;
while (deg > 180)
deg -= 360;
return deg;
}
}
const MinPointsSize = 100;
const MinLinePointsSize = 50;
function compareDistPair(a, b) {
return b[0] - a[0];
}
function getRangeSize(range) {
return range[1] - range[0] + 1;
}
function isRangeSafe(range, threshold) {
return range[1] >= range[0] && range[1] < threshold;
}
function splitRange(range, isLine) {
if (range[0] > range[1]) {
return [null, null];
}
const size = getRangeSize(range);
if (isLine) {
if (size === 2) {
return [range, null];
}
const size1 = Math.floor(size / 2);
return [[range[0], range[0] + size1],
[range[0] + size1, range[1]]];
}
if (size === 1) {
return [range, null];
}
const size1 = Math.floor(size / 2) - 1;
return [[range[0], range[0] + size1],
[range[0] + size1 + 1, range[1]]];
}
function getBBox(coords, range) {
if (!isRangeSafe(range, coords.length)) {
return [Infinity, Infinity, -Infinity, -Infinity];
}
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (let i = range[0]; i <= range[1]; ++i) {
updateBBox(bbox, coords[i]);
}
return bbox;
}
function getPolygonBBox(polygon) {
const bbox = [Infinity, Infinity, -Infinity, -Infinity];
for (const ring of polygon) {
for (const coord of ring) {
updateBBox(bbox, coord);
}
}
return bbox;
}
function isValidBBox(bbox) {
return bbox[0] !== -Infinity && bbox[1] !== -Infinity && bbox[2] !== Infinity && bbox[3] !== Infinity;
}
// Calculate the distance between two bounding boxes.
// Calculate the delta in x and y direction, and use two fake points {0.0, 0.0}
// and {dx, dy} to calculate the distance. Distance will be 0.0 if bounding box are overlapping.
function bboxToBBoxDistance(bbox1, bbox2, ruler) {
if (!isValidBBox(bbox1) || !isValidBBox(bbox2)) {
return NaN;
}
let dx = 0.0;
let dy = 0.0;
// bbox1 in left side
if (bbox1[2] < bbox2[0]) {
dx = bbox2[0] - bbox1[2];
}
// bbox1 in right side
if (bbox1[0] > bbox2[2]) {
dx = bbox1[0] - bbox2[2];
}
// bbox1 in above side
if (bbox1[1] > bbox2[3]) {
dy = bbox1[1] - bbox2[3];
}
// bbox1 in down side
if (bbox1[3] < bbox2[1]) {
dy = bbox2[1] - bbox1[3];
}
return ruler.distance([0.0, 0.0], [dx, dy]);
}
function pointToLineDistance(point, line, ruler) {
const nearestPoint = ruler.pointOnLine(line, point);
return ruler.distance(point, nearestPoint.point);
}
function segmentToSegmentDistance(p1, p2, q1, q2, ruler) {
const dist1 = Math.min(pointToLineDistance(p1, [q1, q2], ruler), pointToLineDistance(p2, [q1, q2], ruler));
const dist2 = Math.min(pointToLineDistance(q1, [p1, p2], ruler), pointToLineDistance(q2, [p1, p2], ruler));
return Math.min(dist1, dist2);
}
function lineToLineDistance(line1, range1, line2, range2, ruler) {
const rangeSafe = isRangeSafe(range1, line1.length) && isRangeSafe(range2, line2.length);
if (!rangeSafe) {
return Infinity;
}
let dist = Infinity;
for (let i = range1[0]; i < range1[1]; ++i) {
const p1 = line1[i];
const p2 = line1[i + 1];
for (let j = range2[0]; j < range2[1]; ++j) {
const q1 = line2[j];
const q2 = line2[j + 1];
if (segmentIntersectSegment(p1, p2, q1, q2)) {
return 0.0;
}
dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
}
}
return dist;
}
function pointsToPointsDistance(points1, range1, points2, range2, ruler) {
const rangeSafe = isRangeSafe(range1, points1.length) && isRangeSafe(range2, points2.length);
if (!rangeSafe) {
return NaN;
}
let dist = Infinity;
for (let i = range1[0]; i <= range1[1]; ++i) {
for (let j = range2[0]; j <= range2[1]; ++j) {
dist = Math.min(dist, ruler.distance(points1[i], points2[j]));
if (dist === 0.0) {
return dist;
}
}
}
return dist;
}
function pointToPolygonDistance(point, polygon, ruler) {
if (pointWithinPolygon(point, polygon, true)) {
return 0.0;
}
let dist = Infinity;
for (const ring of polygon) {
const front = ring[0];
const back = ring[ring.length - 1];
if (front !== back) {
dist = Math.min(dist, pointToLineDistance(point, [back, front], ruler));
if (dist === 0.0) {
return dist;
}
}
const nearestPoint = ruler.pointOnLine(ring, point);
dist = Math.min(dist, ruler.distance(point, nearestPoint.point));
if (dist === 0.0) {
return dist;
}
}
return dist;
}
function lineToPolygonDistance(line, range, polygon, ruler) {
if (!isRangeSafe(range, line.length)) {
return NaN;
}
for (let i = range[0]; i <= range[1]; ++i) {
if (pointWithinPolygon(line[i], polygon, true)) {
return 0.0;
}
}
let dist = Infinity;
for (let i = range[0]; i < range[1]; ++i) {
const p1 = line[i];
const p2 = line[i + 1];
for (const ring of polygon) {
for (let j = 0, len = ring.length, k = len - 1; j < len; k = j++) {
const q1 = ring[k];
const q2 = ring[j];
if (segmentIntersectSegment(p1, p2, q1, q2)) {
return 0.0;
}
dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
}
}
}
return dist;
}
function polygonIntersect(poly1, poly2) {
for (const ring of poly1) {
for (const point of ring) {
if (pointWithinPolygon(point, poly2, true)) {
return true;
}
}
}
return false;
}
function polygonToPolygonDistance(polygon1, polygon2, ruler, currentMiniDist = Infinity) {
const bbox1 = getPolygonBBox(polygon1);
const bbox2 = getPolygonBBox(polygon2);
if (currentMiniDist !== Infinity && bboxToBBoxDistance(bbox1, bbox2, ruler) >= currentMiniDist) {
return currentMiniDist;
}
if (boxWithinBox(bbox1, bbox2)) {
if (polygonIntersect(polygon1, polygon2)) {
return 0.0;
}
}
else if (polygonIntersect(polygon2, polygon1)) {
return 0.0;
}
let dist = Infinity;
for (const ring1 of polygon1) {
for (let i = 0, len1 = ring1.length, l = len1 - 1; i < len1; l = i++) {
const p1 = ring1[l];
const p2 = ring1[i];
for (const ring2 of polygon2) {
for (let j = 0, len2 = ring2.length, k = len2 - 1; j < len2; k = j++) {
const q1 = ring2[k];
const q2 = ring2[j];
if (segmentIntersectSegment(p1, p2, q1, q2)) {
return 0.0;
}
dist = Math.min(dist, segmentToSegmentDistance(p1, p2, q1, q2, ruler));
}
}
}
}
return dist;
}
function updateQueue(distQueue, miniDist, ruler, points, polyBBox, rangeA) {
if (!rangeA) {
return;
}
const tempDist = bboxToBBoxDistance(getBBox(points, rangeA), polyBBox, ruler);
// Insert new pair to the queue if the bbox distance is less than
// miniDist, The pair with biggest distance will be at the top
if (tempDist < miniDist) {
distQueue.push([tempDist, rangeA, [0, 0]]);
}
}
function updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, range1, range2) {
if (!range1 || !range2) {
return;
}
const tempDist = bboxToBBoxDistance(getBBox(pointSet1, range1), getBBox(pointSet2, range2), ruler);
// Insert new pair to the queue if the bbox distance is less than
// miniDist, The pair with biggest distance will be at the top
if (tempDist < miniDist) {
distQueue.push([tempDist, range1, range2]);
}
}
// Divide and conquer, the time complexity is O(n*lgn), faster than Brute force
// O(n*n) Most of the time, use index for in-place processing.
function pointsToPolygonDistance(points, isLine, polygon, ruler, currentMiniDist = Infinity) {
let miniDist = Math.min(ruler.distance(points[0], polygon[0][0]), currentMiniDist);
if (miniDist === 0.0) {
return miniDist;
}
const distQueue = new TinyQueue([[0, [0, points.length - 1], [0, 0]]], compareDistPair);
const polyBBox = getPolygonBBox(polygon);
while (distQueue.length > 0) {
const distPair = distQueue.pop();
if (distPair[0] >= miniDist) {
continue;
}
const range = distPair[1];
// In case the set size are relatively small, we could use brute-force directly
const threshold = isLine ? MinLinePointsSize : MinPointsSize;
if (getRangeSize(range) <= threshold) {
if (!isRangeSafe(range, points.length)) {
return NaN;
}
if (isLine) {
const tempDist = lineToPolygonDistance(points, range, polygon, ruler);
if (isNaN(tempDist) || tempDist === 0.0) {
return tempDist;
}
miniDist = Math.min(miniDist, tempDist);
}
else {
for (let i = range[0]; i <= range[1]; ++i) {
const tempDist = pointToPolygonDistance(points[i], polygon, ruler);
miniDist = Math.min(miniDist, tempDist);
if (miniDist === 0.0) {
return 0.0;
}
}
}
}
else {
const newRangesA = splitRange(range, isLine);
updateQueue(distQueue, miniDist, ruler, points, polyBBox, newRangesA[0]);
updateQueue(distQueue, miniDist, ruler, points, polyBBox, newRangesA[1]);
}
}
return miniDist;
}
function pointSetToPointSetDistance(pointSet1, isLine1, pointSet2, isLine2, ruler, currentMiniDist = Infinity) {
let miniDist = Math.min(currentMiniDist, ruler.distance(pointSet1[0], pointSet2[0]));
if (miniDist === 0.0) {
return miniDist;
}
const distQueue = new TinyQueue([[0, [0, pointSet1.length - 1], [0, pointSet2.length - 1]]], compareDistPair);
while (distQueue.length > 0) {
const distPair = distQueue.pop();
if (distPair[0] >= miniDist) {
continue;
}
const rangeA = distPair[1];
const rangeB = distPair[2];
const threshold1 = isLine1 ? MinLinePointsSize : MinPointsSize;
const threshold2 = isLine2 ? MinLinePointsSize : MinPointsSize;
// In case the set size are relatively small, we could use brute-force directly
if (getRangeSize(rangeA) <= threshold1 && getRangeSize(rangeB) <= threshold2) {
if (!isRangeSafe(rangeA, pointSet1.length) && isRangeSafe(rangeB, pointSet2.length)) {
return NaN;
}
let tempDist;
if (isLine1 && isLine2) {
tempDist = lineToLineDistance(pointSet1, rangeA, pointSet2, rangeB, ruler);
miniDist = Math.min(miniDist, tempDist);
}
else if (isLine1 && !isLine2) {
const sublibe = pointSet1.slice(rangeA[0], rangeA[1] + 1);
for (let i = rangeB[0]; i <= rangeB[1]; ++i) {
tempDist = pointToLineDistance(pointSet2[i], sublibe, ruler);
miniDist = Math.min(miniDist, tempDist);
if (miniDist === 0.0) {
return miniDist;
}
}
}
else if (!isLine1 && isLine2) {
const sublibe = pointSet2.slice(rangeB[0], rangeB[1] + 1);
for (let i = rangeA[0]; i <= rangeA[1]; ++i) {
tempDist = pointToLineDistance(pointSet1[i], sublibe, ruler);
miniDist = Math.min(miniDist, tempDist);
if (miniDist === 0.0) {
return miniDist;
}
}
}
else {
tempDist = pointsToPointsDistance(pointSet1, rangeA, pointSet2, rangeB, ruler);
miniDist = Math.min(miniDist, tempDist);
}
}
else {
const newRangesA = splitRange(rangeA, isLine1);
const newRangesB = splitRange(rangeB, isLine2);
updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[0]);
updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[0], newRangesB[1]);
updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[0]);
updateQueueTwoSets(distQueue, miniDist, ruler, pointSet1, pointSet2, newRangesA[1], newRangesB[1]);
}
}
return miniDist;
}
function pointToGeometryDistance(ctx, geometries) {
const tilePoints = ctx.geometry();
const pointPosition = tilePoints.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
if (tilePoints.length === 0) {
return NaN;
}
const ruler = new CheapRuler(pointPosition[0][1]);
let dist = Infinity;
for (const geometry of geometries) {
switch (geometry.type) {
case 'Point':
dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, [geometry.coordinates], false, ruler, dist));
break;
case 'LineString':
dist = Math.min(dist, pointSetToPointSetDistance(pointPosition, false, geometry.coordinates, true, ruler, dist));
break;
case 'Polygon':
dist = Math.min(dist, pointsToPolygonDistance(pointPosition, false, geometry.coordinates, ruler, dist));
break;
}
if (dist === 0.0) {
return dist;
}
}
return dist;
}
function lineStringToGeometryDistance(ctx, geometries) {
const tileLine = ctx.geometry();
const linePositions = tileLine.flat().map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
if (tileLine.length === 0) {
return NaN;
}
const ruler = new CheapRuler(linePositions[0][1]);
let dist = Infinity;
for (const geometry of geometries) {
switch (geometry.type) {
case 'Point':
dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, [geometry.coordinates], false, ruler, dist));
break;
case 'LineString':
dist = Math.min(dist, pointSetToPointSetDistance(linePositions, true, geometry.coordinates, true, ruler, dist));
break;
case 'Polygon':
dist = Math.min(dist, pointsToPolygonDistance(linePositions, true, geometry.coordinates, ruler, dist));
break;
}
if (dist === 0.0) {
return dist;
}
}
return dist;
}
function polygonToGeometryDistance(ctx, geometries) {
const tilePolygon = ctx.geometry();
if (tilePolygon.length === 0 || tilePolygon[0].length === 0) {
return NaN;
}
const polygons = classifyRings(tilePolygon).map(polygon => {
return polygon.map(ring => {
return ring.map(p => getLngLatFromTileCoord([p.x, p.y], ctx.canonical));
});
});
const ruler = new CheapRuler(polygons[0][0][0][1]);
let dist = Infinity;
for (const geometry of geometries) {
for (const polygon of polygons) {
switch (geometry.type) {
case 'Point':
dist = Math.min(dist, pointsToPolygonDistance([geometry.coordinates], false, polygon, ruler, dist));
break;
case 'LineString':
dist = Math.min(dist, pointsToPolygonDistance(geometry.coordinates, true, polygon, ruler, dist));
break;
case 'Polygon':
dist = Math.min(dist, polygonToPolygonDistance(polygon, geometry.coordinates, ruler, dist));
break;
}
if (dist === 0.0) {
return dist;
}
}
}
return dist;
}
function toSimpleGeometry(geometry) {
if (geometry.type === 'MultiPolygon') {
return geometry.coordinates.map(polygon => {
return {
type: 'Polygon',
coordinates: polygon
};
});
}
if (geometry.type === 'MultiLineString') {
return geometry.coordinates.map(lineString => {
return {
type: 'LineString',
coordinates: lineString
};
});
}
if (geometry.type === 'MultiPoint') {
return geometry.coordinates.map(point => {
return {
type: 'Point',
coordinates: point
};
});
}
return [geometry];
}
class Distance {
constructor(geojson, geometries) {
this.type = NumberType;
this.geojson = geojson;
this.geometries = geometries;
}
static parse(args, context) {
if (args.length !== 2)
return context.error(`'distance' expression requires exactly one argument, but found ${args.length - 1} instead.`);
if (isValue(args[1])) {
const geojson = args[1];
if (geojson.type === 'FeatureCollection') {
return new Distance(geojson, geojson.features.map(feature => toSimpleGeometry(feature.geometry)).flat());
}
else if (geojson.type === 'Feature') {
return new Distance(geojson, toSimpleGeometry(geojson.geometry));
}
else if ('type' in geojson && 'coordinates' in geojson) {
return new Distance(geojson, toSimpleGeometry(geojson));
}
}
return context.error('\'distance\' expression requires valid geojson object that contains polygon geometry type.');
}
evaluate(ctx) {
if (ctx.geometry() != null && ctx.canonicalID() != null) {
if (ctx.geometryType() === 'Point') {
return pointToGeometryDistance(ctx, this.geometries);
}
else if (ctx.geometryType() === 'LineString') {
return lineStringToGeometryDistance(ctx, this.geometries);
}
else if (ctx.geometryType() === 'Polygon') {
return polygonToGeometryDistance(ctx, this.geometries);
}
}
return NaN;
}
eachChild() { }
outputDefined() {
return true;
}
}
class GlobalState {
constructor(key) {
this.type = ValueType;
this.key = key;
}
static parse(args, context) {
if (args.length !== 2) {
return context.error(`Expected 1 argument, but found ${args.length - 1} instead.`);
}
const key = args[1];
if (key === undefined || key === null) {
return context.error('Global state property must be defined.');
}
if (typeof key !== 'string') {
return context.error(`Global state property must be string, but found ${typeof args[1]} instead.`);
}
return new GlobalState(key);
}
evaluate(ctx) {
var _a;
const globalState = (_a = ctx.globals) === null || _a === void 0 ? void 0 : _a.globalState;
if (!globalState || Object.keys(globalState).length === 0)
return null;
return getOwn(globalState, this.key);
}
eachChild() { }
outputDefined() {
return false;
}
}
const expressions = {
// 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,
'distance': Distance,
'global-state': GlobalState
};
class CompoundExpression {
constructor(name, type, evaluate, args) {
this.name = name;
this.type = type;
this._evaluate = evaluate;
this.args = args;
}
evaluate(ctx) {
return this._evaluate(ctx, this.args);
}
eachChild(fn) {
this.args.forEach(fn);
}
outputDefined() {
return false;
}
static parse(args, context) {
const op = args[0];
const definition = CompoundExpression.definitions[op];
if (!definition) {
return context.error(`Unknown expression "${op}". If you wanted a literal array, use ["literal", [...]].`, 0);
}
// Now check argument types against each signature
const type = Array.isArray(definition) ?
definition[0] : definition.type;
const availableOverloads = Array.isArray(definition) ?
[[definition[1], definition[2]]] :
definition.overloads;
const overloads = availableOverloads.filter(([signature]) => (!Array.isArray(signature) || // varags
signature.length === args.length - 1 // correct param count
));
let signatureContext = null;
for (const [params, evaluate] of overloads) {
// Use a fresh context for each attempted signature so that, if
// we eventually succeed, we haven't polluted `context.errors`.
signatureContext = new ParsingContext(context.registry, isExpressionConstant, context.path, null, context.scope);
// First parse all the args, potentially coercing to the
// types expected by this overload.
const parsedArgs = [];
let argParseFailed = false;
for (let i = 1; i < args.length; i++) {
const arg = args[i];
const expectedType = Array.isArray(params) ?
params[i - 1] :
params.type;
const parsed = signatureContext.parse(arg, 1 + parsedArgs.length, expectedType);
if (!parsed) {
argParseFailed = true;
break;
}
parsedArgs.push(parsed);
}
if (argParseFailed) {
// Couldn't coerce args of this overload to expected type, move
// on to next one.
continue;
}
if (Array.isArray(params)) {
if (params.length !== parsedArgs.length) {
signatureContext.error(`Expected ${params.length} arguments, but found ${parsedArgs.length} instead.`);
continue;
}
}
for (let i = 0; i < parsedArgs.length; i++) {
const expected = Array.isArray(params) ? params[i] : params.type;
const arg = parsedArgs[i];
signatureContext.concat(i + 1).checkSubtype(expected, arg.type);
}
if (signatureContext.errors.length === 0) {
return new CompoundExpression(op, type, evaluate, parsedArgs);
}
}
if (overloads.length === 1) {
context.errors.push(...signatureContext.errors);
}
else {
const expected = overloads.length ? overloads : availableOverloads;
const signatures = expected
.map(([params]) => stringifySignature(params))
.join(' | ');
const actualTypes = [];
// For error message, re-parse arguments without trying to
// apply any coercions
for (let i = 1; i < args.length; i++) {
const parsed = context.parse(args[i], 1 + actualTypes.length);
if (!parsed)
return null;
actualTypes.push(typeToString(parsed.type));
}
context.error(`Expected arguments of type ${signatures}, but found (${actualTypes.join(', ')}) instead.`);
}
return null;
}
static register(registry, definitions) {
CompoundExpression.definitions = definitions;
for (const name in definitions) {
registry[name] = CompoundExpression;
}
}
}
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) {
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
],
'elevation': [
NumberType,
[],
(ctx) => ctx.globals.elevation || 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.value] === v.value
],
'filter-id-==': [
BooleanType,
[ValueType],
(ctx, [v]) => ctx.id() === v.value
],
'filter-type-==': [
BooleanType,
[StringType],
(ctx, [v]) => ctx.geometryType() === v.value
],
'filter-<': [
BooleanType,
[StringType, ValueType],
(ctx, [k, v]) => {
const a = ctx.properties()[k.value];
const b = v.value;
return typeof a === typeof b && a < b;
}
],
'filter-id-<': [
BooleanType,
[ValueType],
(ctx, [v]) => {
const a = ctx.id();
const b = v.value;
return typeof a === typeof b && a < b;
}
],
'filter->': [
BooleanType,
[StringType, ValueType],
(ctx, [k, v]) => {
const a = ctx.properties()[k.value];
const b = v.value;
return typeof a === typeof b && a > b;
}
],
'filter-id->': [
BooleanType,
[ValueType],
(ctx, [v]) => {
const a = ctx.id();
const b = v.value;
return typeof a === typeof b && a > b;
}
],
'filter-<=': [
BooleanType,
[StringType, ValueType],
(ctx, [k, v]) => {
const a = ctx.properties()[k.value];
const b = v.value;
return typeof a === typeof b && a <= b;
}
],
'filter-id-<=': [
BooleanType,
[ValueType],
(ctx, [v]) => {
const a = ctx.id();
const b = v.value;
return typeof a === typeof b && a <= b;
}
],
'filter->=': [
BooleanType,
[StringType, ValueType],
(ctx, [k, v]) => {
const a = ctx.properties()[k.value];
const b = v.value;
return typeof a === typeof b && a >= b;
}
],
'filter-id->=': [
BooleanType,
[ValueType],
(ctx, [v]) => {
const a = ctx.id();
const b = v.value;
return typeof a === typeof b && a >= b;
}
],
'filter-has': [
BooleanType,
[ValueType],
(ctx, [k]) => k.value in ctx.properties()
],
'filter-has-id': [
BooleanType,
[],
(ctx) => (ctx.id() !== null && ctx.id() !== undefined)
],
'filter-type-in': [
BooleanType,
[array(StringType)],
(ctx, [v]) => v.value.indexOf(ctx.geometryType()) >= 0
],
'filter-id-in': [
BooleanType,
[array(ValueType)],
(ctx, [v]) => v.value.indexOf(ctx.id()) >= 0
],
'filter-in-small': [
BooleanType,
[StringType, array(ValueType)],
// assumes v is an array literal
(ctx, [k, v]) => v.value.indexOf(ctx.properties()[k.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.value], v.value, 0, v.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) {
if (Array.isArray(signature)) {
return `(${signature.map(typeToString).join(', ')})`;
}
else {
return `(${typeToString(signature.type)}...)`;
}
}
function isExpressionConstant(expression) {
if (expression instanceof Var) {
return isExpressionConstant(expression.boundExpression);
}
else if (expression instanceof CompoundExpression && expression.name === 'error') {
return false;
}
else if (expression instanceof CollatorExpression) {
// Although the results of a Collator expression with fixed arguments
// generally shouldn't change between executions, we can't serialize them
// as constant expressions because results change based on environment.
return false;
}
else if (expression instanceof Within) {
return false;
}
else if (expression instanceof Distance) {
return false;
}
else if (expression instanceof GlobalState) {
return false;
}
const isTypeAnnotation = expression instanceof Coercion ||
expression instanceof Assertion;
let childrenConstant = true;
expression.eachChild(child => {
// We can _almost_ assume that if `expressions` children are constant,
// they would already have been evaluated to Literal values when they
// were parsed. Type annotations are the exception, because they might
// have been inferred and added after a child was parsed.
// So we recurse into isConstant() for the children of type annotations,
// but otherwise simply check whether they are Literals.
if (isTypeAnnotation) {
childrenConstant = childrenConstant && isExpressionConstant(child);
}
else {
childrenConstant = childrenConstant && child instanceof Literal;
}
});
if (!childrenConstant) {
return false;
}
return isFeatureConstant(expression) &&
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density', 'elevation', 'line-progress', 'accumulated', 'is-supported-script']);
}
function isFeatureConstant(e) {
if (e instanceof CompoundExpression) {
if (e.name === 'get' && e.args.length === 1) {
return false;
}
else if (e.name === 'feature-state') {
return false;
}
else if (e.name === 'has' && e.args.length === 1) {
return false;
}
else if (e.name === 'properties' ||
e.name === 'geometry-type' ||
e.name === 'id') {
return false;
}
else if (/^filter-/.test(e.name)) {
return false;
}
}
if (e instanceof Within) {
return false;
}
if (e instanceof Distance) {
return false;
}
let result = true;
e.eachChild(arg => {
if (result && !isFeatureConstant(arg)) {
result = false;
}
});
return result;
}
function isStateConstant(e) {
if (e instanceof CompoundExpression) {
if (e.name === 'feature-state') {
return false;
}
}
let result = true;
e.eachChild(arg => {
if (result && !isStateConstant(arg)) {
result = false;
}
});
return result;
}
function isGlobalPropertyConstant(e, properties) {
if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) {
return false;
}
let result = true;
e.eachChild((arg) => {
if (result && !isGlobalPropertyConstant(arg, properties)) {
result = false;
}
});
return result;
}
function success(value) {
return { result: 'success', value };
}
function error(value) {
return { result: 'error', value };
}
function supportsPropertyExpression(spec) {
return spec['property-type'] === 'data-driven' || spec['property-type'] === 'cross-faded-data-driven';
}
function supportsZoomExpression(spec) {
return !!spec.expression && spec.expression.parameters.indexOf('zoom') > -1;
}
function supportsInterpolation(spec) {
return !!spec.expression && spec.expression.interpolated;
}
function getType(val) {
if (val instanceof Number) {
return 'number';
}
else if (val instanceof String) {
return 'string';
}
else if (val instanceof Boolean) {
return 'boolean';
}
else if (Array.isArray(val)) {
return 'array';
}
else if (val === null) {
return 'null';
}
else {
return typeof val;
}
}
function isFunction(value) {
return typeof value === 'object' && value !== null && !Array.isArray(value) && typeOf(value) === ObjectType;
}
class StyleExpression {
constructor(expression, propertySpec, globalState) {
this.expression = expression;
this._warningHistory = {};
this._evaluator = new EvaluationContext();
this._defaultValue = propertySpec ? getDefaultValue(propertySpec) : null;
this._enumValues = propertySpec && propertySpec.type === 'enum' ? propertySpec.values : null;
this._globalState = globalState;
}
evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
this._evaluator.globals = globals;
this._evaluator.feature = feature;
this._evaluator.featureState = featureState;
this._evaluator.canonical = canonical;
this._evaluator.availableImages = availableImages || null;
this._evaluator.formattedSection = formattedSection;
return this.expression.evaluate(this._evaluator);
}
evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
this._evaluator.globals = globals;
this._evaluator.feature = feature || null;
this._evaluator.featureState = featureState || null;
this._evaluator.canonical = canonical;
this._evaluator.availableImages = availableImages || null;
this._evaluator.formattedSection = formattedSection || null;
try {
const val = this.expression.evaluate(this._evaluator);
if (val === null || val === undefined || (typeof val === 'number' && val !== val)) {
return this._defaultValue;
}
if (this._enumValues && !(val in this._enumValues)) {
throw new RuntimeError(`Expected value to be one of ${Object.keys(this._enumValues).map(v => JSON.stringify(v)).join(', ')}, but found ${JSON.stringify(val)} instead.`);
}
return val;
}
catch (e) {
if (!this._warningHistory[e.message]) {
this._warningHistory[e.message] = true;
if (typeof console !== 'undefined') {
console.warn(e.message);
}
}
return this._defaultValue;
}
}
}
function isExpression(expression) {
return Array.isArray(expression) && expression.length > 0 &&
typeof expression[0] === 'string' && expression[0] in expressions;
}
/**
* Parse and typecheck the given style spec JSON expression. If
* options.defaultValue is provided, then the resulting StyleExpression's
* `evaluate()` method will handle errors by logging a warning (once per
* message) and returning the default value. Otherwise, it will throw
* evaluation errors.
*
* @private
*/
function createExpression(expression, propertySpec, globalState) {
const parser = new ParsingContext(expressions, isExpressionConstant, [], propertySpec ? getExpectedType(propertySpec) : undefined);
// For string-valued properties, coerce to string at the top level rather than asserting.
const parsed = parser.parse(expression, undefined, undefined, undefined, propertySpec && propertySpec.type === 'string' ? { typeAnnotation: 'coerce' } : undefined);
if (!parsed) {
return error(parser.errors);
}
return success(new StyleExpression(parsed, propertySpec, globalState));
}
class ZoomConstantExpression {
constructor(kind, expression, globalState) {
this.kind = kind;
this._styleExpression = expression;
this.isStateDependent = kind !== 'constant' && !isStateConstant(expression.expression);
this.globalStateRefs = findGlobalStateRefs(expression.expression);
this._globalState = globalState;
}
evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
}
evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
}
}
class ZoomDependentExpression {
constructor(kind, expression, zoomStops, interpolationType, globalState) {
this.kind = kind;
this.zoomStops = zoomStops;
this._styleExpression = expression;
this.isStateDependent = kind !== 'camera' && !isStateConstant(expression.expression);
this.globalStateRefs = findGlobalStateRefs(expression.expression);
this.interpolationType = interpolationType;
this._globalState = globalState;
}
evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
return this._styleExpression.evaluateWithoutErrorHandling(globals, feature, featureState, canonical, availableImages, formattedSection);
}
evaluate(globals, feature, featureState, canonical, availableImages, formattedSection) {
if (this._globalState) {
globals = addGlobalState(globals, this._globalState);
}
return this._styleExpression.evaluate(globals, feature, featureState, canonical, availableImages, formattedSection);
}
interpolationFactor(input, lower, upper) {
if (this.interpolationType) {
return Interpolate.interpolationFactor(this.interpolationType, input, lower, upper);
}
else {
return 0;
}
}
}
function createPropertyExpression(expressionInput, propertySpec, globalState) {
const expression = createExpression(expressionInput, propertySpec, globalState);
if (expression.result === 'error') {
return expression;
}
const parsed = expression.value.expression;
const isFeatureConstantResult = isFeatureConstant(parsed);
if (!isFeatureConstantResult && !supportsPropertyExpression(propertySpec)) {
return error([new ExpressionParsingError('', 'data expressions not supported')]);
}
const isZoomConstant = isGlobalPropertyConstant(parsed, ['zoom']);
if (!isZoomConstant && !supportsZoomExpression(propertySpec)) {
return error([new ExpressionParsingError('', 'zoom expressions not supported')]);
}
const zoomCurve = findZoomCurve(parsed);
if (!zoomCurve && !isZoomConstant) {
return error([new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.')]);
}
else if (zoomCurve instanceof ExpressionParsingError) {
return error([zoomCurve]);
}
else if (zoomCurve instanceof Interpolate && !supportsInterpolation(propertySpec)) {
return error([new ExpressionParsingError('', '"interpolate" expressions cannot be used with this property')]);
}
if (!zoomCurve) {
return success(isFeatureConstantResult ?
new ZoomConstantExpression('constant', expression.value, globalState) :
new ZoomConstantExpression('source', expression.value, globalState));
}
const interpolationType = zoomCurve instanceof Interpolate ? zoomCurve.interpolation : undefined;
return success(isFeatureConstantResult ?
new ZoomDependentExpression('camera', expression.value, zoomCurve.labels, interpolationType, globalState) :
new ZoomDependentExpression('composite', expression.value, zoomCurve.labels, interpolationType, globalState));
}
// Zoom-dependent expressions may only use ["zoom"] as the input to a top-level "step" or "interpolate"
// expression (collectively referred to as a "curve"). The curve may be wrapped in one or more "let" or
// "coalesce" expressions.
function findZoomCurve(expression) {
let result = null;
if (expression instanceof Let) {
result = findZoomCurve(expression.result);
}
else if (expression instanceof Coalesce) {
for (const arg of expression.args) {
result = findZoomCurve(arg);
if (result) {
break;
}
}
}
else if ((expression instanceof Step || expression instanceof Interpolate) &&
expression.input instanceof CompoundExpression &&
expression.input.name === 'zoom') {
result = expression;
}
if (result instanceof ExpressionParsingError) {
return result;
}
expression.eachChild((child) => {
const childResult = findZoomCurve(child);
if (childResult instanceof ExpressionParsingError) {
result = childResult;
}
else if (!result && childResult) {
result = new ExpressionParsingError('', '"zoom" expression may only be used as input to a top-level "step" or "interpolate" expression.');
}
else if (result && childResult && result !== childResult) {
result = new ExpressionParsingError('', 'Only one zoom-based "step" or "interpolate" subexpression may be used in an expression.');
}
});
return result;
}
function findGlobalStateRefs(expression, results = new Set()) {
if (expression instanceof GlobalState) {
results.add(expression.key);
}
expression.eachChild(childExpression => {
findGlobalStateRefs(childExpression, results);
});
return results;
}
function getExpectedType(spec) {
const types = {
color: ColorType,
string: StringType,
number: NumberType,
enum: StringType,
boolean: BooleanType,
formatted: FormattedType,
padding: PaddingType,
numberArray: NumberArrayType,
colorArray: ColorArrayType,
projectionDefinition: ProjectionDefinitionType,
resolvedImage: ResolvedImageType,
variableAnchorOffsetCollection: VariableAnchorOffsetCollectionType
};
if (spec.type === 'array') {
return array(types[spec.value] || ValueType, spec.length);
}
return types[spec.type];
}
function getDefaultValue(spec) {
if (spec.type === 'color' && isFunction(spec.default)) {
// Special case for heatmap-color: it uses the 'default:' to define a
// default color ramp, but createExpression expects a simple value to fall
// back to in case of runtime errors
return new Color(0, 0, 0, 0);
}
switch (spec.type) {
case 'color':
return Color.parse(spec.default) || null;
case 'padding':
return Padding.parse(spec.default) || null;
case 'numberArray':
return NumberArray.parse(spec.default) || null;
case 'colorArray':
return ColorArray.parse(spec.default) || null;
case 'variableAnchorOffsetCollection':
return VariableAnchorOffsetCollection.parse(spec.default) || null;
case 'projectionDefinition':
return ProjectionDefinition.parse(spec.default) || null;
default:
return (spec.default === undefined ? null : spec.default);
}
}
function addGlobalState(globals, globalState) {
const { zoom, heatmapDensity, elevation, lineProgress, isSupportedScript, accumulated } = globals !== null && globals !== void 0 ? globals : {};
return {
zoom,
heatmapDensity,
elevation,
lineProgress,
isSupportedScript,
accumulated,
globalState
};
}
function validateObject(options) {
const key = options.key;
const object = options.value;
const elementSpecs = options.valueSpec || {};
const elementValidators = options.objectElementValidators || {};
const style = options.style;
const styleSpec = options.styleSpec;
const validateSpec = options.validateSpec;
let errors = [];
const type = getType(object);
if (type !== 'object') {
return [new ValidationError(key, object, `object expected, ${type} found`)];
}
for (const objectKey in object) {
const elementSpecKey = objectKey.split('.')[0]; // treat 'paint.*' as 'paint'
// objectKey comes from the user controlled style input, so elementSpecKey may be e.g. "__proto__"
const elementSpec = getOwn(elementSpecs, elementSpecKey) || elementSpecs['*'];
let validateElement;
if (getOwn(elementValidators, elementSpecKey)) {
validateElement = elementValidators[elementSpecKey];
}
else if (getOwn(elementSpecs, elementSpecKey)) {
validateElement = validateSpec;
}
else if (elementValidators['*']) {
validateElement = elementValidators['*'];
}
else if (elementSpecs['*']) {
validateElement = validateSpec;
}
else {
errors.push(new ValidationError(key, object[objectKey], `unknown property "${objectKey}"`));
continue;
}
errors = errors.concat(validateElement({
key: (key ? `${key}.` : key) + objectKey,
value: object[objectKey],
valueSpec: elementSpec,
style,
styleSpec,
object,
objectKey,
validateSpec,
}, object));
}
for (const elementSpecKey in elementSpecs) {
// Don't check `required` when there's a custom validator for that property.
if (elementValidators[elementSpecKey]) {
continue;
}
if (elementSpecs[elementSpecKey].required && elementSpecs[elementSpecKey]['default'] === undefined && object[elementSpecKey] === undefined) {
errors.push(new ValidationError(key, object, `missing required property "${elementSpecKey}"`));
}
}
return errors;
}
function validateArray(options) {
const array = options.value;
const arraySpec = options.valueSpec;
const validateSpec = options.validateSpec;
const style = options.style;
const styleSpec = options.styleSpec;
const key = options.key;
const validateArrayElement = options.arrayElementValidator || validateSpec;
if (getType(array) !== 'array') {
return [new ValidationError(key, array, `array expected, ${getType(array)} found`)];
}
if (arraySpec.length && array.length !== arraySpec.length) {
return [new ValidationError(key, array, `array length ${arraySpec.length} expected, length ${array.length} found`)];
}
if (arraySpec['min-length'] && array.length < arraySpec['min-length']) {
return [new ValidationError(key, array, `array length at least ${arraySpec['min-length']} expected, length ${array.length} found`)];
}
let arrayElementSpec = {
'type': arraySpec.value,
'values': arraySpec.values
};
if (styleSpec.$version < 7) {
arrayElementSpec['function'] = arraySpec.function;
}
if (getType(arraySpec.value) === 'object') {
arrayElementSpec = arraySpec.value;
}
let errors = [];
for (let i = 0; i < array.length; i++) {
errors = errors.concat(validateArrayElement({
array,
arrayIndex: i,
value: array[i],
valueSpec: arrayElementSpec,
validateSpec: options.validateSpec,
style,
styleSpec,
key: `${key}[${i}]`
}));
}
return errors;
}
function validateNumber(options) {
const key = options.key;
const value = options.value;
const valueSpec = options.valueSpec;
let type = getType(value);
if (type === 'number' && value !== value) {
type = 'NaN';
}
if (type !== 'number') {
return [new ValidationError(key, value, `number expected, ${type} found`)];
}
if ('minimum' in valueSpec && value < valueSpec.minimum) {
return [new ValidationError(key, value, `${value} is less than the minimum value ${valueSpec.minimum}`)];
}
if ('maximum' in valueSpec && value > valueSpec.maximum) {
return [new ValidationError(key, value, `${value} is greater than the maximum value ${valueSpec.maximum}`)];
}
return [];
}
function validateFunction(options) {
const functionValueSpec = options.valueSpec;
const functionType = unbundle(options.value.type);
let stopKeyType;
let stopDomainValues = {};
let previousStopDomainValue;
let previousStopDomainZoom;
const isZoomFunction = functionType !== 'categorical' && options.value.property === undefined;
const isPropertyFunction = !isZoomFunction;
const isZoomAndPropertyFunction = getType(options.value.stops) === 'array' &&
getType(options.value.stops[0]) === 'array' &&
getType(options.value.stops[0][0]) === 'object';
const errors = validateObject({
key: options.key,
value: options.value,
valueSpec: options.styleSpec.function,
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec,
objectElementValidators: {
stops: validateFunctionStops,
default: validateFunctionDefault
}
});
if (functionType === 'identity' && isZoomFunction) {
errors.push(new ValidationError(options.key, options.value, 'missing required property "property"'));
}
if (functionType !== 'identity' && !options.value.stops) {
errors.push(new ValidationError(options.key, options.value, 'missing required property "stops"'));
}
if (functionType === 'exponential' && options.valueSpec.expression && !supportsInterpolation(options.valueSpec)) {
errors.push(new ValidationError(options.key, options.value, 'exponential functions not supported'));
}
if (options.styleSpec.$version >= 8) {
if (isPropertyFunction && !supportsPropertyExpression(options.valueSpec)) {
errors.push(new ValidationError(options.key, options.value, 'property functions not supported'));
}
else if (isZoomFunction && !supportsZoomExpression(options.valueSpec)) {
errors.push(new ValidationError(options.key, options.value, 'zoom functions not supported'));
}
}
if ((functionType === 'categorical' || isZoomAndPropertyFunction) && options.value.property === undefined) {
errors.push(new ValidationError(options.key, options.value, '"property" property is required'));
}
return errors;
function validateFunctionStops(options) {
if (functionType === 'identity') {
return [new ValidationError(options.key, options.value, 'identity function may not have a "stops" property')];
}
let errors = [];
const value = options.value;
errors = errors.concat(validateArray({
key: options.key,
value,
valueSpec: options.valueSpec,
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec,
arrayElementValidator: validateFunctionStop
}));
if (getType(value) === 'array' && value.length === 0) {
errors.push(new ValidationError(options.key, value, 'array must have at least one stop'));
}
return errors;
}
function validateFunctionStop(options) {
let errors = [];
const value = options.value;
const key = options.key;
if (getType(value) !== 'array') {
return [new ValidationError(key, value, `array expected, ${getType(value)} found`)];
}
if (value.length !== 2) {
return [new ValidationError(key, value, `array length 2 expected, length ${value.length} found`)];
}
if (isZoomAndPropertyFunction) {
if (getType(value[0]) !== 'object') {
return [new ValidationError(key, value, `object expected, ${getType(value[0])} found`)];
}
if (value[0].zoom === undefined) {
return [new ValidationError(key, value, 'object stop key must have zoom')];
}
if (value[0].value === undefined) {
return [new ValidationError(key, value, 'object stop key must have value')];
}
if (previousStopDomainZoom && previousStopDomainZoom > unbundle(value[0].zoom)) {
return [new ValidationError(key, value[0].zoom, 'stop zoom values must appear in ascending order')];
}
if (unbundle(value[0].zoom) !== previousStopDomainZoom) {
previousStopDomainZoom = unbundle(value[0].zoom);
previousStopDomainValue = undefined;
stopDomainValues = {};
}
errors = errors.concat(validateObject({
key: `${key}[0]`,
value: value[0],
valueSpec: { zoom: {} },
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec,
objectElementValidators: { zoom: validateNumber, value: validateStopDomainValue }
}));
}
else {
errors = errors.concat(validateStopDomainValue({
key: `${key}[0]`,
value: value[0],
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec
}, value));
}
if (isExpression(deepUnbundle(value[1]))) {
return errors.concat([new ValidationError(`${key}[1]`, value[1], 'expressions are not allowed in function stops.')]);
}
return errors.concat(options.validateSpec({
key: `${key}[1]`,
value: value[1],
valueSpec: functionValueSpec,
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec
}));
}
function validateStopDomainValue(options, stop) {
const type = getType(options.value);
const value = unbundle(options.value);
const reportValue = options.value !== null ? options.value : stop;
if (!stopKeyType) {
stopKeyType = type;
}
else if (type !== stopKeyType) {
return [new ValidationError(options.key, reportValue, `${type} stop domain type must match previous stop domain type ${stopKeyType}`)];
}
if (type !== 'number' && type !== 'string' && type !== 'boolean') {
return [new ValidationError(options.key, reportValue, 'stop domain value must be a number, string, or boolean')];
}
if (type !== 'number' && functionType !== 'categorical') {
let message = `number expected, ${type} found`;
if (supportsPropertyExpression(functionValueSpec) && functionType === undefined) {
message += '\nIf you intended to use a categorical function, specify `"type": "categorical"`.';
}
return [new ValidationError(options.key, reportValue, message)];
}
if (functionType === 'categorical' && type === 'number' && (!isFinite(value) || Math.floor(value) !== value)) {
return [new ValidationError(options.key, reportValue, `integer expected, found ${value}`)];
}
if (functionType !== 'categorical' && type === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) {
return [new ValidationError(options.key, reportValue, 'stop domain values must appear in ascending order')];
}
else {
previousStopDomainValue = value;
}
if (functionType === 'categorical' && value in stopDomainValues) {
return [new ValidationError(options.key, reportValue, 'stop domain values must be unique')];
}
else {
stopDomainValues[value] = true;
}
return [];
}
function validateFunctionDefault(options) {
return options.validateSpec({
key: options.key,
value: options.value,
valueSpec: functionValueSpec,
validateSpec: options.validateSpec,
style: options.style,
styleSpec: options.styleSpec
});
}
}
function validateExpression(options) {
const expression = (options.expressionContext === 'property' ? createPropertyExpression : createExpression)(deepUnbundle(options.value), options.valueSpec);
if (expression.result === 'error') {
return expression.value.map((error) => {
return new ValidationError(`${options.key}${error.key}`, options.value, error.message);
});
}
const expressionObj = expression.value.expression || expression.value._styleExpression.expression;
if (options.expressionContext === 'property' && (options.propertyKey === 'text-font') &&
!expressionObj.outputDefined()) {
return [new ValidationError(options.key, options.value, `Invalid data expression for "${options.propertyKey}". Output values must be contained as literals within the expression.`)];
}
if (options.expressionContext === 'property' && options.propertyType === 'layout' &&
(!isStateConstant(expressionObj))) {
return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with layout properties.')];
}
if (options.expressionContext === 'filter' && !isStateConstant(expressionObj)) {
return [new ValidationError(options.key, options.value, '"feature-state" data expressions are not supported with filters.')];
}
if (options.expressionContext && options.expressionContext.indexOf('cluster') === 0) {
if (!isGlobalPropertyConstant(expressionObj, ['zoom', 'feature-state'])) {
return [new ValidationError(options.key, options.value, '"zoom" and "feature-state" expressions are not supported with cluster properties.')];
}
if (options.expressionContext === 'cluster-initial' && !isFeatureConstant(expressionObj)) {
return [new ValidationError(options.key, options.value, 'Feature data expressions are not supported with initial expression part of cluster properties.')];
}
}
return [];
}
function validateBoolean(options) {
const value = options.value;
const key = options.key;
const type = getType(value);
if (type !== 'boolean') {
return [new ValidationError(key, value, `boolean expected, ${type} found`)];
}
return [];
}
function validateColor(options) {
const key = options.key;
const value = options.value;
const type = getType(value);
if (type !== 'string') {
return [new ValidationError(key, value, `color expected, ${type} found`)];
}
if (!Color.parse(String(value))) { // cast String object to string primitive
return [new ValidationError(key, value, `color expected, "${value}" found`)];
}
return [];
}
function validateEnum(options) {
const key = options.key;
const value = options.value;
const valueSpec = options.valueSpec;
const errors = [];
if (Array.isArray(valueSpec.values)) { // <=v7
if (valueSpec.values.indexOf(unbundle(value)) === -1) {
errors.push(new ValidationError(key, value, `expected one of [${valueSpec.values.join(', ')}], ${JSON.stringify(value)} found`));
}
}
else { // >=v8
if (Object.keys(valueSpec.values).indexOf(unbundle(value)) === -1) {
errors.push(new ValidationError(key, value, `expected one of [${Object.keys(valueSpec.values).join(', ')}], ${JSON.stringify(value)} found`));
}
}
return errors;
}
function isExpressionFilter(filter) {
if (filter === true || filter === false) {
return true;
}
if (!Array.isArray(filter) || filter.length === 0) {
return false;
}
switch (filter[0]) {
case 'has':
return filter.length >= 2 && filter[1] !== '$id' && filter[1] !== '$type';
case 'in':
return filter.length >= 3 && (typeof filter[1] !== 'string' || Array.isArray(filter[2]));
case '!in':
case '!has':
case 'none':
return false;
case '==':
case '!=':
case '>':
case '>=':
case '<':
case '<=':
return filter.length !== 3 || (Array.isArray(filter[1]) || Array.isArray(filter[2]));
case 'any':
case 'all':
for (const f of filter.slice(1)) {
if (!isExpressionFilter(f) && typeof f !== 'boolean') {
return false;
}
}
return true;
default:
return true;
}
}
function validateFilter(options) {
if (isExpressionFilter(deepUnbundle(options.value))) {
return validateExpression(extendBy({}, options, {
expressionContext: 'filter',
valueSpec: { value: 'boolean' }
}));
}
else {
return validateNonExpressionFilter(options);
}
}
function validateNonExpressionFilter(options) {
const value = options.value;
const key = options.key;
if (getType(value) !== 'array') {
return [new ValidationError(key, value, `array expected, ${getType(value)} found`)];
}
const styleSpec = options.styleSpec;
let type;
let errors = [];
if (value.length < 1) {
return [new ValidationError(key, value, 'filter array must have at least 1 element')];
}
errors = errors.concat(validateEnum({
key: `${key}[0]`,
value: value[0],
valueSpec: styleSpec.filter_operator,
style: options.style,
styleSpec: options.styleSpec
}));
switch (unbundle(value[0])) {
case '<':
case '<=':
case '>':
case '>=':
if (value.length >= 2 && unbundle(value[1]) === '$type') {
errors.push(new ValidationError(key, value, `"$type" cannot be use with operator "${value[0]}"`));
}
/* falls through */
case '==':
case '!=':
if (value.length !== 3) {
errors.push(new ValidationError(key, value, `filter array for operator "${value[0]}" must have 3 elements`));
}
/* falls through */
case 'in':
case '!in':
if (value.length >= 2) {
type = getType(value[1]);
if (type !== 'string') {
errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`));
}
}
for (let i = 2; i < value.length; i++) {
type = getType(value[i]);
if (unbundle(value[1]) === '$type') {
errors = errors.concat(validateEnum({
key: `${key}[${i}]`,
value: value[i],
valueSpec: styleSpec.geometry_type,
style: options.style,
styleSpec: options.styleSpec
}));
}
else if (type !== 'string' && type !== 'number' && type !== 'boolean') {
errors.push(new ValidationError(`${key}[${i}]`, value[i], `string, number, or boolean expected, ${type} found`));
}
}
break;
case 'any':
case 'all':
case 'none':
for (let i = 1; i < value.length; i++) {
errors = errors.concat(validateNonExpressionFilter({
key: `${key}[${i}]`,
value: value[i],
style: options.style,
styleSpec: options.styleSpec
}));
}
break;
case 'has':
case '!has':
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 !== 'string') {
errors.push(new ValidationError(`${key}[1]`, value[1], `string expected, ${type} found`));
}
break;
}
return errors;
}
function validateProperty(options, propertyType) {
const key = options.key;
const validateSpec = options.validateSpec;
const style = options.style;
const styleSpec = options.styleSpec;
const value = options.value;
const propertyKey = options.objectKey;
const layerSpec = styleSpec[`${propertyType}_${options.layerType}`];
if (!layerSpec)
return [];
const transitionMatch = propertyKey.match(/^(.*)-transition$/);
if (propertyType === 'paint' && transitionMatch && layerSpec[transitionMatch[1]] && layerSpec[transitionMatch[1]].transition) {
return validateSpec({
key,
value,
valueSpec: styleSpec.transition,
style,
styleSpec
});
}
const valueSpec = options.valueSpec || layerSpec[propertyKey];
if (!valueSpec) {
return [new ValidationError(key, value, `unknown property "${propertyKey}"`)];
}
let tokenMatch;
if (getType(value) === 'string' && supportsPropertyExpression(valueSpec) && !valueSpec.tokens && (tokenMatch = /^{([^}]+)}$/.exec(value))) {
return [new ValidationError(key, value, `"${propertyKey}" does not support interpolation syntax\n` +
`Use an identity property function instead: \`{ "type": "identity", "property": ${JSON.stringify(tokenMatch[1])} }\`.`)];
}
const errors = [];
if (options.layerType === 'symbol') {
if (propertyKey === 'text-field' && style && !style.glyphs) {
errors.push(new ValidationError(key, value, 'use of "text-field" requires a style "glyphs" property'));
}
if (propertyKey === 'text-font' && isFunction(deepUnbundle(value)) && unbundle(value.type) === 'identity') {
errors.push(new ValidationError(key, value, '"text-font" does not support identity functions'));
}
}
return errors.concat(validateSpec({
key: options.key,
value,
valueSpec,
style,
styleSpec,
expressionContext: 'property',
propertyType,
propertyKey
}));
}
function validatePaintProperty(options) {
return validateProperty(options, 'paint');
}
function validateLayoutProperty(options) {
return validateProperty(options, 'layout');
}
function validateLayer(options) {
let errors = [];
const layer = options.value;
const key = options.key;
const style = options.style;
const styleSpec = options.styleSpec;
if (getType(layer) !== 'object') {
return [new ValidationError(key, layer, `object expected, ${getType(layer)} found`)];
}
if (!layer.type && !layer.ref) {
errors.push(new ValidationError(key, layer, 'either "type" or "ref" is required'));
}
let type = unbundle(layer.type);
const ref = unbundle(layer.ref);
if (layer.id) {
const layerId = unbundle(layer.id);
for (let i = 0; i < options.arrayIndex; i++) {
const otherLayer = style.layers[i];
if (unbundle(otherLayer.id) === layerId) {
errors.push(new ValidationError(key, layer.id, `duplicate layer id "${layer.id}", previously used at line ${otherLayer.id.__line__}`));
}
}
}
if ('ref' in layer) {
['type', 'source', 'source-layer', 'filter', 'layout'].forEach((p) => {
if (p in layer) {
errors.push(new ValidationError(key, layer[p], `"${p}" is prohibited for ref layers`));
}
});
let parent;
style.layers.forEach((layer) => {
if (unbundle(layer.id) === ref)
parent = layer;
});
if (!parent) {
errors.push(new ValidationError(key, layer.ref, `ref layer "${ref}" not found`));
}
else if (parent.ref) {
errors.push(new ValidationError(key, layer.ref, 'ref cannot reference another ref layer'));
}
else {
type = unbundle(parent.type);
}
}
else if (type !== 'background') {
if (!layer.source) {
errors.push(new ValidationError(key, layer, 'missing required property "source"'));
}
else {
const source = style.sources && style.sources[layer.source];
const sourceType = source && unbundle(source.type);
if (!source) {
errors.push(new ValidationError(key, layer.source, `source "${layer.source}" not found`));
}
else if (sourceType === 'vector' && type === 'raster') {
errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster source`));
}
else if (sourceType !== 'raster-dem' && type === 'hillshade') {
errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster-dem source`));
}
else if (sourceType !== 'raster-dem' && type === 'color-relief') {
errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a raster-dem source`));
}
else if (sourceType === 'raster' && type !== 'raster') {
errors.push(new ValidationError(key, layer.source, `layer "${layer.id}" requires a vector source`));
}
else if (sourceType === 'vector' && !layer['source-layer']) {
errors.push(new ValidationError(key, layer, `layer "${layer.id}" must specify a "source-layer"`));
}
else if (sourceType === 'raster-dem' && (type !== 'hillshade' && type !== 'color-relief')) {
errors.push(new ValidationError(key, layer.source, 'raster-dem source can only be used with layer type \'hillshade\' or \'color-relief\'.'));
}
else if (type === 'line' && layer.paint && layer.paint['line-gradient'] &&
(sourceType !== 'geojson' || !source.lineMetrics)) {
errors.push(new ValidationError(key, layer, `layer "${layer.id}" specifies a line-gradient, which requires a GeoJSON source with \`lineMetrics\` enabled.`));
}
}
}
errors = errors.concat(validateObject({
key,
value: layer,
valueSpec: styleSpec.layer,
style: options.style,
styleSpec: options.styleSpec,
validateSpec: options.validateSpec,
objectElementValidators: {
'*'() {
return [];
},
// We don't want to enforce the spec's `"requires": true` for backward compatibility with refs;
// the actual requirement is validated above. See https://github.com/mapbox/mapbox-gl-js/issues/5772.
type() {
return options.validateSpec({
key: `${key}.type`,
value: layer.type,
valueSpec: styleSpec.layer.type,
style: options.style,
styleSpec: options.styleSpec,
validateSpec: options.validateSpec,
object: layer,
objectKey: 'type'
});
},
filter: validateFilter,
layout(options) {
return validateObject({
layer,
key: options.key,
value: options.value,
style: options.style,
styleSpec: options.styleSpec,
validateSpec: options.validateSpec,
objectElementValidators: {
'*'(options) {
return validateLayoutProperty(extendBy({ layerType: type }, options));
}
}
});
},
paint(options) {
return validateObject({
layer,
key: options.key,
value: options.value,
style: options.style,
styleSpec: options.styleSpec,
validateSpec: options.validateSpec,
objectElementValidators: {
'*'(options) {
return validatePaintProperty(extendBy({ layerType: type }, options));
}
}
});
}
}
}));
return errors;
}
function validateString(options) {
const value = options.value;
const key = options.key;
const type = getType(value);
if (type !== 'string') {
return [new ValidationError(key, value, `string expected, ${type} found`)];
}
return [];
}
function validateRasterDEMSource(options) {
var _a;
const sourceName = (_a = options.sourceName) !== null && _a !== void 0 ? _a : '';
const rasterDEM = options.value;
const styleSpec = options.styleSpec;
const rasterDEMSpec = styleSpec.source_raster_dem;
const style = options.style;
let errors = [];
const rootType = getType(rasterDEM);
if (rasterDEM === undefined) {
return errors;
}
else if (rootType !== 'object') {
errors.push(new ValidationError('source_raster_dem', rasterDEM, `object expected, ${rootType} found`));
return errors;
}
const encoding = unbundle(rasterDEM.encoding);
const isCustomEncoding = encoding === 'custom';
const customEncodingKeys = ['redFactor', 'greenFactor', 'blueFactor', 'baseShift'];
const encodingName = options.value.encoding ? `"${options.value.encoding}"` : 'Default';
for (const key in rasterDEM) {
if (!isCustomEncoding && customEncodingKeys.includes(key)) {
errors.push(new ValidationError(key, rasterDEM[key], `In "${sourceName}": "${key}" is only valid when "encoding" is set to "custom". ${encodingName} encoding found`));
}
else if (rasterDEMSpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: rasterDEM[key],
valueSpec: rasterDEMSpec[key],
validateSpec: options.validateSpec,
style,
styleSpec
}));
}
else {
errors.push(new ValidationError(key, rasterDEM[key], `unknown property "${key}"`));
}
}
return errors;
}
const objectElementValidators = {
promoteId: validatePromoteId
};
function validateSource(options) {
const value = options.value;
const key = options.key;
const styleSpec = options.styleSpec;
const style = options.style;
const validateSpec = options.validateSpec;
if (!value.type) {
return [new ValidationError(key, value, '"type" is required')];
}
const type = unbundle(value.type);
let errors;
switch (type) {
case 'vector':
case 'raster':
errors = validateObject({
key,
value,
valueSpec: styleSpec[`source_${type.replace('-', '_')}`],
style: options.style,
styleSpec,
objectElementValidators,
validateSpec,
});
return errors;
case 'raster-dem':
errors = validateRasterDEMSource({
sourceName: key,
value,
style: options.style,
styleSpec,
validateSpec,
});
return errors;
case 'geojson':
errors = validateObject({
key,
value,
valueSpec: styleSpec.source_geojson,
style,
styleSpec,
validateSpec,
objectElementValidators
});
if (value.cluster) {
for (const prop in value.clusterProperties) {
const [operator, mapExpr] = value.clusterProperties[prop];
const reduceExpr = typeof operator === 'string' ? [operator, ['accumulated'], ['get', prop]] : operator;
errors.push(...validateExpression({
key: `${key}.${prop}.map`,
value: mapExpr,
expressionContext: 'cluster-map'
}));
errors.push(...validateExpression({
key: `${key}.${prop}.reduce`,
value: reduceExpr,
expressionContext: 'cluster-reduce'
}));
}
}
return errors;
case 'video':
return validateObject({
key,
value,
valueSpec: styleSpec.source_video,
style,
validateSpec,
styleSpec
});
case 'image':
return validateObject({
key,
value,
valueSpec: styleSpec.source_image,
style,
validateSpec,
styleSpec
});
case 'canvas':
return [new ValidationError(key, null, 'Please use runtime APIs to add canvas sources, rather than including them in stylesheets.', 'source.canvas')];
default:
return validateEnum({
key: `${key}.type`,
value: value.type,
valueSpec: { values: ['vector', 'raster', 'raster-dem', 'geojson', 'video', 'image'] }});
}
}
function validatePromoteId({ key, value }) {
if (getType(value) === 'string') {
return validateString({ key, value });
}
else {
const errors = [];
for (const prop in value) {
errors.push(...validateString({ key: `${key}.${prop}`, value: value[prop] }));
}
return errors;
}
}
function validateLight(options) {
const light = options.value;
const styleSpec = options.styleSpec;
const lightSpec = styleSpec.light;
const style = options.style;
let errors = [];
const rootType = getType(light);
if (light === undefined) {
return errors;
}
else if (rootType !== 'object') {
errors = errors.concat([new ValidationError('light', light, `object expected, ${rootType} found`)]);
return errors;
}
for (const key in light) {
const transitionMatch = key.match(/^(.*)-transition$/);
if (transitionMatch && lightSpec[transitionMatch[1]] && lightSpec[transitionMatch[1]].transition) {
errors = errors.concat(options.validateSpec({
key,
value: light[key],
valueSpec: styleSpec.transition,
validateSpec: options.validateSpec,
style,
styleSpec
}));
}
else if (lightSpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: light[key],
valueSpec: lightSpec[key],
validateSpec: options.validateSpec,
style,
styleSpec
}));
}
else {
errors = errors.concat([new ValidationError(key, light[key], `unknown property "${key}"`)]);
}
}
return errors;
}
function validateSky(options) {
const sky = options.value;
const styleSpec = options.styleSpec;
const skySpec = styleSpec.sky;
const style = options.style;
const rootType = getType(sky);
if (sky === undefined) {
return [];
}
else if (rootType !== 'object') {
return [new ValidationError('sky', sky, `object expected, ${rootType} found`)];
}
let errors = [];
for (const key in sky) {
if (skySpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: sky[key],
valueSpec: skySpec[key],
style,
styleSpec
}));
}
else {
errors = errors.concat([new ValidationError(key, sky[key], `unknown property "${key}"`)]);
}
}
return errors;
}
function validateTerrain(options) {
const terrain = options.value;
const styleSpec = options.styleSpec;
const terrainSpec = styleSpec.terrain;
const style = options.style;
let errors = [];
const rootType = getType(terrain);
if (terrain === undefined) {
return errors;
}
else if (rootType !== 'object') {
errors = errors.concat([new ValidationError('terrain', terrain, `object expected, ${rootType} found`)]);
return errors;
}
for (const key in terrain) {
if (terrainSpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: terrain[key],
valueSpec: terrainSpec[key],
validateSpec: options.validateSpec,
style,
styleSpec
}));
}
else {
errors = errors.concat([new ValidationError(key, terrain[key], `unknown property "${key}"`)]);
}
}
return errors;
}
function validateFormatted(options) {
if (validateString(options).length === 0) {
return [];
}
return validateExpression(options);
}
function validateImage(options) {
if (validateString(options).length === 0) {
return [];
}
return validateExpression(options);
}
function validatePadding(options) {
const key = options.key;
const value = options.value;
const type = getType(value);
if (type === 'array') {
if (value.length < 1 || value.length > 4) {
return [new ValidationError(key, value, `padding requires 1 to 4 values; ${value.length} values found`)];
}
const arrayElementSpec = {
type: 'number'
};
let errors = [];
for (let i = 0; i < value.length; i++) {
errors = errors.concat(options.validateSpec({
key: `${key}[${i}]`,
value: value[i],
validateSpec: options.validateSpec,
valueSpec: arrayElementSpec
}));
}
return errors;
}
else {
return validateNumber({
key,
value,
valueSpec: {}
});
}
}
function validateNumberArray(options) {
const key = options.key;
const value = options.value;
const type = getType(value);
if (type === 'array') {
const arrayElementSpec = {
type: 'number'
};
if (value.length < 1) {
return [new ValidationError(key, value, 'array length at least 1 expected, length 0 found')];
}
let errors = [];
for (let i = 0; i < value.length; i++) {
errors = errors.concat(options.validateSpec({
key: `${key}[${i}]`,
value: value[i],
validateSpec: options.validateSpec,
valueSpec: arrayElementSpec
}));
}
return errors;
}
else {
return validateNumber({
key,
value,
valueSpec: {}
});
}
}
function validateColorArray(options) {
const key = options.key;
const value = options.value;
const type = getType(value);
if (type === 'array') {
if (value.length < 1) {
return [new ValidationError(key, value, 'array length at least 1 expected, length 0 found')];
}
let errors = [];
for (let i = 0; i < value.length; i++) {
errors = errors.concat(validateColor({
key: `${key}[${i}]`,
value: value[i]}));
}
return errors;
}
else {
return validateColor({
key,
value});
}
}
function validateVariableAnchorOffsetCollection(options) {
const key = options.key;
const value = options.value;
const type = getType(value);
const styleSpec = options.styleSpec;
if (type !== 'array' || value.length < 1 || value.length % 2 !== 0) {
return [new ValidationError(key, value, 'variableAnchorOffsetCollection requires a non-empty array of even length')];
}
let errors = [];
for (let i = 0; i < value.length; i += 2) {
// Elements in even positions should be values from text-anchor enum
errors = errors.concat(validateEnum({
key: `${key}[${i}]`,
value: value[i],
valueSpec: styleSpec['layout_symbol']['text-anchor']
}));
// Elements in odd positions should be points (2-element numeric arrays)
errors = errors.concat(validateArray({
key: `${key}[${i + 1}]`,
value: value[i + 1],
valueSpec: {
length: 2,
value: 'number'
},
validateSpec: options.validateSpec,
style: options.style,
styleSpec
}));
}
return errors;
}
function validateSprite(options) {
let errors = [];
const sprite = options.value;
const key = options.key;
if (!Array.isArray(sprite)) {
return validateString({
key,
value: sprite
});
}
else {
const allSpriteIds = [];
const allSpriteURLs = [];
for (const i in sprite) {
if (sprite[i].id && allSpriteIds.includes(sprite[i].id))
errors.push(new ValidationError(key, sprite, `all the sprites' ids must be unique, but ${sprite[i].id} is duplicated`));
allSpriteIds.push(sprite[i].id);
if (sprite[i].url && allSpriteURLs.includes(sprite[i].url))
errors.push(new ValidationError(key, sprite, `all the sprites' URLs must be unique, but ${sprite[i].url} is duplicated`));
allSpriteURLs.push(sprite[i].url);
const pairSpec = {
id: {
type: 'string',
required: true,
},
url: {
type: 'string',
required: true,
}
};
errors = errors.concat(validateObject({
key: `${key}[${i}]`,
value: sprite[i],
valueSpec: pairSpec,
validateSpec: options.validateSpec,
}));
}
return errors;
}
}
function validateProjection(options) {
const projection = options.value;
const styleSpec = options.styleSpec;
const projectionSpec = styleSpec.projection;
const style = options.style;
const rootType = getType(projection);
if (projection === undefined) {
return [];
}
else if (rootType !== 'object') {
return [new ValidationError('projection', projection, `object expected, ${rootType} found`)];
}
let errors = [];
for (const key in projection) {
if (projectionSpec[key]) {
errors = errors.concat(options.validateSpec({
key,
value: projection[key],
valueSpec: projectionSpec[key],
style,
styleSpec
}));
}
else {
errors = errors.concat([new ValidationError(key, projection[key], `unknown property "${key}"`)]);
}
}
return errors;
}
function validateProjectionDefinition(options) {
const key = options.key;
let value = options.value;
value = value instanceof String ? value.valueOf() : value;
const type = getType(value);
if (type === 'array' && !isProjectionDefinitionValue(value) && !isPropertyValueSpecification(value)) {
return [new ValidationError(key, value, `projection expected, invalid array ${JSON.stringify(value)} found`)];
}
else if (!['array', 'string'].includes(type)) {
return [new ValidationError(key, value, `projection expected, invalid type "${type}" found`)];
}
return [];
}
function isPropertyValueSpecification(value) {
if (['interpolate', 'step', 'literal'].includes(value[0])) {
return true;
}
return false;
}
function isProjectionDefinitionValue(value) {
return Array.isArray(value) &&
value.length === 3 &&
typeof value[0] === 'string' &&
typeof value[1] === 'string' &&
typeof value[2] === 'number';
}
function isObjectLiteral(anything) {
return Boolean(anything) && anything.constructor === Object;
}
function validateState(options) {
if (!isObjectLiteral(options.value)) {
return [
new ValidationError(options.key, options.value, `object expected, ${getType(options.value)} found`),
];
}
return [];
}
const VALIDATORS = {
'*'() {
return [];
},
'array': validateArray,
'boolean': validateBoolean,
'number': validateNumber,
'color': validateColor,
'constants': validateConstants,
'enum': validateEnum,
'filter': validateFilter,
'function': validateFunction,
'layer': validateLayer,
'object': validateObject,
'source': validateSource,
'light': validateLight,
'sky': validateSky,
'terrain': validateTerrain,
'projection': validateProjection,
'projectionDefinition': validateProjectionDefinition,
'string': validateString,
'formatted': validateFormatted,
'resolvedImage': validateImage,
'padding': validatePadding,
'numberArray': validateNumberArray,
'colorArray': validateColorArray,
'variableAnchorOffsetCollection': validateVariableAnchorOffsetCollection,
'sprite': validateSprite,
'state': validateState
};
/**
* Main recursive validation function used internally.
* You should use `validateStyleMin` in the browser or `validateStyle` in node env.
* @param options - the options object
* @param options.key - string representing location of validation in style tree. Used only
* for more informative error reporting.
* @param options.value - current value from style being evaluated. May be anything from a
* high level object that needs to be descended into deeper or a simple
* scalar value.
* @param options.valueSpec - current spec being evaluated. Tracks value.
* @param options.styleSpec - current full spec being evaluated.
* @param options.validateSpec - the validate function itself
* @param options.style - the style object
* @param options.objectElementValidators - optional object of functions that will be called
* @returns an array of errors, or an empty array if no errors are found.
*/
function validate(options) {
const value = options.value;
const valueSpec = options.valueSpec;
const styleSpec = options.styleSpec;
options.validateSpec = validate;
if (valueSpec.expression && isFunction(unbundle(value))) {
return validateFunction(options);
}
else if (valueSpec.expression && isExpression(deepUnbundle(value))) {
return validateExpression(options);
}
else if (valueSpec.type && VALIDATORS[valueSpec.type]) {
return VALIDATORS[valueSpec.type](options);
}
else {
const valid = validateObject(extendBy({}, options, {
valueSpec: valueSpec.type ? styleSpec[valueSpec.type] : valueSpec
}));
return valid;
}
}
var $version = 8;
var $root = {
version: {
required: true,
type: "enum",
values: [
8
]
},
name: {
type: "string"
},
metadata: {
type: "*"
},
center: {
type: "array",
value: "number"
},
centerAltitude: {
type: "number"
},
zoom: {
type: "number"
},
bearing: {
type: "number",
"default": 0,
period: 360,
units: "degrees"
},
pitch: {
type: "number",
"default": 0,
units: "degrees"
},
roll: {
type: "number",
"default": 0,
units: "degrees"
},
state: {
type: "state",
"default": {
}
},
light: {
type: "light"
},
sky: {
type: "sky"
},
projection: {
type: "projection"
},
terrain: {
type: "terrain"
},
sources: {
required: true,
type: "sources"
},
sprite: {
type: "sprite"
},
glyphs: {
type: "string"
},
"font-faces": {
type: "array",
value: "fontFaces"
},
transition: {
type: "transition"
},
layers: {
required: true,
type: "array",
value: "layer"
}
};
var sources = {
"*": {
type: "source"
}
};
var source = [
"source_vector",
"source_raster",
"source_raster_dem",
"source_geojson",
"source_video",
"source_image"
];
var source_vector = {
type: {
required: true,
type: "enum",
values: {
vector: {
}
}
},
url: {
type: "string"
},
tiles: {
type: "array",
value: "string"
},
bounds: {
type: "array",
value: "number",
length: 4,
"default": [
-180,
-85.051129,
180,
85.051129
]
},
scheme: {
type: "enum",
values: {
xyz: {
},
tms: {
}
},
"default": "xyz"
},
minzoom: {
type: "number",
"default": 0
},
maxzoom: {
type: "number",
"default": 22
},
attribution: {
type: "string"
},
promoteId: {
type: "promoteId"
},
volatile: {
type: "boolean",
"default": false
},
"*": {
type: "*"
}
};
var source_raster = {
type: {
required: true,
type: "enum",
values: {
raster: {
}
}
},
url: {
type: "string"
},
tiles: {
type: "array",
value: "string"
},
bounds: {
type: "array",
value: "number",
length: 4,
"default": [
-180,
-85.051129,
180,
85.051129
]
},
minzoom: {
type: "number",
"default": 0
},
maxzoom: {
type: "number",
"default": 22
},
tileSize: {
type: "number",
"default": 512,
units: "pixels"
},
scheme: {
type: "enum",
values: {
xyz: {
},
tms: {
}
},
"default": "xyz"
},
attribution: {
type: "string"
},
volatile: {
type: "boolean",
"default": false
},
"*": {
type: "*"
}
};
var source_raster_dem = {
type: {
required: true,
type: "enum",
values: {
"raster-dem": {
}
}
},
url: {
type: "string"
},
tiles: {
type: "array",
value: "string"
},
bounds: {
type: "array",
value: "number",
length: 4,
"default": [
-180,
-85.051129,
180,
85.051129
]
},
minzoom: {
type: "number",
"default": 0
},
maxzoom: {
type: "number",
"default": 22
},
tileSize: {
type: "number",
"default": 512,
units: "pixels"
},
attribution: {
type: "string"
},
encoding: {
type: "enum",
values: {
terrarium: {
},
mapbox: {
},
custom: {
}
},
"default": "mapbox"
},
redFactor: {
type: "number",
"default": 1
},
blueFactor: {
type: "number",
"default": 1
},
greenFactor: {
type: "number",
"default": 1
},
baseShift: {
type: "number",
"default": 0
},
volatile: {
type: "boolean",
"default": false
},
"*": {
type: "*"
}
};
var source_geojson = {
type: {
required: true,
type: "enum",
values: {
geojson: {
}
}
},
data: {
required: true,
type: "*"
},
maxzoom: {
type: "number",
"default": 18
},
attribution: {
type: "string"
},
buffer: {
type: "number",
"default": 128,
maximum: 512,
minimum: 0
},
filter: {
type: "*"
},
tolerance: {
type: "number",
"default": 0.375
},
cluster: {
type: "boolean",
"default": false
},
clusterRadius: {
type: "number",
"default": 50,
minimum: 0
},
clusterMaxZoom: {
type: "number"
},
clusterMinPoints: {
type: "number"
},
clusterProperties: {
type: "*"
},
lineMetrics: {
type: "boolean",
"default": false
},
generateId: {
type: "boolean",
"default": false
},
promoteId: {
type: "promoteId"
}
};
var source_video = {
type: {
required: true,
type: "enum",
values: {
video: {
}
}
},
urls: {
required: true,
type: "array",
value: "string"
},
coordinates: {
required: true,
type: "array",
length: 4,
value: {
type: "array",
length: 2,
value: "number"
}
}
};
var source_image = {
type: {
required: true,
type: "enum",
values: {
image: {
}
}
},
url: {
required: true,
type: "string"
},
coordinates: {
required: true,
type: "array",
length: 4,
value: {
type: "array",
length: 2,
value: "number"
}
}
};
var layer = {
id: {
type: "string",
required: true
},
type: {
type: "enum",
values: {
fill: {
},
line: {
},
symbol: {
},
circle: {
},
heatmap: {
},
"fill-extrusion": {
},
raster: {
},
hillshade: {
},
"color-relief": {
},
background: {
}
},
required: true
},
metadata: {
type: "*"
},
source: {
type: "string"
},
"source-layer": {
type: "string"
},
minzoom: {
type: "number",
minimum: 0,
maximum: 24
},
maxzoom: {
type: "number",
minimum: 0,
maximum: 24
},
filter: {
type: "filter"
},
layout: {
type: "layout"
},
paint: {
type: "paint"
}
};
var layout = [
"layout_fill",
"layout_line",
"layout_circle",
"layout_heatmap",
"layout_fill-extrusion",
"layout_symbol",
"layout_raster",
"layout_hillshade",
"layout_color-relief",
"layout_background"
];
var layout_background = {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_fill = {
"fill-sort-key": {
type: "number",
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_circle = {
"circle-sort-key": {
type: "number",
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_heatmap = {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_line = {
"line-cap": {
type: "enum",
values: {
butt: {
},
round: {
},
square: {
}
},
"default": "butt",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"line-join": {
type: "enum",
values: {
bevel: {
},
round: {
},
miter: {
}
},
"default": "miter",
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"line-miter-limit": {
type: "number",
"default": 2,
requires: [
{
"line-join": "miter"
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"line-round-limit": {
type: "number",
"default": 1.05,
requires: [
{
"line-join": "round"
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"line-sort-key": {
type: "number",
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_symbol = {
"symbol-placement": {
type: "enum",
values: {
point: {
},
line: {
},
"line-center": {
}
},
"default": "point",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"symbol-spacing": {
type: "number",
"default": 250,
minimum: 1,
units: "pixels",
requires: [
{
"symbol-placement": "line"
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"symbol-avoid-edges": {
type: "boolean",
"default": false,
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"symbol-sort-key": {
type: "number",
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"symbol-z-order": {
type: "enum",
values: {
auto: {
},
"viewport-y": {
},
source: {
}
},
"default": "auto",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-allow-overlap": {
type: "boolean",
"default": false,
requires: [
"icon-image",
{
"!": "icon-overlap"
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-overlap": {
type: "enum",
values: {
never: {
},
always: {
},
cooperative: {
}
},
requires: [
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-ignore-placement": {
type: "boolean",
"default": false,
requires: [
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-optional": {
type: "boolean",
"default": false,
requires: [
"icon-image",
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-rotation-alignment": {
type: "enum",
values: {
map: {
},
viewport: {
},
auto: {
}
},
"default": "auto",
requires: [
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-size": {
type: "number",
"default": 1,
minimum: 0,
units: "factor of the original icon size",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-text-fit": {
type: "enum",
values: {
none: {
},
width: {
},
height: {
},
both: {
}
},
"default": "none",
requires: [
"icon-image",
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-text-fit-padding": {
type: "array",
value: "number",
length: 4,
"default": [
0,
0,
0,
0
],
units: "pixels",
requires: [
"icon-image",
"text-field",
{
"icon-text-fit": [
"both",
"width",
"height"
]
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-image": {
type: "resolvedImage",
tokens: true,
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-rotate": {
type: "number",
"default": 0,
period: 360,
units: "degrees",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-padding": {
type: "padding",
"default": [
2
],
units: "pixels",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-keep-upright": {
type: "boolean",
"default": false,
requires: [
"icon-image",
{
"icon-rotation-alignment": "map"
},
{
"symbol-placement": [
"line",
"line-center"
]
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-offset": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-anchor": {
type: "enum",
values: {
center: {
},
left: {
},
right: {
},
top: {
},
bottom: {
},
"top-left": {
},
"top-right": {
},
"bottom-left": {
},
"bottom-right": {
}
},
"default": "center",
requires: [
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"icon-pitch-alignment": {
type: "enum",
values: {
map: {
},
viewport: {
},
auto: {
}
},
"default": "auto",
requires: [
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-pitch-alignment": {
type: "enum",
values: {
map: {
},
viewport: {
},
auto: {
}
},
"default": "auto",
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-rotation-alignment": {
type: "enum",
values: {
map: {
},
viewport: {
},
"viewport-glyph": {
},
auto: {
}
},
"default": "auto",
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-field": {
type: "formatted",
"default": "",
tokens: true,
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-font": {
type: "array",
value: "string",
"default": [
"Open Sans Regular",
"Arial Unicode MS Regular"
],
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-size": {
type: "number",
"default": 16,
minimum: 0,
units: "pixels",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-max-width": {
type: "number",
"default": 10,
minimum: 0,
units: "ems",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-line-height": {
type: "number",
"default": 1.2,
units: "ems",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-letter-spacing": {
type: "number",
"default": 0,
units: "ems",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-justify": {
type: "enum",
values: {
auto: {
},
left: {
},
center: {
},
right: {
}
},
"default": "center",
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-radial-offset": {
type: "number",
units: "ems",
"default": 0,
requires: [
"text-field"
],
"property-type": "data-driven",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
}
},
"text-variable-anchor": {
type: "array",
value: "enum",
values: {
center: {
},
left: {
},
right: {
},
top: {
},
bottom: {
},
"top-left": {
},
"top-right": {
},
"bottom-left": {
},
"bottom-right": {
}
},
requires: [
"text-field",
{
"symbol-placement": [
"point"
]
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-variable-anchor-offset": {
type: "variableAnchorOffsetCollection",
requires: [
"text-field",
{
"symbol-placement": [
"point"
]
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-anchor": {
type: "enum",
values: {
center: {
},
left: {
},
right: {
},
top: {
},
bottom: {
},
"top-left": {
},
"top-right": {
},
"bottom-left": {
},
"bottom-right": {
}
},
"default": "center",
requires: [
"text-field",
{
"!": "text-variable-anchor"
}
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-max-angle": {
type: "number",
"default": 45,
units: "degrees",
requires: [
"text-field",
{
"symbol-placement": [
"line",
"line-center"
]
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-writing-mode": {
type: "array",
value: "enum",
values: {
horizontal: {
},
vertical: {
}
},
requires: [
"text-field",
{
"symbol-placement": [
"point"
]
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-rotate": {
type: "number",
"default": 0,
period: 360,
units: "degrees",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-padding": {
type: "number",
"default": 2,
minimum: 0,
units: "pixels",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-keep-upright": {
type: "boolean",
"default": true,
requires: [
"text-field",
{
"text-rotation-alignment": "map"
},
{
"symbol-placement": [
"line",
"line-center"
]
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-transform": {
type: "enum",
values: {
none: {
},
uppercase: {
},
lowercase: {
}
},
"default": "none",
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-offset": {
type: "array",
value: "number",
units: "ems",
length: 2,
"default": [
0,
0
],
requires: [
"text-field",
{
"!": "text-radial-offset"
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature"
]
},
"property-type": "data-driven"
},
"text-allow-overlap": {
type: "boolean",
"default": false,
requires: [
"text-field",
{
"!": "text-overlap"
}
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-overlap": {
type: "enum",
values: {
never: {
},
always: {
},
cooperative: {
}
},
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-ignore-placement": {
type: "boolean",
"default": false,
requires: [
"text-field"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-optional": {
type: "boolean",
"default": false,
requires: [
"text-field",
"icon-image"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_raster = {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var layout_hillshade = {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
};
var filter = {
type: "array",
value: "*"
};
var filter_operator = {
type: "enum",
values: {
"==": {
},
"!=": {
},
">": {
},
">=": {
},
"<": {
},
"<=": {
},
"in": {
},
"!in": {
},
all: {
},
any: {
},
none: {
},
has: {
},
"!has": {
}
}
};
var geometry_type = {
type: "enum",
values: {
Point: {
},
LineString: {
},
Polygon: {
}
}
};
var function_stop = {
type: "array",
minimum: 0,
maximum: 24,
value: [
"number",
"color"
],
length: 2
};
var expression = {
type: "array",
value: "*",
minimum: 1
};
var light = {
anchor: {
type: "enum",
"default": "viewport",
values: {
map: {
},
viewport: {
}
},
"property-type": "data-constant",
transition: false,
expression: {
interpolated: false,
parameters: [
"zoom"
]
}
},
position: {
type: "array",
"default": [
1.15,
210,
30
],
length: 3,
value: "number",
"property-type": "data-constant",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
}
},
color: {
type: "color",
"property-type": "data-constant",
"default": "#ffffff",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
intensity: {
type: "number",
"property-type": "data-constant",
"default": 0.5,
minimum: 0,
maximum: 1,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
}
};
var sky = {
"sky-color": {
type: "color",
"property-type": "data-constant",
"default": "#88C6FC",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"horizon-color": {
type: "color",
"property-type": "data-constant",
"default": "#ffffff",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"fog-color": {
type: "color",
"property-type": "data-constant",
"default": "#ffffff",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"fog-ground-blend": {
type: "number",
"property-type": "data-constant",
"default": 0.5,
minimum: 0,
maximum: 1,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"horizon-fog-blend": {
type: "number",
"property-type": "data-constant",
"default": 0.8,
minimum: 0,
maximum: 1,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"sky-horizon-blend": {
type: "number",
"property-type": "data-constant",
"default": 0.8,
minimum: 0,
maximum: 1,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
},
"atmosphere-blend": {
type: "number",
"property-type": "data-constant",
"default": 0.8,
minimum: 0,
maximum: 1,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
transition: true
}
};
var terrain = {
source: {
type: "string",
required: true
},
exaggeration: {
type: "number",
minimum: 0,
"default": 1
}
};
var projection = {
type: {
type: "projectionDefinition",
"default": "mercator",
"property-type": "data-constant",
transition: false,
expression: {
interpolated: true,
parameters: [
"zoom"
]
}
}
};
var paint = [
"paint_fill",
"paint_line",
"paint_circle",
"paint_heatmap",
"paint_fill-extrusion",
"paint_symbol",
"paint_raster",
"paint_hillshade",
"paint_color-relief",
"paint_background"
];
var paint_fill = {
"fill-antialias": {
type: "boolean",
"default": true,
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-color": {
type: "color",
"default": "#000000",
transition: true,
requires: [
{
"!": "fill-pattern"
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-outline-color": {
type: "color",
transition: true,
requires: [
{
"!": "fill-pattern"
},
{
"fill-antialias": true
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"fill-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-pattern": {
type: "resolvedImage",
transition: true,
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "cross-faded-data-driven"
}
};
var paint_line = {
"line-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-color": {
type: "color",
"default": "#000000",
transition: true,
requires: [
{
"!": "line-pattern"
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"line-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"line-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"line-width": {
type: "number",
"default": 1,
minimum: 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-gap-width": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-offset": {
type: "number",
"default": 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-blur": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"line-dasharray": {
type: "array",
value: "number",
minimum: 0,
transition: true,
units: "line widths",
requires: [
{
"!": "line-pattern"
}
],
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "cross-faded-data-driven"
},
"line-pattern": {
type: "resolvedImage",
transition: true,
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "cross-faded-data-driven"
},
"line-gradient": {
type: "color",
transition: false,
requires: [
{
"!": "line-dasharray"
},
{
"!": "line-pattern"
},
{
source: "geojson",
has: {
lineMetrics: true
}
}
],
expression: {
interpolated: true,
parameters: [
"line-progress"
]
},
"property-type": "color-ramp"
}
};
var paint_circle = {
"circle-radius": {
type: "number",
"default": 5,
minimum: 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-color": {
type: "color",
"default": "#000000",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-blur": {
type: "number",
"default": 0,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"circle-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"circle-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"circle-pitch-scale": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"circle-pitch-alignment": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "viewport",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"circle-stroke-width": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-stroke-color": {
type: "color",
"default": "#000000",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"circle-stroke-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
}
};
var paint_heatmap = {
"heatmap-radius": {
type: "number",
"default": 30,
minimum: 1,
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"heatmap-weight": {
type: "number",
"default": 1,
minimum: 0,
transition: false,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"heatmap-intensity": {
type: "number",
"default": 1,
minimum: 0,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"heatmap-color": {
type: "color",
"default": [
"interpolate",
[
"linear"
],
[
"heatmap-density"
],
0,
"rgba(0, 0, 255, 0)",
0.1,
"royalblue",
0.3,
"cyan",
0.5,
"lime",
0.7,
"yellow",
1,
"red"
],
transition: false,
expression: {
interpolated: true,
parameters: [
"heatmap-density"
]
},
"property-type": "color-ramp"
},
"heatmap-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
};
var paint_symbol = {
"icon-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"icon-color": {
type: "color",
"default": "#000000",
transition: true,
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"icon-halo-color": {
type: "color",
"default": "rgba(0, 0, 0, 0)",
transition: true,
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"icon-halo-width": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"icon-halo-blur": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"icon-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
requires: [
"icon-image"
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"icon-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"icon-image",
"icon-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"text-color": {
type: "color",
"default": "#000000",
transition: true,
overridable: true,
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"text-halo-color": {
type: "color",
"default": "rgba(0, 0, 0, 0)",
transition: true,
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"text-halo-width": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"text-halo-blur": {
type: "number",
"default": 0,
minimum: 0,
transition: true,
units: "pixels",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"text-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
requires: [
"text-field"
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"text-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"text-field",
"text-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
};
var paint_raster = {
"raster-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-hue-rotate": {
type: "number",
"default": 0,
period: 360,
transition: true,
units: "degrees",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-brightness-min": {
type: "number",
"default": 0,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-brightness-max": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-saturation": {
type: "number",
"default": 0,
minimum: -1,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-contrast": {
type: "number",
"default": 0,
minimum: -1,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-resampling": {
type: "enum",
values: {
linear: {
},
nearest: {
}
},
"default": "linear",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"raster-fade-duration": {
type: "number",
"default": 300,
minimum: 0,
transition: false,
units: "milliseconds",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
};
var paint_hillshade = {
"hillshade-illumination-direction": {
type: "numberArray",
"default": 335,
minimum: 0,
maximum: 359,
transition: false,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-illumination-altitude": {
type: "numberArray",
"default": 45,
minimum: 0,
maximum: 90,
transition: false,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-illumination-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "viewport",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-exaggeration": {
type: "number",
"default": 0.5,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-shadow-color": {
type: "colorArray",
"default": "#000000",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-highlight-color": {
type: "colorArray",
"default": "#FFFFFF",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-accent-color": {
type: "color",
"default": "#000000",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"hillshade-method": {
type: "enum",
values: {
standard: {
},
basic: {
},
combined: {
},
igor: {
},
multidirectional: {
}
},
"default": "standard",
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
};
var paint_background = {
"background-color": {
type: "color",
"default": "#000000",
transition: true,
requires: [
{
"!": "background-pattern"
}
],
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"background-pattern": {
type: "resolvedImage",
transition: true,
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "cross-faded"
},
"background-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
};
var transition = {
duration: {
type: "number",
"default": 300,
minimum: 0,
units: "milliseconds"
},
delay: {
type: "number",
"default": 0,
minimum: 0,
units: "milliseconds"
}
};
var promoteId = {
"*": {
type: "string"
}
};
var v8Spec = {
$version: $version,
$root: $root,
sources: sources,
source: source,
source_vector: source_vector,
source_raster: source_raster,
source_raster_dem: source_raster_dem,
source_geojson: source_geojson,
source_video: source_video,
source_image: source_image,
layer: layer,
layout: layout,
layout_background: layout_background,
layout_fill: layout_fill,
layout_circle: layout_circle,
layout_heatmap: layout_heatmap,
"layout_fill-extrusion": {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
},
layout_line: layout_line,
layout_symbol: layout_symbol,
layout_raster: layout_raster,
layout_hillshade: layout_hillshade,
"layout_color-relief": {
visibility: {
type: "enum",
values: {
visible: {
},
none: {
}
},
"default": "visible",
"property-type": "constant"
}
},
filter: filter,
filter_operator: filter_operator,
geometry_type: geometry_type,
"function": {
expression: {
type: "expression"
},
stops: {
type: "array",
value: "function_stop"
},
base: {
type: "number",
"default": 1,
minimum: 0
},
property: {
type: "string",
"default": "$zoom"
},
type: {
type: "enum",
values: {
identity: {
},
exponential: {
},
interval: {
},
categorical: {
}
},
"default": "exponential"
},
colorSpace: {
type: "enum",
values: {
rgb: {
},
lab: {
},
hcl: {
}
},
"default": "rgb"
},
"default": {
type: "*",
required: false
}
},
function_stop: function_stop,
expression: expression,
light: light,
sky: sky,
terrain: terrain,
projection: projection,
paint: paint,
paint_fill: paint_fill,
"paint_fill-extrusion": {
"fill-extrusion-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-extrusion-color": {
type: "color",
"default": "#000000",
transition: true,
requires: [
{
"!": "fill-extrusion-pattern"
}
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-extrusion-translate": {
type: "array",
value: "number",
length: 2,
"default": [
0,
0
],
transition: true,
units: "pixels",
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-extrusion-translate-anchor": {
type: "enum",
values: {
map: {
},
viewport: {
}
},
"default": "map",
requires: [
"fill-extrusion-translate"
],
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"fill-extrusion-pattern": {
type: "resolvedImage",
transition: true,
expression: {
interpolated: false,
parameters: [
"zoom",
"feature"
]
},
"property-type": "cross-faded-data-driven"
},
"fill-extrusion-height": {
type: "number",
"default": 0,
minimum: 0,
units: "meters",
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-extrusion-base": {
type: "number",
"default": 0,
minimum: 0,
units: "meters",
transition: true,
requires: [
"fill-extrusion-height"
],
expression: {
interpolated: true,
parameters: [
"zoom",
"feature",
"feature-state"
]
},
"property-type": "data-driven"
},
"fill-extrusion-vertical-gradient": {
type: "boolean",
"default": true,
transition: false,
expression: {
interpolated: false,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
}
},
paint_line: paint_line,
paint_circle: paint_circle,
paint_heatmap: paint_heatmap,
paint_symbol: paint_symbol,
paint_raster: paint_raster,
paint_hillshade: paint_hillshade,
"paint_color-relief": {
"color-relief-opacity": {
type: "number",
"default": 1,
minimum: 0,
maximum: 1,
transition: true,
expression: {
interpolated: true,
parameters: [
"zoom"
]
},
"property-type": "data-constant"
},
"color-relief-color": {
type: "color",
transition: false,
expression: {
interpolated: true,
parameters: [
"elevation"
]
},
"property-type": "color-ramp"
}
},
paint_background: paint_background,
transition: transition,
"property-type": {
"data-driven": {
type: "property-type"
},
"cross-faded": {
type: "property-type"
},
"cross-faded-data-driven": {
type: "property-type"
},
"color-ramp": {
type: "property-type"
},
"data-constant": {
type: "property-type"
},
constant: {
type: "property-type"
}
},
promoteId: promoteId
};
function validateGlyphsUrl(options) {
const value = options.value;
const key = options.key;
const errors = validateString(options);
if (errors.length)
return errors;
if (value.indexOf('{fontstack}') === -1) {
errors.push(new ValidationError(key, value, '"glyphs" url must include a "{fontstack}" token'));
}
if (value.indexOf('{range}') === -1) {
errors.push(new ValidationError(key, value, '"glyphs" url must include a "{range}" token'));
}
return errors;
}
/**
* Validate a MapLibre style against the style specification.
* Use this when running in the browser.
*
* @param style - The style to be validated.
* @param styleSpec - The style specification to validate against.
* If omitted, the latest style spec is used.
* @returns an array of errors, or an empty array if no errors are found.
* @example
* const validate = require('@maplibre/maplibre-gl-style-spec/').validateStyleMin;
* const errors = validate(style);
*/
function validateStyleMin(style, styleSpec = v8Spec) {
let errors = [];
errors = errors.concat(validate({
key: '',
value: style,
valueSpec: styleSpec.$root,
styleSpec,
style,
validateSpec: validate,
objectElementValidators: {
glyphs: validateGlyphsUrl,
'*'() {
return [];
}
}
}));
if (style['constants']) {
errors = errors.concat(validateConstants({
key: 'constants',
value: style['constants']}));
}
return sortErrors(errors);
}
validateStyleMin.source = wrapCleanErrors(injectValidateSpec(validateSource));
validateStyleMin.sprite = wrapCleanErrors(injectValidateSpec(validateSprite));
validateStyleMin.glyphs = wrapCleanErrors(injectValidateSpec(validateGlyphsUrl));
validateStyleMin.light = wrapCleanErrors(injectValidateSpec(validateLight));
validateStyleMin.sky = wrapCleanErrors(injectValidateSpec(validateSky));
validateStyleMin.terrain = wrapCleanErrors(injectValidateSpec(validateTerrain));
validateStyleMin.state = wrapCleanErrors(injectValidateSpec(validateState));
validateStyleMin.layer = wrapCleanErrors(injectValidateSpec(validateLayer));
validateStyleMin.filter = wrapCleanErrors(injectValidateSpec(validateFilter));
validateStyleMin.paintProperty = wrapCleanErrors(injectValidateSpec(validatePaintProperty));
validateStyleMin.layoutProperty = wrapCleanErrors(injectValidateSpec(validateLayoutProperty));
function injectValidateSpec(validator) {
return function (options) {
return validator(Object.assign({}, options, { validateSpec: validate }));
};
}
function sortErrors(errors) {
return [].concat(errors).sort((a, b) => {
return a.line - b.line;
});
}
function wrapCleanErrors(inner) {
return function (...args) {
return sortErrors(inner.apply(this, args));
};
}
// Note: Do not inherit from Error. It breaks when transpiling to ES5.
class ParsingError {
constructor(error) {
this.error = error;
this.message = error.message;
const match = error.message.match(/line (\d+)/);
this.line = match ? parseInt(match[1], 10) : 0;
}
}
const v8 = v8Spec;
function commonjsRequire(path) {
throw new Error('Could not dynamically require "' + path + '". Please configure the dynamicRequireTargets or/and ignoreDynamicRequires option of @rollup/plugin-commonjs appropriately for this require call to work.');
}
var jsonlint$1 = {};
/* parser generated by jison 0.4.15 */
var hasRequiredJsonlint;
function requireJsonlint () {
if (hasRequiredJsonlint) return jsonlint$1;
hasRequiredJsonlint = 1;
(function (exports) {
/*
Returns a Parser object of the following structure:
Parser: {
yy: {}
}
Parser.prototype: {
yy: {},
trace: function(),
symbols_: {associative list: name ==> number},
terminals_: {associative list: number ==> name},
productions_: [...],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$),
table: [...],
defaultActions: {...},
parseError: function(str, hash),
parse: function(input),
lexer: {
EOF: 1,
parseError: function(str, hash),
setInput: function(input),
input: function(),
unput: function(str),
more: function(),
less: function(n),
pastInput: function(),
upcomingInput: function(),
showPosition: function(),
test_match: function(regex_match_array, rule_index),
next: function(),
lex: function(),
begin: function(condition),
popState: function(),
_currentRules: function(),
topState: function(),
pushState: function(condition),
options: {
ranges: boolean (optional: true ==> token location info will include a .range[] member)
flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match)
backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code)
},
performAction: function(yy, yy_, $avoiding_name_collisions, YY_START),
rules: [...],
conditions: {associative list: name ==> set},
}
}
token location info (@$, _$, etc.): {
first_line: n,
last_line: n,
first_column: n,
last_column: n,
range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based)
}
the parseError function receives a 'hash' object with these members for lexer and parser errors: {
text: (matched text)
token: (the produced terminal token, if any)
line: (yylineno)
}
while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: {
loc: (yylloc)
expected: (string describing the set of expected tokens)
recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error)
}
*/
var parser = (function(){
var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,12],$V1=[1,13],$V2=[1,9],$V3=[1,10],$V4=[1,11],$V5=[1,14],$V6=[1,15],$V7=[14,18,22,24],$V8=[18,22],$V9=[22,24];
var parser = {trace: function trace() { },
yy: {},
symbols_: {"error":2,"JSONString":3,"STRING":4,"JSONNumber":5,"NUMBER":6,"JSONNullLiteral":7,"NULL":8,"JSONBooleanLiteral":9,"TRUE":10,"FALSE":11,"JSONText":12,"JSONValue":13,"EOF":14,"JSONObject":15,"JSONArray":16,"{":17,"}":18,"JSONMemberList":19,"JSONMember":20,":":21,",":22,"[":23,"]":24,"JSONElementList":25,"$accept":0,"$end":1},
terminals_: {2:"error",4:"STRING",6:"NUMBER",8:"NULL",10:"TRUE",11:"FALSE",14:"EOF",17:"{",18:"}",21:":",22:",",23:"[",24:"]"},
productions_: [0,[3,1],[5,1],[7,1],[9,1],[9,1],[12,2],[13,1],[13,1],[13,1],[13,1],[13,1],[13,1],[15,2],[15,3],[20,3],[19,1],[19,3],[16,2],[16,3],[25,1],[25,3]],
performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) {
/* this == yyval */
var $0 = $$.length - 1;
switch (yystate) {
case 1:
// replace escaped characters with actual character
this.$ = new String(yytext.replace(/\\(\\|")/g, "$"+"1")
.replace(/\\n/g,'\n')
.replace(/\\r/g,'\r')
.replace(/\\t/g,'\t')
.replace(/\\v/g,'\v')
.replace(/\\f/g,'\f')
.replace(/\\b/g,'\b'));
this.$.__line__ = this._$.first_line;
break;
case 2:
this.$ = new Number(yytext);
this.$.__line__ = this._$.first_line;
break;
case 3:
this.$ = null;
break;
case 4:
this.$ = new Boolean(true);
this.$.__line__ = this._$.first_line;
break;
case 5:
this.$ = new Boolean(false);
this.$.__line__ = this._$.first_line;
break;
case 6:
return this.$ = $$[$0-1];
case 13:
this.$ = {}; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
});
break;
case 14: case 19:
this.$ = $$[$0-1]; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
});
break;
case 15:
this.$ = [$$[$0-2], $$[$0]];
break;
case 16:
this.$ = {}; this.$[$$[$0][0]] = $$[$0][1];
break;
case 17:
this.$ = $$[$0-2]; $$[$0-2][$$[$0][0]] = $$[$0][1];
break;
case 18:
this.$ = []; Object.defineProperty(this.$, '__line__', {
value: this._$.first_line,
enumerable: false
});
break;
case 20:
this.$ = [$$[$0]];
break;
case 21:
this.$ = $$[$0-2]; $$[$0-2].push($$[$0]);
break;
}
},
table: [{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,12:1,13:2,15:7,16:8,17:$V5,23:$V6},{1:[3]},{14:[1,16]},o($V7,[2,7]),o($V7,[2,8]),o($V7,[2,9]),o($V7,[2,10]),o($V7,[2,11]),o($V7,[2,12]),o($V7,[2,3]),o($V7,[2,4]),o($V7,[2,5]),o([14,18,21,22,24],[2,1]),o($V7,[2,2]),{3:20,4:$V0,18:[1,17],19:18,20:19},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:23,15:7,16:8,17:$V5,23:$V6,24:[1,21],25:22},{1:[2,6]},o($V7,[2,13]),{18:[1,24],22:[1,25]},o($V8,[2,16]),{21:[1,26]},o($V7,[2,18]),{22:[1,28],24:[1,27]},o($V9,[2,20]),o($V7,[2,14]),{3:20,4:$V0,20:29},{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:30,15:7,16:8,17:$V5,23:$V6},o($V7,[2,19]),{3:5,4:$V0,5:6,6:$V1,7:3,8:$V2,9:4,10:$V3,11:$V4,13:31,15:7,16:8,17:$V5,23:$V6},o($V8,[2,17]),o($V8,[2,15]),o($V9,[2,21])],
defaultActions: {16:[2,6]},
parseError: function parseError(str, hash) {
if (hash.recoverable) {
this.trace(str);
} else {
throw new Error(str);
}
},
parse: function parse(input) {
var self = this, stack = [0], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, TERROR = 2, EOF = 1;
var args = lstack.slice.call(arguments, 1);
var lexer = Object.create(this.lexer);
var sharedState = { yy: {} };
for (var k in this.yy) {
if (Object.prototype.hasOwnProperty.call(this.yy, k)) {
sharedState.yy[k] = this.yy[k];
}
}
lexer.setInput(input, sharedState.yy);
sharedState.yy.lexer = lexer;
sharedState.yy.parser = this;
if (typeof lexer.yylloc == 'undefined') {
lexer.yylloc = {};
}
var yyloc = lexer.yylloc;
lstack.push(yyloc);
var ranges = lexer.options && lexer.options.ranges;
if (typeof sharedState.yy.parseError === 'function') {
this.parseError = sharedState.yy.parseError;
} else {
this.parseError = Object.getPrototypeOf(this).parseError;
}
function lex() {
var token;
token = lexer.lex() || EOF;
if (typeof token !== 'number') {
token = self.symbols_[token] || token;
}
return token;
}
var symbol, state, action, r, yyval = {}, p, len, newState, expected;
while (true) {
state = stack[stack.length - 1];
if (this.defaultActions[state]) {
action = this.defaultActions[state];
} else {
if (symbol === null || typeof symbol == 'undefined') {
symbol = lex();
}
action = table[state] && table[state][symbol];
}
if (typeof action === 'undefined' || !action.length || !action[0]) {
var errStr = '';
expected = [];
for (p in table[state]) {
if (this.terminals_[p] && p > TERROR) {
expected.push('\'' + this.terminals_[p] + '\'');
}
}
if (lexer.showPosition) {
errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\'';
} else {
errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\'');
}
this.parseError(errStr, {
text: lexer.match,
token: this.terminals_[symbol] || symbol,
line: lexer.yylineno,
loc: yyloc,
expected: expected
});
}
if (action[0] instanceof Array && action.length > 1) {
throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol);
}
switch (action[0]) {
case 1:
stack.push(symbol);
vstack.push(lexer.yytext);
lstack.push(lexer.yylloc);
stack.push(action[1]);
symbol = null;
{
yyleng = lexer.yyleng;
yytext = lexer.yytext;
yylineno = lexer.yylineno;
yyloc = lexer.yylloc;
}
break;
case 2:
len = this.productions_[action[1]][1];
yyval.$ = vstack[vstack.length - len];
yyval._$ = {
first_line: lstack[lstack.length - (len || 1)].first_line,
last_line: lstack[lstack.length - 1].last_line,
first_column: lstack[lstack.length - (len || 1)].first_column,
last_column: lstack[lstack.length - 1].last_column
};
if (ranges) {
yyval._$.range = [
lstack[lstack.length - (len || 1)].range[0],
lstack[lstack.length - 1].range[1]
];
}
r = this.performAction.apply(yyval, [
yytext,
yyleng,
yylineno,
sharedState.yy,
action[1],
vstack,
lstack
].concat(args));
if (typeof r !== 'undefined') {
return r;
}
if (len) {
stack = stack.slice(0, -1 * len * 2);
vstack = vstack.slice(0, -1 * len);
lstack = lstack.slice(0, -1 * len);
}
stack.push(this.productions_[action[1]][0]);
vstack.push(yyval.$);
lstack.push(yyval._$);
newState = table[stack[stack.length - 2]][stack[stack.length - 1]];
stack.push(newState);
break;
case 3:
return true;
}
}
return true;
}};
/* generated by jison-lex 0.3.4 */
var lexer = (function(){
var lexer = ({
EOF:1,
parseError:function parseError(str, hash) {
if (this.yy.parser) {
this.yy.parser.parseError(str, hash);
} else {
throw new Error(str);
}
},
// resets the lexer, sets new input
setInput:function (input, yy) {
this.yy = yy || this.yy || {};
this._input = input;
this._more = this._backtrack = this.done = false;
this.yylineno = this.yyleng = 0;
this.yytext = this.matched = this.match = '';
this.conditionStack = ['INITIAL'];
this.yylloc = {
first_line: 1,
first_column: 0,
last_line: 1,
last_column: 0
};
if (this.options.ranges) {
this.yylloc.range = [0,0];
}
this.offset = 0;
return this;
},
// consumes and returns one char from the input
input:function () {
var ch = this._input[0];
this.yytext += ch;
this.yyleng++;
this.offset++;
this.match += ch;
this.matched += ch;
var lines = ch.match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno++;
this.yylloc.last_line++;
} else {
this.yylloc.last_column++;
}
if (this.options.ranges) {
this.yylloc.range[1]++;
}
this._input = this._input.slice(1);
return ch;
},
// unshifts one char (or a string) into the input
unput:function (ch) {
var len = ch.length;
var lines = ch.split(/(?:\r\n?|\n)/g);
this._input = ch + this._input;
this.yytext = this.yytext.substr(0, this.yytext.length - len);
//this.yyleng -= len;
this.offset -= len;
var oldLines = this.match.split(/(?:\r\n?|\n)/g);
this.match = this.match.substr(0, this.match.length - 1);
this.matched = this.matched.substr(0, this.matched.length - 1);
if (lines.length - 1) {
this.yylineno -= lines.length - 1;
}
var r = this.yylloc.range;
this.yylloc = {
first_line: this.yylloc.first_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.first_column,
last_column: lines ?
(lines.length === oldLines.length ? this.yylloc.first_column : 0)
+ oldLines[oldLines.length - lines.length].length - lines[0].length :
this.yylloc.first_column - len
};
if (this.options.ranges) {
this.yylloc.range = [r[0], r[0] + this.yyleng - len];
}
this.yyleng = this.yytext.length;
return this;
},
// When called from action, caches matched text and appends it on next action
more:function () {
this._more = true;
return this;
},
// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead.
reject:function () {
if (this.options.backtrack_lexer) {
this._backtrack = true;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
return this;
},
// retain first n characters of the match
less:function (n) {
this.unput(this.match.slice(n));
},
// displays already matched input, i.e. for error messages
pastInput:function () {
var past = this.matched.substr(0, this.matched.length - this.match.length);
return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, "");
},
// displays upcoming input, i.e. for error messages
upcomingInput:function () {
var next = this.match;
if (next.length < 20) {
next += this._input.substr(0, 20-next.length);
}
return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, "");
},
// displays the character position where the lexing error occurred, i.e. for error messages
showPosition:function () {
var pre = this.pastInput();
var c = new Array(pre.length + 1).join("-");
return pre + this.upcomingInput() + "\n" + c + "^";
},
// test the lexed token: return FALSE when not a match, otherwise return token
test_match:function (match, indexed_rule) {
var token,
lines,
backup;
if (this.options.backtrack_lexer) {
// save context
backup = {
yylineno: this.yylineno,
yylloc: {
first_line: this.yylloc.first_line,
last_line: this.last_line,
first_column: this.yylloc.first_column,
last_column: this.yylloc.last_column
},
yytext: this.yytext,
match: this.match,
matches: this.matches,
matched: this.matched,
yyleng: this.yyleng,
offset: this.offset,
_more: this._more,
_input: this._input,
yy: this.yy,
conditionStack: this.conditionStack.slice(0),
done: this.done
};
if (this.options.ranges) {
backup.yylloc.range = this.yylloc.range.slice(0);
}
}
lines = match[0].match(/(?:\r\n?|\n).*/g);
if (lines) {
this.yylineno += lines.length;
}
this.yylloc = {
first_line: this.yylloc.last_line,
last_line: this.yylineno + 1,
first_column: this.yylloc.last_column,
last_column: lines ?
lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length :
this.yylloc.last_column + match[0].length
};
this.yytext += match[0];
this.match += match[0];
this.matches = match;
this.yyleng = this.yytext.length;
if (this.options.ranges) {
this.yylloc.range = [this.offset, this.offset += this.yyleng];
}
this._more = false;
this._backtrack = false;
this._input = this._input.slice(match[0].length);
this.matched += match[0];
token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]);
if (this.done && this._input) {
this.done = false;
}
if (token) {
return token;
} else if (this._backtrack) {
// recover context
for (var k in backup) {
this[k] = backup[k];
}
return false; // rule action called reject() implying the next rule should be tested instead.
}
return false;
},
// return next match in input
next:function () {
if (this.done) {
return this.EOF;
}
if (!this._input) {
this.done = true;
}
var token,
match,
tempMatch,
index;
if (!this._more) {
this.yytext = '';
this.match = '';
}
var rules = this._currentRules();
for (var i = 0; i < rules.length; i++) {
tempMatch = this._input.match(this.rules[rules[i]]);
if (tempMatch && (!match || tempMatch[0].length > match[0].length)) {
match = tempMatch;
index = i;
if (this.options.backtrack_lexer) {
token = this.test_match(tempMatch, rules[i]);
if (token !== false) {
return token;
} else if (this._backtrack) {
match = false;
continue; // rule action called reject() implying a rule MISmatch.
} else {
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
} else if (!this.options.flex) {
break;
}
}
}
if (match) {
token = this.test_match(match, rules[index]);
if (token !== false) {
return token;
}
// else: this is a lexer rule which consumes input without producing a token (e.g. whitespace)
return false;
}
if (this._input === "") {
return this.EOF;
} else {
return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), {
text: "",
token: null,
line: this.yylineno
});
}
},
// return next match that has a token
lex:function lex() {
var r = this.next();
if (r) {
return r;
} else {
return this.lex();
}
},
// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack)
begin:function begin(condition) {
this.conditionStack.push(condition);
},
// pop the previously active lexer condition state off the condition stack
popState:function popState() {
var n = this.conditionStack.length - 1;
if (n > 0) {
return this.conditionStack.pop();
} else {
return this.conditionStack[0];
}
},
// produce the lexer rule set which is active for the currently active lexer condition state
_currentRules:function _currentRules() {
if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) {
return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules;
} else {
return this.conditions["INITIAL"].rules;
}
},
// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available
topState:function topState(n) {
n = this.conditionStack.length - 1 - Math.abs(n || 0);
if (n >= 0) {
return this.conditionStack[n];
} else {
return "INITIAL";
}
},
// alias for begin(condition)
pushState:function pushState(condition) {
this.begin(condition);
},
// return the number of states currently on the stack
stateStackSize:function stateStackSize() {
return this.conditionStack.length;
},
options: {},
performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) {
switch($avoiding_name_collisions) {
case 0:/* skip whitespace */
break;
case 1:return 6
case 2:yy_.yytext = yy_.yytext.substr(1,yy_.yyleng-2); return 4
case 3:return 17
case 4:return 18
case 5:return 23
case 6:return 24
case 7:return 22
case 8:return 21
case 9:return 10
case 10:return 11
case 11:return 8
case 12:return 14
case 13:return 'INVALID'
}
},
rules: [/^(?:\s+)/,/^(?:(-?([0-9]|[1-9][0-9]+))(\.[0-9]+)?([eE][-+]?[0-9]+)?\b)/,/^(?:"(?:\\[\\"bfnrt/]|\\u[a-fA-F0-9]{4}|[^\\\0-\x09\x0a-\x1f"])*")/,/^(?:\{)/,/^(?:\})/,/^(?:\[)/,/^(?:\])/,/^(?:,)/,/^(?::)/,/^(?:true\b)/,/^(?:false\b)/,/^(?:null\b)/,/^(?:$)/,/^(?:.)/],
conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13],"inclusive":true}}
});
return lexer;
})();
parser.lexer = lexer;
function Parser () {
this.yy = {};
}
Parser.prototype = parser;parser.Parser = Parser;
return new Parser;
})();
if (typeof commonjsRequire !== 'undefined' && 'object' !== 'undefined') {
exports.parser = parser;
exports.Parser = parser.Parser;
exports.parse = function () { return parser.parse.apply(parser, arguments); };
}
} (jsonlint$1));
return jsonlint$1;
}
var jsonlintExports = requireJsonlint();
var jsonlint = /*@__PURE__*/getDefaultExportFromCjs(jsonlintExports);
function readStyle(style) {
if (style instanceof String || typeof style === 'string' || style instanceof Buffer) {
try {
return jsonlint.parse(style.toString());
}
catch (e) {
throw new ParsingError(e);
}
}
return style;
}
/**
* Validate a MapLibre GL style against the style specification.
*
* @param style - The style to be validated. If a `String` or `Buffer` is provided,
* the returned errors will contain line numbers.
* @param styleSpec - The style specification to validate against.
* If omitted, the spec version is inferred from the stylesheet.
* @returns an array of errors, or an empty array if no errors are found.
* @example
* const validate = require('maplibre-gl-style-spec').validate;
* const style = fs.readFileSync('./style.json', 'utf8');
* const errors = validate(style);
*/
function validateStyle(style, styleSpec = v8) {
let s = style;
try {
s = readStyle(s);
}
catch (e) {
return [e];
}
return validateStyleMin(s, styleSpec);
}
const argv = minimist(process.argv.slice(2), {
boolean: 'json',
});
if (argv.help || argv.h || (!argv._.length && process.stdin.isTTY)) {
help();
} else {
let status = 0;
if (!argv._.length) {
argv._.push('/dev/stdin');
}
argv._.forEach((file) => {
const errors = validateStyle(rw.readFileSync(file, 'utf8'));
if (errors.length) {
if (argv.json) {
process.stdout.write(JSON.stringify(errors, null, 2));
} else {
errors.forEach((e) => {
console.log('%s:%d: %s', file, e.line, e.message);
});
}
status = 1;
}
});
process.exit(status);
}
function help() {
console.log('usage:');
console.log(' gl-style-validate file.json');
console.log(' gl-style-validate < file.json');
console.log('');
console.log('options:');
console.log('--json output errors as json');
}
//# sourceMappingURL=gl-style-validate.mjs.map