@mapbox/mapbox-gl-style-spec
Advanced tools
Comparing version 9.0.1 to 10.0.0
@@ -0,1 +1,5 @@ | ||
## 10.0.0 | ||
* Add expression and heatmap layer support. See Mapbox GL JS v0.40.1 changelog entry for details. | ||
## 9.0.1 | ||
@@ -2,0 +6,0 @@ |
18
diff.js
@@ -47,2 +47,7 @@ | ||
/* | ||
* { command: 'setGeoJSONSourceData', args: ['sourceId', data] } | ||
*/ | ||
setGeoJSONSourceData: 'setGeoJSONSourceData', | ||
/* | ||
* { command: 'setLayerZoomRange', args: ['layerId', 0, 22] } | ||
@@ -121,6 +126,11 @@ */ | ||
} else if (!isEqual(before[sourceId], after[sourceId])) { | ||
// no update command, must remove then add | ||
commands.push({ command: operations.removeSource, args: [sourceId] }); | ||
commands.push({ command: operations.addSource, args: [sourceId, after[sourceId]] }); | ||
sourcesRemoved[sourceId] = true; | ||
if (before[sourceId].type === 'geojson' && after[sourceId].type === 'geojson') { | ||
// geojson sources use setGeoJSONSourceData command to update | ||
commands.push({ command: operations.setGeoJSONSourceData, args: [sourceId, after[sourceId].data] }); | ||
} else { | ||
// no update command, must remove then add | ||
commands.push({ command: operations.removeSource, args: [sourceId] }); | ||
commands.push({ command: operations.addSource, args: [sourceId, after[sourceId]] }); | ||
sourcesRemoved[sourceId] = true; | ||
} | ||
} | ||
@@ -127,0 +137,0 @@ } |
@@ -0,5 +1,56 @@ | ||
// @flow | ||
const {createExpression} = require('../expression'); | ||
import type {GlobalProperties} from '../expression'; | ||
export type FeatureFilter = (globalProperties: GlobalProperties, feature: VectorTileFeature) => boolean; | ||
module.exports = createFilter; | ||
module.exports.isExpressionFilter = isExpressionFilter; | ||
function isExpressionFilter(filter) { | ||
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': | ||
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; | ||
} | ||
} | ||
const types = ['Unknown', 'Point', 'LineString', 'Polygon']; | ||
const filterSpec = { | ||
'type': 'boolean', | ||
'default': false, | ||
'function': true, | ||
'property-function': true | ||
}; | ||
/** | ||
@@ -14,4 +65,17 @@ * Given a filter expressed as nested arrays, return a new function | ||
*/ | ||
function createFilter(filter) { | ||
return new Function('f', `var p = (f && f.properties || {}); return ${compile(filter)}`); | ||
function createFilter(filter: any): FeatureFilter { | ||
if (!filter) { | ||
return () => true; | ||
} | ||
if (!isExpressionFilter(filter)) { | ||
return (new Function('g', 'f', `var p = (f && f.properties || {}); return ${compile(filter)}`): any); | ||
} | ||
const compiled = createExpression(filter, filterSpec, 'filter'); | ||
if (compiled.result === 'success') { | ||
return compiled.evaluate; | ||
} else { | ||
throw new Error(compiled.errors.map(err => `${err.key}: ${err.message}`).join(', ')); | ||
} | ||
} | ||
@@ -18,0 +82,0 @@ |
const colorSpaces = require('./color_spaces'); | ||
const colorSpaces = require('../util/color_spaces'); | ||
const parseColor = require('../util/parse_color'); | ||
@@ -8,2 +8,6 @@ const extend = require('../util/extend'); | ||
function isFunction(value) { | ||
return typeof value === 'object' && value !== null && !Array.isArray(value); | ||
} | ||
function identityFunction(x) { | ||
@@ -13,130 +17,132 @@ return x; | ||
function createFunction(parameters, propertySpec) { | ||
function createFunction(parameters, propertySpec, name) { | ||
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 || (propertySpec.function === 'interpolated' ? 'exponential' : 'interval'); | ||
let fun; | ||
if (isColor) { | ||
parameters = extend({}, parameters); | ||
if (!isFunctionDefinition(parameters)) { | ||
if (isColor && parameters) { | ||
parameters = parseColor(parameters); | ||
if (parameters.stops) { | ||
parameters.stops = parameters.stops.map((stop) => { | ||
return [stop[0], parseColor(stop[1])]; | ||
}); | ||
} | ||
fun = function() { | ||
return parameters; | ||
}; | ||
fun.isFeatureConstant = true; | ||
fun.isZoomConstant = true; | ||
} else { | ||
const zoomAndFeatureDependent = parameters.stops && typeof parameters.stops[0][0] === 'object'; | ||
const featureDependent = zoomAndFeatureDependent || parameters.property !== undefined; | ||
const zoomDependent = zoomAndFeatureDependent || !featureDependent; | ||
const type = parameters.type || (propertySpec.function === 'interpolated' ? 'exponential' : 'interval'); | ||
if (parameters.default) { | ||
parameters.default = parseColor(parameters.default); | ||
} else { | ||
parameters.default = parseColor(propertySpec.default); | ||
} | ||
} | ||
if (isColor) { | ||
parameters = extend({}, parameters); | ||
let innerFun; | ||
let hashedStops; | ||
let categoricalKeyType; | ||
if (type === 'exponential') { | ||
innerFun = evaluateExponentialFunction; | ||
} else if (type === 'interval') { | ||
innerFun = evaluateIntervalFunction; | ||
} else if (type === 'categorical') { | ||
innerFun = evaluateCategoricalFunction; | ||
if (parameters.stops) { | ||
parameters.stops = parameters.stops.map((stop) => { | ||
return [stop[0], parseColor(stop[1])]; | ||
}); | ||
} | ||
if (parameters.default) { | ||
parameters.default = parseColor(parameters.default); | ||
} else { | ||
parameters.default = parseColor(propertySpec.default); | ||
} | ||
// 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]; | ||
} | ||
let innerFun; | ||
let hashedStops; | ||
let categoricalKeyType; | ||
if (type === 'exponential') { | ||
innerFun = evaluateExponentialFunction; | ||
} else if (type === 'interval') { | ||
innerFun = evaluateIntervalFunction; | ||
} else if (type === 'categorical') { | ||
innerFun = evaluateCategoricalFunction; | ||
// Infer key type based on first stop key-- used to encforce strict type checking later | ||
categoricalKeyType = typeof parameters.stops[0][0]; | ||
// 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]; | ||
} | ||
} else if (type === 'identity') { | ||
innerFun = evaluateIdentityFunction; | ||
} else { | ||
throw new Error(`Unknown function type "${type}"`); | ||
} | ||
// Infer key type based on first stop key-- used to encforce strict type checking later | ||
categoricalKeyType = typeof parameters.stops[0][0]; | ||
let outputFunction; | ||
} else if (type === 'identity') { | ||
innerFun = evaluateIdentityFunction; | ||
// If we're interpolating colors in a color system other than RGBA, | ||
// first translate all stop values to that color system, then interpolate | ||
// arrays as usual. The `outputFunction` option lets us then translate | ||
// the result of that interpolation back into RGBA. | ||
if (parameters.colorSpace && parameters.colorSpace !== 'rgb') { | ||
if (colorSpaces[parameters.colorSpace]) { | ||
const colorspace = colorSpaces[parameters.colorSpace]; | ||
// Avoid mutating the parameters value | ||
parameters = JSON.parse(JSON.stringify(parameters)); | ||
for (let s = 0; s < parameters.stops.length; s++) { | ||
parameters.stops[s] = [ | ||
parameters.stops[s][0], | ||
colorspace.forward(parameters.stops[s][1]) | ||
]; | ||
} | ||
outputFunction = colorspace.reverse; | ||
} else { | ||
throw new Error(`Unknown function type "${type}"`); | ||
throw new Error(`Unknown color space: ${parameters.colorSpace}`); | ||
} | ||
} else { | ||
outputFunction = identityFunction; | ||
} | ||
let outputFunction; | ||
// If we're interpolating colors in a color system other than RGBA, | ||
// first translate all stop values to that color system, then interpolate | ||
// arrays as usual. The `outputFunction` option lets us then translate | ||
// the result of that interpolation back into RGBA. | ||
if (parameters.colorSpace && parameters.colorSpace !== 'rgb') { | ||
if (colorSpaces[parameters.colorSpace]) { | ||
const colorspace = colorSpaces[parameters.colorSpace]; | ||
// Avoid mutating the parameters value | ||
parameters = JSON.parse(JSON.stringify(parameters)); | ||
for (let s = 0; s < parameters.stops.length; s++) { | ||
parameters.stops[s] = [ | ||
parameters.stops[s][0], | ||
colorspace.forward(parameters.stops[s][1]) | ||
]; | ||
} | ||
outputFunction = colorspace.reverse; | ||
} else { | ||
throw new Error(`Unknown color space: ${parameters.colorSpace}`); | ||
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: zoom, | ||
type: parameters.type, | ||
property: parameters.property, | ||
default: parameters.default, | ||
stops: [] | ||
}; | ||
zoomStops.push(zoom); | ||
} | ||
} else { | ||
outputFunction = identityFunction; | ||
featureFunctions[zoom].stops.push([stop[0].value, stop[1]]); | ||
} | ||
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: 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 featureFunctionStops = []; | ||
for (const z of zoomStops) { | ||
featureFunctionStops.push([featureFunctions[z].zoom, createFunction(featureFunctions[z], propertySpec)]); | ||
} | ||
fun = function(zoom, feature) { | ||
return { | ||
isFeatureConstant: false, | ||
interpolation: {name: 'linear'}, | ||
zoomStops: featureFunctionStops.map(s => s[0]), | ||
evaluate({zoom}, properties) { | ||
return outputFunction(evaluateExponentialFunction({ | ||
stops: featureFunctionStops, | ||
base: parameters.base | ||
}, propertySpec, zoom)(zoom, feature)); | ||
}; | ||
fun.isFeatureConstant = false; | ||
fun.isZoomConstant = false; | ||
} else if (zoomDependent) { | ||
fun = function(zoom) { | ||
return outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)); | ||
}; | ||
fun.isFeatureConstant = true; | ||
fun.isZoomConstant = false; | ||
}, propertySpec, zoom).evaluate(zoom, properties)); | ||
} | ||
}; | ||
} else if (zoomDependent) { | ||
let evaluate; | ||
if (name === 'heatmap-color') { | ||
evaluate = ({heatmapDensity}) => outputFunction(innerFun(parameters, propertySpec, heatmapDensity, hashedStops, categoricalKeyType)); | ||
} else { | ||
fun = function(zoom, feature) { | ||
const value = feature[parameters.property]; | ||
evaluate = ({zoom}) => outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)); | ||
} | ||
return { | ||
isFeatureConstant: true, | ||
isZoomConstant: false, | ||
interpolation: type === 'exponential' ? | ||
{name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1} : | ||
{name: 'step'}, | ||
zoomStops: parameters.stops.map(s => s[0]), | ||
evaluate | ||
}; | ||
} else { | ||
return { | ||
isFeatureConstant: false, | ||
isZoomConstant: true, | ||
evaluate(_, feature) { | ||
const value = feature && feature.properties ? feature.properties[parameters.property] : undefined; | ||
if (value === undefined) { | ||
@@ -146,9 +152,5 @@ return coalesce(parameters.default, propertySpec.default); | ||
return outputFunction(innerFun(parameters, propertySpec, value, hashedStops, categoricalKeyType)); | ||
}; | ||
fun.isFeatureConstant = false; | ||
fun.isZoomConstant = true; | ||
} | ||
} | ||
}; | ||
} | ||
return fun; | ||
} | ||
@@ -200,11 +202,13 @@ | ||
if (typeof outputLower === 'function') { | ||
return function(...args) { | ||
const evaluatedLower = outputLower.apply(undefined, args); | ||
const evaluatedUpper = outputUpper.apply(undefined, args); | ||
// Special case for fill-outline-color, which has no spec default. | ||
if (evaluatedLower === undefined || evaluatedUpper === undefined) { | ||
return undefined; | ||
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(evaluatedLower, evaluatedUpper, t); | ||
}; | ||
@@ -253,6 +257,2 @@ } | ||
function isFunctionDefinition(value) { | ||
return typeof value === 'object' && (value.stops || value.type === 'identity'); | ||
} | ||
/** | ||
@@ -295,3 +295,3 @@ * Returns a ratio that can be used to interpolate between exponential function | ||
* @private | ||
*/ | ||
*/ | ||
function interpolationFactor(input, base, lowerValue, upperValue) { | ||
@@ -310,5 +310,5 @@ const difference = upperValue - lowerValue; | ||
module.exports = createFunction; | ||
module.exports.isFunctionDefinition = isFunctionDefinition; | ||
module.exports.interpolationFactor = interpolationFactor; | ||
module.exports.findStopLessThanOrEqualTo = findStopLessThanOrEqualTo; | ||
module.exports = { | ||
createFunction, | ||
isFunction | ||
}; |
@@ -6,3 +6,3 @@ | ||
const type = typeof obj; | ||
if (type === 'number' || type === 'string' || obj === undefined || obj === null) | ||
if (type === 'number' || type === 'boolean' || type === 'string' || obj === undefined || obj === null) | ||
return JSON.stringify(obj); | ||
@@ -9,0 +9,0 @@ |
@@ -13,3 +13,3 @@ | ||
exports.ParsingError = require('./error/parsing_error'); | ||
exports.function = require('./function'); | ||
exports.expression = require('./expression'); | ||
exports.featureFilter = require('./feature_filter'); | ||
@@ -16,0 +16,0 @@ |
{ | ||
"name": "@mapbox/mapbox-gl-style-spec", | ||
"description": "a specification for mapbox gl styles", | ||
"version": "9.0.1", | ||
"version": "10.0.0", | ||
"author": "Mapbox", | ||
@@ -6,0 +6,0 @@ "keywords": [ |
@@ -0,5 +1,6 @@ | ||
// @flow | ||
const parseColorString = require('csscolorparser').parseCSSColor; | ||
module.exports = function parseColor(input) { | ||
module.exports = function parseColor(input: string | [number, number, number, number]): ?[number, number, number, number] { | ||
if (typeof input === 'string') { | ||
@@ -6,0 +7,0 @@ const rgba = parseColorString(input); |
// Turn jsonlint-lines-primitives objects into primitive objects | ||
module.exports = function unbundle(value) { | ||
function unbundle(value) { | ||
if (value instanceof Number || value instanceof String || value instanceof Boolean) { | ||
@@ -9,2 +9,12 @@ return value.valueOf(); | ||
} | ||
}; | ||
} | ||
function deepUnbundle(value) { | ||
if (Array.isArray(value)) { | ||
return value.map(deepUnbundle); | ||
} | ||
return unbundle(value); | ||
} | ||
module.exports = unbundle; | ||
module.exports.deep = deepUnbundle; |
@@ -13,7 +13,7 @@ | ||
if (valueSpec.values.indexOf(unbundle(value)) === -1) { | ||
errors.push(new ValidationError(key, value, 'expected one of [%s], %s found', valueSpec.values.join(', '), value)); | ||
errors.push(new ValidationError(key, value, 'expected one of [%s], %s found', valueSpec.values.join(', '), JSON.stringify(value))); | ||
} | ||
} else { // >=v8 | ||
if (Object.keys(valueSpec.values).indexOf(unbundle(value)) === -1) { | ||
errors.push(new ValidationError(key, value, 'expected one of [%s], %s found', Object.keys(valueSpec.values).join(', '), value)); | ||
errors.push(new ValidationError(key, value, 'expected one of [%s], %s found', Object.keys(valueSpec.values).join(', '), JSON.stringify(value))); | ||
} | ||
@@ -20,0 +20,0 @@ } |
const ValidationError = require('../error/validation_error'); | ||
const validateExpression = require('./validate_expression'); | ||
const validateEnum = require('./validate_enum'); | ||
const getType = require('../util/get_type'); | ||
const unbundle = require('../util/unbundle_jsonlint'); | ||
const extend = require('../util/extend'); | ||
const {isExpressionFilter} = require('../feature_filter'); | ||
@@ -19,2 +22,9 @@ module.exports = function validateFilter(options) { | ||
if (isExpressionFilter(unbundle.deep(value))) { | ||
return validateExpression(extend({}, options, { | ||
expressionContext: 'filter', | ||
valueSpec: { value: 'boolean' } | ||
})); | ||
} | ||
if (value.length < 1) { | ||
@@ -21,0 +31,0 @@ return [new ValidationError(key, value, 'filter array must have at least 1 element')]; |
@@ -133,3 +133,3 @@ | ||
styleSpec: options.styleSpec | ||
})); | ||
}, value)); | ||
} | ||
@@ -146,14 +146,16 @@ | ||
function validateStopDomainValue(options) { | ||
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, options.value, '%s stop domain type must match previous stop domain type %s', type, stopKeyType)]; | ||
return [new ValidationError(options.key, reportValue, '%s stop domain type must match previous stop domain type %s', type, stopKeyType)]; | ||
} | ||
if (type !== 'number' && type !== 'string' && type !== 'boolean') { | ||
return [new ValidationError(options.key, options.value, 'stop domain value must be a number, string, or boolean')]; | ||
return [new ValidationError(options.key, reportValue, 'stop domain value must be a number, string, or boolean')]; | ||
} | ||
@@ -166,11 +168,11 @@ | ||
} | ||
return [new ValidationError(options.key, options.value, message, type)]; | ||
return [new ValidationError(options.key, reportValue, message, type)]; | ||
} | ||
if (functionType === 'categorical' && type === 'number' && (!isFinite(value) || Math.floor(value) !== value)) { | ||
return [new ValidationError(options.key, options.value, 'integer expected, found %s', value)]; | ||
return [new ValidationError(options.key, reportValue, 'integer expected, found %s', value)]; | ||
} | ||
if (functionType !== 'categorical' && type === 'number' && previousStopDomainValue !== undefined && value < previousStopDomainValue) { | ||
return [new ValidationError(options.key, options.value, 'stop domain values must appear in ascending order')]; | ||
return [new ValidationError(options.key, reportValue, 'stop domain values must appear in ascending order')]; | ||
} else { | ||
@@ -181,3 +183,3 @@ previousStopDomainValue = value; | ||
if (functionType === 'categorical' && value in stopDomainValues) { | ||
return [new ValidationError(options.key, options.value, 'stop domain values must be unique')]; | ||
return [new ValidationError(options.key, reportValue, 'stop domain values must be unique')]; | ||
} else { | ||
@@ -184,0 +186,0 @@ stopDomainValues[value] = true; |
@@ -46,3 +46,3 @@ | ||
objectKey: objectKey | ||
})); | ||
}, object)); | ||
} | ||
@@ -49,0 +49,0 @@ |
@@ -55,4 +55,5 @@ | ||
style: style, | ||
styleSpec: styleSpec | ||
styleSpec: styleSpec, | ||
expressionContext: 'property' | ||
})); | ||
}; |
@@ -5,2 +5,5 @@ | ||
const extend = require('../util/extend'); | ||
const unbundle = require('../util/unbundle_jsonlint'); | ||
const {isExpression} = require('../expression'); | ||
const {isFunction} = require('../function'); | ||
@@ -19,2 +22,3 @@ // Main recursive validation function. Tracks: | ||
const validateFunction = require('./validate_function'); | ||
const validateExpression = require('./validate_expression'); | ||
const validateObject = require('./validate_object'); | ||
@@ -56,5 +60,8 @@ const VALIDATORS = { | ||
if (valueSpec.function && getType(value) === 'object') { | ||
if (valueSpec.function && isFunction(unbundle(value))) { | ||
return validateFunction(options); | ||
} else if (valueSpec.function && isExpression(unbundle.deep(value))) { | ||
return validateExpression(options); | ||
} else if (valueSpec.type && VALIDATORS[valueSpec.type]) { | ||
@@ -61,0 +68,0 @@ return VALIDATORS[valueSpec.type](options); |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
Uses eval
Supply chain riskPackage uses dynamic code execution (e.g., eval()), which is a dangerous practice. This can prevent the code from running in certain environments and increases the risk that the code may contain exploits or malicious behavior.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
355722
81
10850
2