diff --git a/bench/benchmarks/expressions.js b/bench/benchmarks/expressions.js index ab627f0dc6b..18c7d07670e 100644 --- a/bench/benchmarks/expressions.js +++ b/bench/benchmarks/expressions.js @@ -7,7 +7,7 @@ const createFunction = require('../../src/style-spec/function'); const convertFunction = require('../../src/style-spec/function/convert'); const { isExpression, - createExpressionWithErrorHandling, + createExpression, getExpectedType, getDefaultValue } = require('../../src/style-spec/expression'); @@ -40,7 +40,14 @@ class ExpressionBenchmark extends Benchmark { const expressionData = function(rawValue, propertySpec: StylePropertySpecification) { const rawExpression = convertFunction(rawValue, propertySpec); const compiledFunction = createFunction(rawValue, propertySpec); - const compiledExpression = createExpressionWithErrorHandling(rawExpression, getExpectedType(propertySpec), getDefaultValue(propertySpec)); + const compiledExpression = createExpression(rawExpression, { + context: 'property', + expectedType: getExpectedType(propertySpec), + defaultValue: getDefaultValue(propertySpec) + }); + if (compiledExpression.result !== 'success') { + throw new Error(compiledExpression.errors.map(err => `${err.key}: ${err.message}`).join(', ')); + } return { propertySpec, rawValue, @@ -93,7 +100,11 @@ class FunctionConvert extends ExpressionBenchmark { class ExpressionCreate extends ExpressionBenchmark { bench() { for (const {rawExpression, propertySpec} of this.data) { - createExpressionWithErrorHandling(rawExpression, getExpectedType(propertySpec), getDefaultValue(propertySpec)); + createExpression(rawExpression, { + context: 'property', + expectedType: getExpectedType(propertySpec), + defaultValue: getDefaultValue(propertySpec) + }); } } } diff --git a/src/style-spec/expression/definitions/index.js b/src/style-spec/expression/definitions/index.js index bb3d2a906f7..2cae731313b 100644 --- a/src/style-spec/expression/definitions/index.js +++ b/src/style-spec/expression/definitions/index.js @@ -185,6 +185,11 @@ CompoundExpression.register(expressions, { [], (ctx) => ctx.globals.zoom ], + 'heatmap-density': [ + NumberType, + [], + (ctx) => ctx.globals.heatmapDensity || 0 + ], '+': [ NumberType, varargs(NumberType), diff --git a/src/style-spec/expression/evaluation_context.js b/src/style-spec/expression/evaluation_context.js index de3d9bb87d9..bf0351d45f6 100644 --- a/src/style-spec/expression/evaluation_context.js +++ b/src/style-spec/expression/evaluation_context.js @@ -5,13 +5,13 @@ const Scope = require('./scope'); const parseColor = require('../util/parse_color'); const {Color} = require('./values'); -import type { Feature } from './index'; +import type { Feature, GlobalProperties } from './index'; import type { Expression } from './expression'; const geometryTypes = ['Unknown', 'Point', 'LineString', 'Polygon']; class EvaluationContext { - globals: {zoom: number}; + globals: GlobalProperties; feature: ?Feature; scope: Scope; diff --git a/src/style-spec/expression/index.js b/src/style-spec/expression/index.js index e4dc3f82a05..a420603f049 100644 --- a/src/style-spec/expression/index.js +++ b/src/style-spec/expression/index.js @@ -11,7 +11,6 @@ const Let = require('./definitions/let'); const definitions = require('./definitions'); const isConstant = require('./is_constant'); const {unwrap} = require('./values'); -const extend = require('../util/extend'); import type {Type} from './types'; import type {Value} from './values'; @@ -24,32 +23,69 @@ export type Feature = { +properties: {[string]: any} }; +export type GlobalProperties = { + zoom: number, + heatmapDensity?: number +}; + +export type StyleExpressionContext = 'property' | 'filter'; + +export type StyleExpressionOptions = { + context: StyleExpressionContext, + expectedType: Type | null, + defaultValue?: Value | null +} + export type StyleExpressionErrors = { result: 'error', errors: Array }; -export type StyleExpression = { +type ZoomConstantExpression = { result: 'success', + context: StyleExpressionContext, isZoomConstant: true, isFeatureConstant: boolean, - evaluate: (globals: {zoom: number}, feature?: Feature) => any, + evaluate: (globals: GlobalProperties, feature?: Feature) => any, // parsed: Expression -} | { +}; + +export type StyleDeclarationExpression = ZoomConstantExpression | { result: 'success', + context: 'property', isZoomConstant: false, isFeatureConstant: boolean, - evaluate: (globals: {zoom: number}, feature?: Feature) => any, + evaluate: (globals: GlobalProperties, feature?: Feature) => any, // parsed: Expression, interpolation: InterpolationType, zoomStops: Array }; +export type StyleFilterExpression = ZoomConstantExpression | { + result: 'success', + context: 'filter', + isZoomConstant: false, + isFeatureConstant: boolean, + evaluate: (GlobalProperties, feature?: Feature) => any, + // parsed: Expression, +}; + +export type StyleExpression = StyleDeclarationExpression | StyleFilterExpression; + type StylePropertyValue = null | string | number | Array | Array; type FunctionParameters = DataDrivenPropertyValueSpecification -function createExpression(expression: mixed, expectedType: Type | null): StyleExpressionErrors | StyleExpression { - const parser = new ParsingContext(definitions, [], expectedType); +/** + * 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: mixed, options: StyleExpressionOptions): StyleExpressionErrors | StyleExpression { + const parser = new ParsingContext(definitions, [], options.expectedType); const parsed = parser.parse(expression); if (!parsed) { assert(parser.errors.length > 0); @@ -60,19 +96,55 @@ function createExpression(expression: mixed, expectedType: Type | null): StyleEx } const evaluator = new EvaluationContext(); - function evaluate(globals, feature) { - evaluator.globals = globals; - evaluator.feature = feature; - return parsed.evaluate(evaluator); + + let evaluate; + if (options.defaultValue === undefined) { + evaluate = function (globals, feature) { + evaluator.globals = globals; + evaluator.feature = feature; + return parsed.evaluate(evaluator); + }; + } else { + const warningHistory: {[key: string]: boolean} = {}; + const defaultValue = options.defaultValue; + evaluate = function (globals, feature) { + evaluator.globals = globals; + evaluator.feature = feature; + try { + const val = parsed.evaluate(evaluator); + if (val === null || val === undefined) { + return unwrap(defaultValue); + } + return unwrap(val); + } catch (e) { + if (!warningHistory[e.message]) { + warningHistory[e.message] = true; + if (typeof console !== 'undefined') { + console.warn(e.message); + } + } + return unwrap(defaultValue); + } + }; } const isFeatureConstant = isConstant.isFeatureConstant(parsed); - const isZoomConstant = isConstant.isZoomConstant(parsed); + const isZoomConstant = isConstant.isGlobalPropertyConstant(parsed, ['zoom']); if (isZoomConstant) { return { result: 'success', - isZoomConstant, + context: options.context, + isZoomConstant: true, + isFeatureConstant, + evaluate, + parsed + }; + } else if (options.context === 'filter') { + return { + result: 'success', + context: 'filter', + isZoomConstant: false, isFeatureConstant, evaluate, parsed @@ -94,6 +166,7 @@ function createExpression(expression: mixed, expectedType: Type | null): StyleEx return { result: 'success', + context: 'property', isZoomConstant: false, isFeatureConstant, evaluate, @@ -107,40 +180,7 @@ function createExpression(expression: mixed, expectedType: Type | null): StyleEx }; } -function createExpressionWithErrorHandling(expression: mixed, expectedType: Type | null, defaultValue: Value | null): StyleExpression { - expression = createExpression(expression, expectedType); - - if (expression.result !== 'success') { - // this should have been caught in validation - throw new Error(expression.errors.map(err => `${err.key}: ${err.message}`).join(', ')); - } - - const evaluate = expression.evaluate; - const warningHistory: {[key: string]: boolean} = {}; - - return extend({}, expression, { - evaluate(globals, feature) { - try { - const val = evaluate(globals, feature); - if (val === null || val === undefined) { - return unwrap(defaultValue); - } - return unwrap(val); - } catch (e) { - if (!warningHistory[e.message]) { - warningHistory[e.message] = true; - if (typeof console !== 'undefined') { - console.warn(e.message); - } - } - return unwrap(defaultValue); - } - } - }); -} - -module.exports = createExpression; -module.exports.createExpressionWithErrorHandling = createExpressionWithErrorHandling; +module.exports.createExpression = createExpression; module.exports.isExpression = isExpression; module.exports.getExpectedType = getExpectedType; module.exports.getDefaultValue = getDefaultValue; diff --git a/src/style-spec/expression/is_constant.js b/src/style-spec/expression/is_constant.js index c7dca51472c..08c2484afea 100644 --- a/src/style-spec/expression/is_constant.js +++ b/src/style-spec/expression/is_constant.js @@ -26,16 +26,16 @@ function isFeatureConstant(e: Expression) { return result; } -function isZoomConstant(e: Expression) { - if (e instanceof CompoundExpression && e.name === 'zoom') { return false; } +function isGlobalPropertyConstant(e: Expression, properties: Array) { + if (e instanceof CompoundExpression && properties.indexOf(e.name) >= 0) { return false; } let result = true; e.eachChild((arg) => { - if (result && !isZoomConstant(arg)) { result = false; } + if (result && !isGlobalPropertyConstant(arg, properties)) { result = false; } }); return result; } module.exports = { isFeatureConstant, - isZoomConstant, + isGlobalPropertyConstant, }; diff --git a/src/style-spec/expression/parsing_context.js b/src/style-spec/expression/parsing_context.js index de1b5d18342..e6aaf5c0bc2 100644 --- a/src/style-spec/expression/parsing_context.js +++ b/src/style-spec/expression/parsing_context.js @@ -163,7 +163,7 @@ module.exports = ParsingContext; function isConstant(expression: Expression) { // requires within function body to workaround circular dependency const {CompoundExpression} = require('./compound_expression'); - const {isZoomConstant, isFeatureConstant} = require('./is_constant'); + const {isGlobalPropertyConstant, isFeatureConstant} = require('./is_constant'); const Var = require('./definitions/var'); if (expression instanceof Var) { @@ -180,5 +180,6 @@ function isConstant(expression: Expression) { return false; } - return isZoomConstant(expression) && isFeatureConstant(expression); + return isFeatureConstant(expression) && + isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density']); } diff --git a/src/style-spec/function/convert.js b/src/style-spec/function/convert.js index 549922b6645..f89aa4b898b 100644 --- a/src/style-spec/function/convert.js +++ b/src/style-spec/function/convert.js @@ -3,7 +3,7 @@ const extend = require('../util/extend'); module.exports = convertFunction; -function convertFunction(parameters, propertySpec) { +function convertFunction(parameters, propertySpec, name) { let expression; parameters = extend({}, parameters); @@ -30,7 +30,10 @@ function convertFunction(parameters, propertySpec) { throw new Error('Unimplemented'); } - if (zoomAndFeatureDependent) { + if (name === 'heatmap-color') { + assert(zoomDependent); + expression = convertZoomFunction(parameters, propertySpec, stops, ['heatmap-density']); + } else if (zoomAndFeatureDependent) { expression = convertZoomAndPropertyFunction(parameters, propertySpec, stops, defaultExpression); } else if (zoomDependent) { expression = convertZoomFunction(parameters, propertySpec, stops); @@ -177,16 +180,16 @@ function convertPropertyFunction(parameters, propertySpec, stops, defaultExpress return expression; } -function convertZoomFunction(parameters, propertySpec, stops) { +function convertZoomFunction(parameters, propertySpec, stops, input = ['zoom']) { const type = getFunctionType(parameters, propertySpec); let expression; let isStep = false; if (type === 'interval') { - expression = ['curve', ['step'], ['zoom']]; + expression = ['curve', ['step'], input]; isStep = true; } else if (type === 'exponential') { const base = parameters.base !== undefined ? parameters.base : 1; - expression = ['curve', ['exponential', base], ['zoom']]; + expression = ['curve', ['exponential', base], input]; } else { throw new Error(`Unknown zoom function type "${type}"`); } diff --git a/src/style-spec/function/index.js b/src/style-spec/function/index.js index 9a27c6d26b0..34c0345986f 100644 --- a/src/style-spec/function/index.js +++ b/src/style-spec/function/index.js @@ -9,7 +9,7 @@ function identityFunction(x) { 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; @@ -118,6 +118,12 @@ function createFunction(parameters, propertySpec) { } }; } else if (zoomDependent) { + let evaluate; + if (name === 'heatmap-color') { + evaluate = ({heatmapDensity}) => outputFunction(innerFun(parameters, propertySpec, heatmapDensity, hashedStops, categoricalKeyType)); + } else { + evaluate = ({zoom}) => outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)); + } return { isFeatureConstant: true, isZoomConstant: false, @@ -125,9 +131,7 @@ function createFunction(parameters, propertySpec) { {name: 'exponential', base: parameters.base !== undefined ? parameters.base : 1} : {name: 'step'}, zoomStops: parameters.stops.map(s => s[0]), - evaluate({zoom}) { - return outputFunction(innerFun(parameters, propertySpec, zoom, hashedStops, categoricalKeyType)); - } + evaluate }; } else { return { diff --git a/src/style-spec/validate/validate_expression.js b/src/style-spec/validate/validate_expression.js index a0c14e135e6..4e5ea17b5c6 100644 --- a/src/style-spec/validate/validate_expression.js +++ b/src/style-spec/validate/validate_expression.js @@ -1,14 +1,17 @@ const ValidationError = require('../error/validation_error'); -const createExpression = require('../expression'); +const {createExpression} = require('../expression'); const {getExpectedType, getDefaultValue} = require('../expression'); const unbundle = require('../util/unbundle_jsonlint'); module.exports = function validateExpression(options) { const expression = createExpression( deepUnbundle(options.value.expression), - getExpectedType(options.valueSpec), - getDefaultValue(options.valueSpec)); + { + context: options.expressionContext, + expectedType: getExpectedType(options.valueSpec), + defaultValue: getDefaultValue(options.valueSpec) + }); if (expression.result === 'success') { return []; diff --git a/src/style-spec/validate/validate_function.js b/src/style-spec/validate/validate_function.js index 8d8ea9bbdef..374d0991971 100644 --- a/src/style-spec/validate/validate_function.js +++ b/src/style-spec/validate/validate_function.js @@ -12,7 +12,7 @@ const extend = require('../util/extend'); module.exports = function validateFunction(options) { if (options.value.expression) { return validateExpression(extend({}, options, { - disallowNestedZoom: true + context: 'declaration' })); } diff --git a/src/style/light.js b/src/style/light.js index 1e9ab7d5bb1..bbfb9d58de3 100644 --- a/src/style/light.js +++ b/src/style/light.js @@ -8,6 +8,7 @@ const StyleDeclaration = require('./style_declaration'); const StyleTransition = require('./style_transition'); import type AnimationLoop from './animation_loop'; +import type {GlobalProperties} from '../style-spec/expression'; const TRANSITION_SUFFIX = '-transition'; const properties = ['anchor', 'color', 'position', 'intensity']; @@ -42,7 +43,7 @@ class Light extends Evented { }, lightOpts); for (const prop of properties) { - this._declarations[prop] = new StyleDeclaration(specifications[prop], lightOpts[prop]); + this._declarations[prop] = new StyleDeclaration(specifications[prop], lightOpts[prop], prop); } return this; @@ -70,7 +71,7 @@ class Light extends Evented { } } - getLightValue(property: string, globalProperties: {zoom: number}) { + getLightValue(property: string, globalProperties: GlobalProperties) { if (property === 'position') { const calculated: any = this._transitions[property].calculate(globalProperties), cartesian = util.sphericalToCartesian(calculated); @@ -95,7 +96,7 @@ class Light extends Evented { } else if (value === null || value === undefined) { delete this._declarations[key]; } else { - this._declarations[key] = new StyleDeclaration(specifications[key], value); + this._declarations[key] = new StyleDeclaration(specifications[key], value, key); } } } @@ -111,7 +112,7 @@ class Light extends Evented { const spec = specifications[property]; if (declaration === null || declaration === undefined) { - declaration = new StyleDeclaration(spec, spec.default); + declaration = new StyleDeclaration(spec, spec.default, property); } if (oldTransition && oldTransition.declaration.json === declaration.json) return; diff --git a/src/style/style_declaration.js b/src/style/style_declaration.js index ae06b1d1060..2fec473e073 100644 --- a/src/style/style_declaration.js +++ b/src/style/style_declaration.js @@ -1,18 +1,19 @@ // @flow const parseColor = require('../style-spec/util/parse_color'); -const {createExpressionWithErrorHandling, getExpectedType, getDefaultValue} = require('../style-spec/expression'); +const {createExpression, getExpectedType, getDefaultValue} = require('../style-spec/expression'); const createFunction = require('../style-spec/function'); const util = require('../util/util'); const Curve = require('../style-spec/expression/definitions/curve'); -import type {StyleExpression, Feature} from '../style-spec/expression'; +import type {StyleDeclarationExpression, Feature, GlobalProperties} from '../style-spec/expression'; -function normalizeToExpression(parameters, propertySpec): StyleExpression { +function normalizeToExpression(parameters, propertySpec, name): StyleDeclarationExpression { if (typeof parameters === 'string' && propertySpec.type === 'color') { const color = parseColor(parameters); return { result: 'success', + context: 'property', isFeatureConstant: true, isZoomConstant: true, evaluate() { return color; } @@ -22,6 +23,7 @@ function normalizeToExpression(parameters, propertySpec): StyleExpression { if (parameters === null || typeof parameters !== 'object' || Array.isArray(parameters)) { return { result: 'success', + context: 'property', isFeatureConstant: true, isZoomConstant: true, evaluate() { return parameters; } @@ -29,12 +31,25 @@ function normalizeToExpression(parameters, propertySpec): StyleExpression { } if (parameters.expression) { - return createExpressionWithErrorHandling( - parameters.expression, - getExpectedType(propertySpec), - getDefaultValue(propertySpec)); + const expression = createExpression( + parameters.expression, { + context: 'property', + expectedType: getExpectedType(propertySpec), + defaultValue: getDefaultValue(propertySpec) + }); + + if (expression.result !== 'success') { + // this should have been caught in validation + throw new Error(expression.errors.map(err => `${err.key}: ${err.message}`).join(', ')); + } + + if (expression.context === 'property') { + return expression; + } else { + throw new Error(`Incorrect expression context ${expression.context}`); + } } else { - return createFunction(parameters, propertySpec); + return createFunction(parameters, propertySpec, name); } } @@ -46,19 +61,19 @@ class StyleDeclaration { value: any; json: mixed; minimum: number; - expression: StyleExpression; + expression: StyleDeclarationExpression; - constructor(reference: any, value: any) { + constructor(reference: any, value: any, name: string) { this.value = util.clone(value); // immutable representation of value. used for comparison this.json = JSON.stringify(this.value); this.minimum = reference.minimum; - this.expression = normalizeToExpression(this.value, reference); + this.expression = normalizeToExpression(this.value, reference, name); } - calculate(globals: {zoom: number}, feature?: Feature) { + calculate(globals: GlobalProperties, feature?: Feature) { const value = this.expression.evaluate(globals, feature); if (this.minimum !== undefined && value < this.minimum) { return this.minimum; diff --git a/src/style/style_layer.js b/src/style/style_layer.js index 5b85ac23bcb..140e9f35bac 100644 --- a/src/style/style_layer.js +++ b/src/style/style_layer.js @@ -10,17 +10,9 @@ const Evented = require('../util/evented'); import type {Bucket, BucketParameters} from '../data/bucket'; import type Point from '@mapbox/point-geometry'; -import type {Feature} from '../style-spec/expression'; +import type {Feature, GlobalProperties} from '../style-spec/expression'; import type RenderTexture from '../render/render_texture'; -export type GlobalProperties = { - zoom: number -}; - -export type FeatureProperties = { - [string]: string | number | boolean -}; - const TRANSITION_SUFFIX = '-transition'; class StyleLayer extends Evented { @@ -117,7 +109,7 @@ class StyleLayer extends Evented { } else { const key = `layers.${this.id}.layout.${name}`; if (this._validate(validateStyle.layoutProperty, key, name, value, options)) return; - this._layoutDeclarations[name] = new StyleDeclaration(this._layoutSpecifications[name], value); + this._layoutDeclarations[name] = new StyleDeclaration(this._layoutSpecifications[name], value, name); } this._updateLayoutValue(name); } @@ -161,7 +153,7 @@ class StyleLayer extends Evented { delete this._paintDeclarations[klass || ''][name]; } else { if (this._validate(validateStyle.paintProperty, validateStyleKey, name, value, options)) return; - this._paintDeclarations[klass || ''][name] = new StyleDeclaration(this._paintSpecifications[name], value); + this._paintDeclarations[klass || ''][name] = new StyleDeclaration(this._paintSpecifications[name], value, name); } } } @@ -284,7 +276,7 @@ class StyleLayer extends Evented { const spec = this._paintSpecifications[name]; if (declaration === null || declaration === undefined) { - declaration = new StyleDeclaration(spec, spec.default); + declaration = new StyleDeclaration(spec, spec.default, name); } if (oldTransition && oldTransition.declaration.json === declaration.json) return; diff --git a/src/style/style_layer/fill_extrusion_style_layer.js b/src/style/style_layer/fill_extrusion_style_layer.js index bef6bca6a6a..3ef06b65267 100644 --- a/src/style/style_layer/fill_extrusion_style_layer.js +++ b/src/style/style_layer/fill_extrusion_style_layer.js @@ -5,8 +5,7 @@ const FillExtrusionBucket = require('../../data/bucket/fill_extrusion_bucket'); const {multiPolygonIntersectsMultiPolygon} = require('../../util/intersection_tests'); const {translateDistance, translate} = require('../query_utils'); -import type {Feature} from '../../style-spec/expression'; -import type {GlobalProperties} from '../style_layer'; +import type {Feature, GlobalProperties} from '../../style-spec/expression'; import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; diff --git a/src/style/style_layer/fill_style_layer.js b/src/style/style_layer/fill_style_layer.js index acfd0f91efa..85180a950b7 100644 --- a/src/style/style_layer/fill_style_layer.js +++ b/src/style/style_layer/fill_style_layer.js @@ -5,8 +5,7 @@ const FillBucket = require('../../data/bucket/fill_bucket'); const {multiPolygonIntersectsMultiPolygon} = require('../../util/intersection_tests'); const {translateDistance, translate} = require('../query_utils'); -import type {Feature} from '../../style-spec/expression'; -import type {GlobalProperties} from '../style_layer'; +import type {Feature, GlobalProperties} from '../../style-spec/expression'; import type {BucketParameters} from '../../data/bucket'; import type Point from '@mapbox/point-geometry'; diff --git a/src/style/style_layer/symbol_style_layer.js b/src/style/style_layer/symbol_style_layer.js index 5dca5b04e93..f6378155340 100644 --- a/src/style/style_layer/symbol_style_layer.js +++ b/src/style/style_layer/symbol_style_layer.js @@ -4,8 +4,7 @@ const StyleLayer = require('../style_layer'); const SymbolBucket = require('../../data/bucket/symbol_bucket'); const assert = require('assert'); -import type {Feature} from '../../style-spec/expression'; -import type {GlobalProperties} from '../style_layer'; +import type {Feature, GlobalProperties} from '../../style-spec/expression'; import type {BucketParameters} from '../../data/bucket'; class SymbolStyleLayer extends StyleLayer { diff --git a/src/style/style_transition.js b/src/style/style_transition.js index 266bac614c3..bc19c36e662 100644 --- a/src/style/style_transition.js +++ b/src/style/style_transition.js @@ -4,7 +4,7 @@ const util = require('../util/util'); const interpolate = require('../style-spec/util/interpolate'); import type StyleDeclaration from './style_declaration'; -import type {Feature} from '../style-spec/expression'; +import type {Feature, GlobalProperties} from '../style-spec/expression'; const fakeZoomHistory = { lastIntegerZoom: 0, lastIntegerZoomTime: 0, lastZoom: 0 }; @@ -57,7 +57,7 @@ class StyleTransition { /* * Return the value of the transitioning property. */ - calculate(globals: {zoom: number}, feature?: Feature, time?: number) { + calculate(globals: GlobalProperties, feature?: Feature, time?: number) { const value = this._calculateTargetValue(globals, feature); if (this.instant()) @@ -73,7 +73,7 @@ class StyleTransition { return this.interp(oldValue, value, t); } - _calculateTargetValue(globals: {zoom: number}, feature?: Feature) { + _calculateTargetValue(globals: GlobalProperties, feature?: Feature) { if (!this.zoomTransitioned) return this.declaration.calculate(globals, feature); diff --git a/test/expression.test.js b/test/expression.test.js index 97a26b076ca..8e75ed7a179 100644 --- a/test/expression.test.js +++ b/test/expression.test.js @@ -2,7 +2,7 @@ require('flow-remove-types/register'); const expressionSuite = require('./integration').expression; -const createExpression = require('../src/style-spec/expression'); +const { createExpression } = require('../src/style-spec/expression'); const { toString } = require('../src/style-spec/expression/types'); const { unwrap } = require('../src/style-spec/expression/values'); @@ -14,9 +14,10 @@ if (process.argv[1] === __filename && process.argv.length > 2) { expressionSuite.run('js', {tests: tests}, (fixture) => { const expression = createExpression( - fixture.expression, - fixture.expectExpressionType || null, - null); + fixture.expression, { + context: 'declaration', + expectedType: fixture.expectExpressionType || null + }); if (expression.result === 'error') { return { diff --git a/test/integration/expression-tests/heatmap-density/basic/test.json b/test/integration/expression-tests/heatmap-density/basic/test.json new file mode 100644 index 00000000000..6465e6de16e --- /dev/null +++ b/test/integration/expression-tests/heatmap-density/basic/test.json @@ -0,0 +1,14 @@ +{ + "expectExpressionType": {"kind": "Color"}, + "expression": ["curve", ["linear"], ["heatmap-density"], 0, "#000000", 1, "#ff0000"], + "inputs": [[{"heatmapDensity": 0.5}, {}]], + "expected": { + "compiled": { + "result": "success", + "isFeatureConstant": true, + "isZoomConstant": true, + "type": "Color" + }, + "outputs": [[0.5, 0, 0, 1]] + } +}