Skip to content

Commit

Permalink
Add heatmap-density expression (prepares for #5253)
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker committed Oct 5, 2017
1 parent 74c4898 commit d70a7db
Show file tree
Hide file tree
Showing 15 changed files with 73 additions and 51 deletions.
5 changes: 5 additions & 0 deletions src/style-spec/expression/definitions/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,11 @@ CompoundExpression.register(expressions, {
[],
(ctx) => ctx.globals.zoom
],
'heatmap-density': [
NumberType,
[],
(ctx) => ctx.globals.heatmapDensity || 0
],
'+': [
NumberType,
varargs(NumberType),
Expand Down
4 changes: 2 additions & 2 deletions src/style-spec/expression/evaluation_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
11 changes: 8 additions & 3 deletions src/style-spec/expression/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,11 @@ export type Feature = {
+properties: {[string]: any}
};

export type GlobalProperties = {
zoom: number,
heatmapDensity?: number
};

export type StyleExpressionErrors = {
result: 'error',
errors: Array<ParsingError>
Expand All @@ -33,13 +38,13 @@ export type StyleExpression = {
result: 'success',
isZoomConstant: true,
isFeatureConstant: boolean,
evaluate: (globals: {zoom: number}, feature?: Feature) => any,
evaluate: (globals: GlobalProperties, feature?: Feature) => any,
// parsed: Expression
} | {
result: 'success',
isZoomConstant: false,
isFeatureConstant: boolean,
evaluate: (globals: {zoom: number}, feature?: Feature) => any,
evaluate: (globals: GlobalProperties, feature?: Feature) => any,
// parsed: Expression,
interpolation: InterpolationType,
zoomStops: Array<number>
Expand Down Expand Up @@ -67,7 +72,7 @@ function createExpression(expression: mixed, expectedType: Type | null): StyleEx
}

const isFeatureConstant = isConstant.isFeatureConstant(parsed);
const isZoomConstant = isConstant.isZoomConstant(parsed);
const isZoomConstant = isConstant.isGlobalPropertyConstant(parsed, ['zoom']);

if (isZoomConstant) {
return {
Expand Down
8 changes: 4 additions & 4 deletions src/style-spec/expression/is_constant.js
Original file line number Diff line number Diff line change
Expand Up @@ -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<string>) {
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,
};
5 changes: 3 additions & 2 deletions src/style-spec/expression/parsing_context.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand All @@ -180,5 +180,6 @@ function isConstant(expression: Expression) {
return false;
}

return isZoomConstant(expression) && isFeatureConstant(expression);
return isFeatureConstant(expression) &&
isGlobalPropertyConstant(expression, ['zoom', 'heatmap-density']);
}
13 changes: 8 additions & 5 deletions src/style-spec/function/convert.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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}"`);
}
Expand Down
12 changes: 8 additions & 4 deletions src/style-spec/function/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -118,16 +118,20 @@ 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,
interpolation: type === 'exponential' ?
{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 {
Expand Down
9 changes: 5 additions & 4 deletions src/style/light.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'];
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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);
}
}
}
Expand All @@ -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;
Expand Down
12 changes: 6 additions & 6 deletions src/style/style_declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ 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 {StyleExpression, Feature, GlobalProperties} from '../style-spec/expression';

function normalizeToExpression(parameters, propertySpec): StyleExpression {
function normalizeToExpression(parameters, propertySpec, name): StyleExpression {
if (typeof parameters === 'string' && propertySpec.type === 'color') {
const color = parseColor(parameters);
return {
Expand All @@ -34,7 +34,7 @@ function normalizeToExpression(parameters, propertySpec): StyleExpression {
getExpectedType(propertySpec),
getDefaultValue(propertySpec));
} else {
return createFunction(parameters, propertySpec);
return createFunction(parameters, propertySpec, name);
}
}

Expand All @@ -48,17 +48,17 @@ class StyleDeclaration {
minimum: number;
expression: StyleExpression;

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;
Expand Down
16 changes: 4 additions & 12 deletions src/style/style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
}
}
}
Expand Down Expand Up @@ -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;
Expand Down
3 changes: 1 addition & 2 deletions src/style/style_layer/fill_extrusion_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
3 changes: 1 addition & 2 deletions src/style/style_layer/fill_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
3 changes: 1 addition & 2 deletions src/style/style_layer/symbol_style_layer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
6 changes: 3 additions & 3 deletions src/style/style_transition.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 };

Expand Down Expand Up @@ -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())
Expand All @@ -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);

Expand Down
14 changes: 14 additions & 0 deletions test/integration/expression-tests/heatmap-density/basic/test.json
Original file line number Diff line number Diff line change
@@ -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]]
}
}

0 comments on commit d70a7db

Please sign in to comment.