This commit is contained in:
270
node_modules/@mapbox/mapbox-gl-style-spec/function/convert.js
generated
vendored
Normal file
270
node_modules/@mapbox/mapbox-gl-style-spec/function/convert.js
generated
vendored
Normal file
@@ -0,0 +1,270 @@
|
||||
// @flow
|
||||
|
||||
import assert from 'assert';
|
||||
|
||||
import type {StylePropertySpecification} from '../style-spec.js';
|
||||
import type {ExpressionSpecification} from '../types.js';
|
||||
|
||||
function convertLiteral(value) {
|
||||
return typeof value === 'object' ? ['literal', value] : value;
|
||||
}
|
||||
|
||||
export default function convertFunction(parameters: any, propertySpec: StylePropertySpecification): ExpressionSpecification {
|
||||
let stops = parameters.stops;
|
||||
if (!stops) {
|
||||
// identity function
|
||||
return convertIdentityFunction(parameters, propertySpec);
|
||||
}
|
||||
|
||||
const zoomAndFeatureDependent = stops && typeof stops[0][0] === 'object';
|
||||
const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
|
||||
const zoomDependent = zoomAndFeatureDependent || !featureDependent;
|
||||
|
||||
stops = stops.map((stop) => {
|
||||
if (!featureDependent && propertySpec.tokens && typeof stop[1] === 'string') {
|
||||
return [stop[0], convertTokenString(stop[1])];
|
||||
}
|
||||
return [stop[0], convertLiteral(stop[1])];
|
||||
});
|
||||
|
||||
if (zoomAndFeatureDependent) {
|
||||
return convertZoomAndPropertyFunction(parameters, propertySpec, stops);
|
||||
} else if (zoomDependent) {
|
||||
return convertZoomFunction(parameters, propertySpec, stops);
|
||||
} else {
|
||||
return convertPropertyFunction(parameters, propertySpec, stops);
|
||||
}
|
||||
}
|
||||
|
||||
function convertIdentityFunction(parameters, propertySpec): Array<mixed> {
|
||||
const get = ['get', parameters.property];
|
||||
|
||||
if (parameters.default === undefined) {
|
||||
// By default, expressions for string-valued properties get coerced. To preserve
|
||||
// legacy function semantics, insert an explicit assertion instead.
|
||||
return propertySpec.type === 'string' ? ['string', get] : get;
|
||||
} else if (propertySpec.type === 'enum') {
|
||||
return [
|
||||
'match',
|
||||
get,
|
||||
Object.keys(propertySpec.values),
|
||||
get,
|
||||
parameters.default
|
||||
];
|
||||
} else {
|
||||
const expression = [propertySpec.type === 'color' ? 'to-color' : propertySpec.type, get, convertLiteral(parameters.default)];
|
||||
if (propertySpec.type === 'array') {
|
||||
expression.splice(1, 0, propertySpec.value, propertySpec.length || null);
|
||||
}
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
function getInterpolateOperator(parameters) {
|
||||
switch (parameters.colorSpace) {
|
||||
case 'hcl': return 'interpolate-hcl';
|
||||
case 'lab': return 'interpolate-lab';
|
||||
default: return 'interpolate';
|
||||
}
|
||||
}
|
||||
|
||||
function convertZoomAndPropertyFunction(parameters, propertySpec, stops) {
|
||||
const featureFunctionParameters = {};
|
||||
const featureFunctionStops = {};
|
||||
const zoomStops = [];
|
||||
for (let s = 0; s < stops.length; s++) {
|
||||
const stop = stops[s];
|
||||
const zoom = stop[0].zoom;
|
||||
if (featureFunctionParameters[zoom] === undefined) {
|
||||
featureFunctionParameters[zoom] = {
|
||||
zoom,
|
||||
type: parameters.type,
|
||||
property: parameters.property,
|
||||
default: parameters.default,
|
||||
};
|
||||
featureFunctionStops[zoom] = [];
|
||||
zoomStops.push(zoom);
|
||||
}
|
||||
featureFunctionStops[zoom].push([stop[0].value, stop[1]]);
|
||||
}
|
||||
|
||||
// the interpolation type for the zoom dimension of a zoom-and-property
|
||||
// function is determined directly from the style property specification
|
||||
// for which it's being used: linear for interpolatable properties, step
|
||||
// otherwise.
|
||||
const functionType = getFunctionType({}, propertySpec);
|
||||
if (functionType === 'exponential') {
|
||||
const expression = [getInterpolateOperator(parameters), ['linear'], ['zoom']];
|
||||
|
||||
for (const z of zoomStops) {
|
||||
const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
|
||||
appendStopPair(expression, z, output, false);
|
||||
}
|
||||
|
||||
return expression;
|
||||
} else {
|
||||
const expression = ['step', ['zoom']];
|
||||
|
||||
for (const z of zoomStops) {
|
||||
const output = convertPropertyFunction(featureFunctionParameters[z], propertySpec, featureFunctionStops[z]);
|
||||
appendStopPair(expression, z, output, true);
|
||||
}
|
||||
|
||||
fixupDegenerateStepCurve(expression);
|
||||
|
||||
return expression;
|
||||
}
|
||||
}
|
||||
|
||||
function coalesce(a, b) {
|
||||
if (a !== undefined) return a;
|
||||
if (b !== undefined) return b;
|
||||
}
|
||||
|
||||
function getFallback(parameters, propertySpec) {
|
||||
const defaultValue = convertLiteral(coalesce(parameters.default, propertySpec.default));
|
||||
|
||||
/*
|
||||
* Some fields with type: resolvedImage have an undefined default.
|
||||
* Because undefined is an invalid value for resolvedImage, set fallback to
|
||||
* an empty string instead of undefined to ensure output
|
||||
* passes validation.
|
||||
*/
|
||||
if (defaultValue === undefined && propertySpec.type === 'resolvedImage') {
|
||||
return '';
|
||||
}
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
function convertPropertyFunction(parameters, propertySpec, stops) {
|
||||
const type = getFunctionType(parameters, propertySpec);
|
||||
const get = ['get', parameters.property];
|
||||
if (type === 'categorical' && typeof stops[0][0] === 'boolean') {
|
||||
assert(parameters.stops.length > 0 && parameters.stops.length <= 2);
|
||||
const expression = ['case'];
|
||||
for (const stop of stops) {
|
||||
expression.push(['==', get, stop[0]], stop[1]);
|
||||
}
|
||||
|
||||
expression.push(getFallback(parameters, propertySpec));
|
||||
return expression;
|
||||
} else if (type === 'categorical') {
|
||||
const expression = ['match', get];
|
||||
for (const stop of stops) {
|
||||
appendStopPair(expression, stop[0], stop[1], false);
|
||||
}
|
||||
expression.push(getFallback(parameters, propertySpec));
|
||||
return expression;
|
||||
} else if (type === 'interval') {
|
||||
const expression = ['step', ['number', get]];
|
||||
for (const stop of stops) {
|
||||
appendStopPair(expression, stop[0], stop[1], true);
|
||||
}
|
||||
fixupDegenerateStepCurve(expression);
|
||||
return parameters.default === undefined ? expression : [
|
||||
'case',
|
||||
['==', ['typeof', get], 'number'],
|
||||
expression,
|
||||
convertLiteral(parameters.default)
|
||||
];
|
||||
} else if (type === 'exponential') {
|
||||
const base = parameters.base !== undefined ? parameters.base : 1;
|
||||
const expression = [
|
||||
getInterpolateOperator(parameters),
|
||||
base === 1 ? ["linear"] : ["exponential", base],
|
||||
["number", get]
|
||||
];
|
||||
|
||||
for (const stop of stops) {
|
||||
appendStopPair(expression, stop[0], stop[1], false);
|
||||
}
|
||||
return parameters.default === undefined ? expression : [
|
||||
'case',
|
||||
['==', ['typeof', get], 'number'],
|
||||
expression,
|
||||
convertLiteral(parameters.default)
|
||||
];
|
||||
} else {
|
||||
throw new Error(`Unknown property function type ${type}`);
|
||||
}
|
||||
}
|
||||
|
||||
function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom']) {
|
||||
const type = getFunctionType(parameters, propertySpec);
|
||||
let expression;
|
||||
let isStep = false;
|
||||
if (type === 'interval') {
|
||||
expression = ['step', input];
|
||||
isStep = true;
|
||||
} else if (type === 'exponential') {
|
||||
const base = parameters.base !== undefined ? parameters.base : 1;
|
||||
expression = [getInterpolateOperator(parameters), base === 1 ? ["linear"] : ["exponential", base], input];
|
||||
|
||||
} else {
|
||||
throw new Error(`Unknown zoom function type "${type}"`);
|
||||
}
|
||||
|
||||
for (const stop of stops) {
|
||||
appendStopPair(expression, stop[0], stop[1], isStep);
|
||||
}
|
||||
|
||||
fixupDegenerateStepCurve(expression);
|
||||
|
||||
return expression;
|
||||
}
|
||||
|
||||
function fixupDegenerateStepCurve(expression) {
|
||||
// degenerate step curve (i.e. a constant function): add a noop stop
|
||||
if (expression[0] === 'step' && expression.length === 3) {
|
||||
expression.push(0);
|
||||
expression.push(expression[3]);
|
||||
}
|
||||
}
|
||||
|
||||
function appendStopPair(curve, input, output, isStep) {
|
||||
// Skip duplicate stop values. They were not validated for functions, but they are for expressions.
|
||||
// https://github.com/mapbox/mapbox-gl-js/issues/4107
|
||||
if (curve.length > 3 && input === curve[curve.length - 2]) {
|
||||
return;
|
||||
}
|
||||
// step curves don't get the first input value, as it is redundant.
|
||||
if (!(isStep && curve.length === 2)) {
|
||||
curve.push(input);
|
||||
}
|
||||
curve.push(output);
|
||||
}
|
||||
|
||||
function getFunctionType(parameters, propertySpec) {
|
||||
if (parameters.type) {
|
||||
return parameters.type;
|
||||
} else {
|
||||
assert(propertySpec.expression);
|
||||
return (propertySpec.expression: any).interpolated ? 'exponential' : 'interval';
|
||||
}
|
||||
}
|
||||
|
||||
// "String with {name} token" => ["concat", "String with ", ["get", "name"], " token"]
|
||||
export function convertTokenString(s: string): string | ExpressionSpecification {
|
||||
const result = ['concat'];
|
||||
const re = /{([^{}]+)}/g;
|
||||
let pos = 0;
|
||||
for (let match = re.exec(s); match !== null; match = re.exec(s)) {
|
||||
const literal = s.slice(pos, re.lastIndex - match[0].length);
|
||||
pos = re.lastIndex;
|
||||
if (literal.length > 0) result.push(literal);
|
||||
result.push(['get', match[1]]);
|
||||
}
|
||||
|
||||
if (result.length === 1) {
|
||||
return s;
|
||||
}
|
||||
|
||||
if (pos < s.length) {
|
||||
result.push(s.slice(pos));
|
||||
} else if (result.length === 2) {
|
||||
return ['to-string', result[1]];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
262
node_modules/@mapbox/mapbox-gl-style-spec/function/index.js
generated
vendored
Normal file
262
node_modules/@mapbox/mapbox-gl-style-spec/function/index.js
generated
vendored
Normal file
@@ -0,0 +1,262 @@
|
||||
|
||||
import * as colorSpaces from '../util/color_spaces.js';
|
||||
import Color from '../util/color.js';
|
||||
import extend from '../util/extend.js';
|
||||
import getType from '../util/get_type.js';
|
||||
import * as interpolate from '../util/interpolate.js';
|
||||
import Interpolate from '../expression/definitions/interpolate.js';
|
||||
import Formatted from '../expression/types/formatted.js';
|
||||
import ResolvedImage from '../expression/types/resolved_image.js';
|
||||
import {supportsInterpolation} from '../util/properties.js';
|
||||
import {findStopLessThanOrEqualTo} from '../expression/stops.js';
|
||||
|
||||
export function isFunction(value) {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function identityFunction(x) {
|
||||
return x;
|
||||
}
|
||||
|
||||
export function createFunction(parameters, propertySpec) {
|
||||
const isColor = propertySpec.type === 'color';
|
||||
const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object';
|
||||
const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined;
|
||||
const zoomDependent = zoomAndFeatureDependent || !featureDependent;
|
||||
const type = parameters.type || (supportsInterpolation(propertySpec) ? 'exponential' : 'interval');
|
||||
|
||||
if (isColor) {
|
||||
parameters = extend({}, parameters);
|
||||
|
||||
if (parameters.stops) {
|
||||
parameters.stops = parameters.stops.map((stop) => {
|
||||
return [stop[0], Color.parse(stop[1])];
|
||||
});
|
||||
}
|
||||
|
||||
if (parameters.default) {
|
||||
parameters.default = Color.parse(parameters.default);
|
||||
} else {
|
||||
parameters.default = Color.parse(propertySpec.default);
|
||||
}
|
||||
}
|
||||
|
||||
if (parameters.colorSpace && parameters.colorSpace !== 'rgb' && !colorSpaces[parameters.colorSpace]) { // eslint-disable-line import/namespace
|
||||
throw new Error(`Unknown color space: ${parameters.colorSpace}`);
|
||||
}
|
||||
|
||||
let innerFun;
|
||||
let hashedStops;
|
||||
let categoricalKeyType;
|
||||
if (type === 'exponential') {
|
||||
innerFun = evaluateExponentialFunction;
|
||||
} else if (type === 'interval') {
|
||||
innerFun = evaluateIntervalFunction;
|
||||
} else if (type === 'categorical') {
|
||||
innerFun = evaluateCategoricalFunction;
|
||||
|
||||
// For categorical functions, generate an Object as a hashmap of the stops for fast searching
|
||||
hashedStops = Object.create(null);
|
||||
for (const stop of parameters.stops) {
|
||||
hashedStops[stop[0]] = stop[1];
|
||||
}
|
||||
|
||||
// Infer key type based on first stop key-- used to encforce strict type checking later
|
||||
categoricalKeyType = typeof parameters.stops[0][0];
|
||||
|
||||
} else if (type === 'identity') {
|
||||
innerFun = evaluateIdentityFunction;
|
||||
} else {
|
||||
throw new Error(`Unknown function type "${type}"`);
|
||||
}
|
||||
|
||||
if (zoomAndFeatureDependent) {
|
||||
const featureFunctions = {};
|
||||
const zoomStops = [];
|
||||
for (let s = 0; s < parameters.stops.length; s++) {
|
||||
const stop = parameters.stops[s];
|
||||
const zoom = stop[0].zoom;
|
||||
if (featureFunctions[zoom] === undefined) {
|
||||
featureFunctions[zoom] = {
|
||||
zoom,
|
||||
type: parameters.type,
|
||||
property: parameters.property,
|
||||
default: parameters.default,
|
||||
stops: []
|
||||
};
|
||||
zoomStops.push(zoom);
|
||||
}
|
||||
featureFunctions[zoom].stops.push([stop[0].value, stop[1]]);
|
||||
}
|
||||
|
||||
const featureFunctionStops = [];
|
||||
for (const z of zoomStops) {
|
||||
featureFunctionStops.push([featureFunctions[z].zoom, createFunction(featureFunctions[z], propertySpec)]);
|
||||
}
|
||||
|
||||
const interpolationType = {name: 'linear'};
|
||||
return {
|
||||
kind: 'composite',
|
||||
interpolationType,
|
||||
interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType),
|
||||
zoomStops: featureFunctionStops.map(s => s[0]),
|
||||
evaluate({zoom}, properties) {
|
||||
return evaluateExponentialFunction({
|
||||
stops: featureFunctionStops,
|
||||
base: parameters.base
|
||||
}, propertySpec, zoom).evaluate(zoom, properties);
|
||||
}
|
||||
};
|
||||
} else if (zoomDependent) {
|
||||
const interpolationType = type === 'exponential' ?
|
||||
{name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1} : null;
|
||||
return {
|
||||
kind: 'camera',
|
||||
interpolationType,
|
||||
interpolationFactor: Interpolate.interpolationFactor.bind(undefined, interpolationType),
|
||||
zoomStops: parameters.stops.map(s => s[0]),
|
||||
evaluate: ({zoom}) => innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)
|
||||
};
|
||||
} else {
|
||||
return {
|
||||
kind: 'source',
|
||||
evaluate(_, feature) {
|
||||
const value = feature && feature.properties ? feature.properties[parameters.property] : undefined;
|
||||
if (value === undefined) {
|
||||
return coalesce(parameters.default, propertySpec.default);
|
||||
}
|
||||
return innerFun(parameters, propertySpec, value, hashedStops, categoricalKeyType);
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
function coalesce(a, b, c) {
|
||||
if (a !== undefined) return a;
|
||||
if (b !== undefined) return b;
|
||||
if (c !== undefined) return c;
|
||||
}
|
||||
|
||||
function evaluateCategoricalFunction(parameters, propertySpec, input, hashedStops, keyType) {
|
||||
const evaluated = typeof input === keyType ? hashedStops[input] : undefined; // Enforce strict typing on input
|
||||
return coalesce(evaluated, parameters.default, propertySpec.default);
|
||||
}
|
||||
|
||||
function evaluateIntervalFunction(parameters, propertySpec, input) {
|
||||
// Edge cases
|
||||
if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default);
|
||||
const n = parameters.stops.length;
|
||||
if (n === 1) return parameters.stops[0][1];
|
||||
if (input <= parameters.stops[0][0]) return parameters.stops[0][1];
|
||||
if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1];
|
||||
|
||||
const index = findStopLessThanOrEqualTo(parameters.stops.map((stop) => stop[0]), input);
|
||||
|
||||
return parameters.stops[index][1];
|
||||
}
|
||||
|
||||
function evaluateExponentialFunction(parameters, propertySpec, input) {
|
||||
const base = parameters.base !== undefined ? parameters.base : 1;
|
||||
|
||||
// Edge cases
|
||||
if (getType(input) !== 'number') return coalesce(parameters.default, propertySpec.default);
|
||||
const n = parameters.stops.length;
|
||||
if (n === 1) return parameters.stops[0][1];
|
||||
if (input <= parameters.stops[0][0]) return parameters.stops[0][1];
|
||||
if (input >= parameters.stops[n - 1][0]) return parameters.stops[n - 1][1];
|
||||
|
||||
const index = findStopLessThanOrEqualTo(parameters.stops.map((stop) => stop[0]), input);
|
||||
const t = interpolationFactor(
|
||||
input, base,
|
||||
parameters.stops[index][0],
|
||||
parameters.stops[index + 1][0]);
|
||||
|
||||
const outputLower = parameters.stops[index][1];
|
||||
const outputUpper = parameters.stops[index + 1][1];
|
||||
let interp = interpolate[propertySpec.type] || identityFunction; // eslint-disable-line import/namespace
|
||||
|
||||
if (parameters.colorSpace && parameters.colorSpace !== 'rgb') {
|
||||
const colorspace = colorSpaces[parameters.colorSpace]; // eslint-disable-line import/namespace
|
||||
interp = (a, b) => colorspace.reverse(colorspace.interpolate(colorspace.forward(a), colorspace.forward(b), t));
|
||||
}
|
||||
|
||||
if (typeof outputLower.evaluate === 'function') {
|
||||
return {
|
||||
evaluate(...args) {
|
||||
const evaluatedLower = outputLower.evaluate.apply(undefined, args);
|
||||
const evaluatedUpper = outputUpper.evaluate.apply(undefined, args);
|
||||
// Special case for fill-outline-color, which has no spec default.
|
||||
if (evaluatedLower === undefined || evaluatedUpper === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
return interp(evaluatedLower, evaluatedUpper, t);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return interp(outputLower, outputUpper, t);
|
||||
}
|
||||
|
||||
function evaluateIdentityFunction(parameters, propertySpec, input) {
|
||||
if (propertySpec.type === 'color') {
|
||||
input = Color.parse(input);
|
||||
} else if (propertySpec.type === 'formatted') {
|
||||
input = Formatted.fromString(input.toString());
|
||||
} else if (propertySpec.type === 'resolvedImage') {
|
||||
input = ResolvedImage.fromString(input.toString());
|
||||
} else if (getType(input) !== propertySpec.type && (propertySpec.type !== 'enum' || !propertySpec.values[input])) {
|
||||
input = undefined;
|
||||
}
|
||||
return coalesce(input, parameters.default, propertySpec.default);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 interpolationFactor(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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user