Skip to content

Commit

Permalink
Convert old-style filters to expressions
Browse files Browse the repository at this point in the history
  • Loading branch information
Anand Thakker authored and jfirebaugh committed Jan 3, 2018
1 parent fe1ca76 commit 949a2ec
Show file tree
Hide file tree
Showing 3 changed files with 532 additions and 507 deletions.
112 changes: 58 additions & 54 deletions src/style-spec/feature_filter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,6 @@ function isExpressionFilter(filter) {
}
}

const types = ['Unknown', 'Point', 'LineString', 'Polygon'];

const filterSpec = {
'type': 'boolean',
'default': false,
Expand All @@ -69,7 +67,7 @@ function createFilter(filter: any): FeatureFilter {
}

if (!isExpressionFilter(filter)) {
return (new Function('g', 'f', `var p = (f && f.properties || {}); return ${compile(filter)}`): any);
filter = convertFilter(filter);
}

const compiled = createExpression(filter, filterSpec);
Expand All @@ -80,70 +78,76 @@ function createFilter(filter: any): FeatureFilter {
}
}

function compile(filter) {
if (!filter) return 'true';
// Comparison function to sort numbers and strings
function compare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}

function convertFilter(filter: ?Array<any>): mixed {
if (!filter) return true;
const op = filter[0];
if (filter.length <= 1) return op === 'any' ? 'false' : 'true';
const str =
op === '==' ? compileComparisonOp(filter[1], filter[2], '===', false) :
op === '!=' ? compileComparisonOp(filter[1], filter[2], '!==', false) :
if (filter.length <= 1) return (op !== 'any');
const converted =
op === '==' ? convertComparisonOp(filter[1], filter[2], '==') :
op === '!=' ? convertNegation(convertComparisonOp(filter[1], filter[2], '==')) :
op === '<' ||
op === '>' ||
op === '<=' ||
op === '>=' ? compileComparisonOp(filter[1], filter[2], op, true) :
op === 'any' ? compileLogicalOp(filter.slice(1), '||') :
op === 'all' ? compileLogicalOp(filter.slice(1), '&&') :
op === 'none' ? compileNegation(compileLogicalOp(filter.slice(1), '||')) :
op === 'in' ? compileInOp(filter[1], filter.slice(2)) :
op === '!in' ? compileNegation(compileInOp(filter[1], filter.slice(2))) :
op === 'has' ? compileHasOp(filter[1]) :
op === '!has' ? compileNegation(compileHasOp(filter[1])) :
'true';
return `(${str})`;
op === '>=' ? convertComparisonOp(filter[1], filter[2], op) :
op === 'any' ? convertDisjunctionOp(filter.slice(1)) :
op === 'all' ? ['all'].concat(filter.slice(1).map(convertFilter)) :
op === 'none' ? ['all'].concat(filter.slice(1).map(convertFilter).map(convertNegation)) :
op === 'in' ? convertInOp(filter[1], filter.slice(2)) :
op === '!in' ? convertNegation(convertInOp(filter[1], filter.slice(2))) :
op === 'has' ? convertHasOp(filter[1]) :
op === '!has' ? convertNegation(convertHasOp(filter[1])) :
true;
return converted;
}

function compilePropertyReference(property) {
const ref =
property === '$type' ? 'f.type' :
property === '$id' ? 'f.id' : `p[${JSON.stringify(property)}]`;
return ref;
}

function compileComparisonOp(property, value, op, checkType) {
const left = compilePropertyReference(property);
const right = property === '$type' ? types.indexOf(value) : JSON.stringify(value);
return (checkType ? `typeof ${left}=== typeof ${right}&&` : '') + left + op + right;
function convertComparisonOp(property: string, value: any, op: string) {
switch (property) {
case '$type':
return [`filter-type-${op}`, value];
case '$id':
return [`filter-id-${op}`, value];
default:
return [`filter-${op}`, property, value];
}
}

function compileLogicalOp(expressions, op) {
return expressions.map(compile).join(op);
function convertDisjunctionOp(filters: Array<Array<any>>) {
return ['any'].concat(filters.map(convertFilter));
}

function compileInOp(property, values) {
if (property === '$type') values = values.map((value) => {
return types.indexOf(value);
});
const left = JSON.stringify(values.sort(compare));
const right = compilePropertyReference(property);

if (values.length <= 200 || values.some(v => typeof v !== typeof values[0])) return `${left}.indexOf(${right}) !== -1`;

return `${'function(v, a, i, j) {' +
'while (i <= j) { var m = (i + j) >> 1;' +
' if (a[m] === v) return true; if (a[m] > v) j = m - 1; else i = m + 1;' +
'}' +
'return false; }('}${right}, ${left},0,${values.length - 1})`;
function convertInOp(property: string, values: Array<any>) {
if (values.length === 0) { return false; }
switch (property) {
case '$type':
return [`filter-type-in`, ['literal', values]];
case '$id':
return [`filter-id-in`, ['literal', values]];
default:
if (values.length > 200 && !values.some(v => typeof v !== typeof values[0])) {
return ['filter-in-large', property, ['literal', values.sort(compare)]];
} else {
return ['filter-in-small', property, ['literal', values]];
}
}
}

function compileHasOp(property) {
return property === '$id' ? '"id" in f' : `${JSON.stringify(property)} in p`;
function convertHasOp(property: string) {
switch (property) {
case '$type':
return true;
case '$id':
return [`filter-has-id`];
default:
return [`filter-has`, property];
}
}

function compileNegation(expression) {
return `!(${expression})`;
function convertNegation(filter: mixed) {
return ['!', filter];
}

// Comparison function to sort numbers and strings
function compare(a, b) {
return a < b ? -1 : a > b ? 1 : 0;
}
10 changes: 10 additions & 0 deletions src/style-spec/util/eval_support.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
// @flow

module.exports = (function () {
try {
new Function('');
return true;
} catch (e) {
return false;
}
})();
Loading

0 comments on commit 949a2ec

Please sign in to comment.