Skip to content

Commit

Permalink
Properly handle zoom-based expressions
Browse files Browse the repository at this point in the history
 - Add validation for 'zoom' expression usage
 - Add zoom function metadata to compiled output
  • Loading branch information
Anand Thakker committed Jun 22, 2017
1 parent 8275bb2 commit 4f22bda
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 25 deletions.
29 changes: 29 additions & 0 deletions src/style-spec/function/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
'use strict';

const assert = require('assert');
const expressions = require('./expressions');
const compileExpression = require('./compile');
const convert = require('./convert');
Expand Down Expand Up @@ -29,6 +30,34 @@ function createFunction(parameters, propertySpec) {
};
f.isFeatureConstant = compiled.isFeatureConstant;
f.isZoomConstant = compiled.isZoomConstant;
if (!f.isZoomConstant) {
// capture metadata from the curve definition that's needed for
// our prepopulate-and-interpolate approach to paint properties
// that are zoom-and-property dependent.
let curve = compiled.expression;
if (curve.name !== 'curve') { curve = curve.arguments[0]; }
const curveArgs = [].concat(curve.arguments);
const interpolation = serialize(curveArgs.shift());

f.zoomStops = [];
for (let i = 1; i < curveArgs.length; i += 2) {
f.zoomStops.push(curveArgs[i].value);
}

if (!f.isFeatureConstant) {
const interpExpression = ['curve', interpolation, ['zoom']];
for (let i = 0; i < f.zoomStops.length; i++) {
interpExpression.push(f.zoomStops[i], i);
}
const interpFunction = compileExpression(
expressions,
['coalesce', interpExpression, 0],
NumberType
);
assert(!interpFunction.errors);
f.interpolationT = interpFunction.function;
}
}
return f;
} else {
console.log(expr)
Expand Down
30 changes: 24 additions & 6 deletions src/style-spec/function/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,8 @@ module.exports = parseExpression;
function parseExpression(
definitions: {[string]: Definition},
expr: mixed,
path: Array<number> = []
path: Array<number> = [],
ancestorNames: Array<string> = []
) /*: TypedExpression | ParseError */ {
const key = path.join('.');
if (expr === null || typeof expr === 'undefined') return {
Expand Down Expand Up @@ -92,14 +93,31 @@ function parseExpression(
};
}

// special case validation for `zoom`
if (op === 'zoom') {
const ancestors = ancestorNames.join(':');
// zoom expressions may only appear like:
// ['curve', interp, ['zoom'], ...]
// or ['coalesce', ['curve', interp, ['zoom'], ...], ... ]
if (
!/^(1.)?2/.test(key) ||
!/(coalesce:)?curve/.test(ancestors)
) {
return {
key,
error: 'The "zoom" expression may only be used as the input to a top-level "curve" expression.'
};
}
}

// special case parsing for `match`
if (op === 'match') {
if (expr.length < 3) return {
key,
error: `Expected at least 2 arguments, but found only ${expr.length - 1}.`
};

const inputExpression = parseExpression(definitions, expr[1], path.concat(1));
const inputExpression = parseExpression(definitions, expr[1], path.concat(1), ancestorNames.concat(op));
if (inputExpression.error) return inputExpression;

// parse input/output pairs.
Expand All @@ -116,7 +134,7 @@ function parseExpression(

const parsedInputGroup = [];
for (let j = 0; j < inputGroup.length; j++) {
const parsedValue = parseExpression(definitions, inputGroup[j], path.concat(i, j));
const parsedValue = parseExpression(definitions, inputGroup[j], path.concat(i, j), ancestorNames.concat(op));
if (parsedValue.error) return parsedValue;
if (!parsedValue.literal) return {
key: `${key}.${i}.${j}`,
Expand All @@ -126,12 +144,12 @@ function parseExpression(
}
matchInputs.push(parsedInputGroup);

const output = parseExpression(definitions, expr[i + 1], path.concat(i));
const output = parseExpression(definitions, expr[i + 1], path.concat(i), ancestorNames.concat(op));
if (output.error) return output;
outputExpressions.push(output);
}

const otherwise = parseExpression(definitions, expr[expr.length - 1], path.concat(expr.length - 1));
const otherwise = parseExpression(definitions, expr[expr.length - 1], path.concat(expr.length - 1), ancestorNames.concat(op));
if (otherwise.error) return otherwise;
outputExpressions.push(otherwise);

Expand All @@ -147,7 +165,7 @@ function parseExpression(

const args = [];
for (const arg of expr.slice(1)) {
const parsedArg = parseExpression(definitions, arg, path.concat(1 + args.length));
const parsedArg = parseExpression(definitions, arg, path.concat(1 + args.length), ancestorNames.concat(op));
if (parsedArg.error) return parsedArg;
args.push(parsedArg);
}
Expand Down
1 change: 1 addition & 0 deletions src/style-spec/function/type_check.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ const util = require('../../util/util');
const { NullType, lambda, array, anyArray, vector, variant, nargs } = require('./types');

module.exports = typeCheckExpression;
module.exports.serialize = serializeExpression;

// typecheck the given expression and return a new TypedExpression
// tree with all generics resolved
Expand Down
20 changes: 2 additions & 18 deletions src/style/style_declaration.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,7 @@ class StyleDeclaration {
this.isZoomConstant = this.function.isZoomConstant;

if (!this.isFeatureConstant && !this.isZoomConstant) {
this.stopZoomLevels = [];
const interpolationAmountStops = [];
for (const stop of this.value.stops) {
const zoom = stop[0].zoom;
if (this.stopZoomLevels.indexOf(zoom) < 0) {
this.stopZoomLevels.push(zoom);
interpolationAmountStops.push([zoom, interpolationAmountStops.length]);
}
}

this._functionInterpolationT = createFunction({
type: 'exponential',
stops: interpolationAmountStops,
base: value.base
}, {
type: 'number'
});
this.stopZoomLevels = [].concat(this.function.zoomStops);
} else if (!this.isZoomConstant) {
this.stopZoomLevels = [];
for (const stop of this.value.stops) {
Expand Down Expand Up @@ -68,7 +52,7 @@ class StyleDeclaration {
*/
calculateInterpolationT(globalProperties) {
if (this.isFeatureConstant || this.isZoomConstant) return 0;
return this._functionInterpolationT(globalProperties && globalProperties.zoom, {});
return this.function.interpolationT(globalProperties);
}
}

Expand Down
12 changes: 11 additions & 1 deletion test/integration/expression-tests/zoom/basic/test.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
{
"expression": [
"zoom"
"curve",
[
"linear"
],
[
"zoom"
],
0,
0,
30,
30
],
"evaluate": [
[
Expand Down
34 changes: 34 additions & 0 deletions test/integration/expression-tests/zoom/too-deep/test.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"expression": [
"curve",
[
"step"
],
1,
0,
[
"curve",
[
"linear"
],
[
"zoom"
],
0,
0,
22,
22
]
],
"expected": {
"compileResult": {
"result": "error",
"errors": [
{
"key": "4.2",
"error": "The \"zoom\" expression may only be used as the input to a top-level \"curve\" expression."
}
]
}
}
}

0 comments on commit 4f22bda

Please sign in to comment.