Skip to content

Commit

Permalink
faster and simpler filter implementation
Browse files Browse the repository at this point in the history
Closes #6, closes #10, ref #8.
  • Loading branch information
mourner committed Jan 29, 2016
1 parent d2e04ec commit b4bc18d
Show file tree
Hide file tree
Showing 3 changed files with 47 additions and 67 deletions.
103 changes: 37 additions & 66 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,8 @@
'use strict';

var VectorTileFeatureTypes = ['Unknown', 'Point', 'LineString', 'Polygon'];
module.exports = createFilter;

function infix(operator) {
return function(_, key, value) {
if (key === '$type') {
return 't' + operator + VectorTileFeatureTypes.indexOf(value);
} else {
return 'p[' + JSON.stringify(key) + ']' + operator + JSON.stringify(value);
}
};
}

function strictInfix(operator) {
var nonstrictInfix = infix(operator);
return function(_, key, value) {
if (key === '$type') {
return nonstrictInfix(_, key, value);
} else {
return 'typeof(p[' + JSON.stringify(key) + ']) === typeof(' + JSON.stringify(value) + ') && ' +
nonstrictInfix(_, key, value);
}
};
}

var operators = {
'==': infix('==='),
'!=': infix('!=='),
'>': strictInfix('>'),
'<': strictInfix('<'),
'<=': strictInfix('<='),
'>=': strictInfix('>='),
'in': function(_, key) {
return '(function(){' + Array.prototype.slice.call(arguments, 2).map(function(value) {
return 'if (' + operators['=='](_, key, value) + ') return true;';
}).join('') + 'return false;})()';
},
'!in': function() {
return '!(' + operators.in.apply(this, arguments) + ')';
},
'any': function() {
return Array.prototype.slice.call(arguments, 1).map(function(filter) {
return '(' + compile(filter) + ')';
}).join('||') || 'false';
},
'all': function() {
return Array.prototype.slice.call(arguments, 1).map(function(filter) {
return '(' + compile(filter) + ')';
}).join('&&') || 'true';
},
'none': function() {
return '!(' + operators.any.apply(this, arguments) + ')';
}
};

function compile(filter) {
return operators[filter[0]].apply(filter, filter);
}

function truth() {
return true;
}
var types = ['Unknown', 'Point', 'LineString', 'Polygon'];

/**
* Given a filter expressed as nested arrays, return a new function
Expand All @@ -70,9 +12,38 @@ function truth() {
* @param {Array} filter mapbox gl filter
* @returns {Function} filter-evaluating function
*/
module.exports = function (filter) {
if (!filter) return truth;
var filterStr = 'var p = f.properties || f.tags || {}, t = f.type; return ' + compile(filter) + ';';
// jshint evil: true
return new Function('f', filterStr);
};
function createFilter(filter) {
return new Function('f', 'return ' + compile(filter));
}

function compile(filter) {
if (!filter || filter.length <= 1) return 'true';
var op = filter[0];
var str =
op === '==' ? compare(filter[1], filter[2], '===', false) :
op === '!=' ? compare(filter[1], filter[2], '!==', false) :
op === '<' ||
op === '>' ||
op === '<=' ||
op === '>=' ? compare(filter[1], filter[2], op, true) :
op === 'any' ? filter.slice(1).map(compile).join('||') :
op === 'all' ? filter.slice(1).map(compile).join('&&') :
op === 'none' ? '!(' + filter.slice(1).map(compile).join('||') + ')' :
op === 'in' ? compileIn(filter[1], filter.slice(2)) :
op === '!in' ? '!(' + compileIn(filter[1], filter.slice(2)) + ')' :
'true';
return '(' + str + ')';
}

function valueExpr(key) {
return key === '$type' ? 'f.type' : '(f.properties || {})[' + JSON.stringify(key) + ']';
}
function compare(key, val, op, checkType) {
var left = valueExpr(key);
var right = key === '$type' ? types.indexOf(val) : JSON.stringify(val);
return (checkType ? 'typeof ' + left + '=== typeof ' + right + '&&' : '') + left + op + right;
}
function compileIn(key, values) {
if (key === '$type') values = values.map(types.indexOf.bind(types));
return JSON.stringify(values) + '.indexOf(' + valueExpr(key) + ') !== -1';
}
9 changes: 9 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,20 @@
"description": "Creates filtering function for vector tile features",
"dependencies": {},
"devDependencies": {
"eslint": "^1.10.3",
"eslint-config-mourner": "^1.0.1",
"tape": "^3.2.1"
},
"scripts": {
"pretest": "eslint index.js test.js",
"test": "tape test.js"
},
"eslintConfig": {
"extends": "mourner",
"rules": {
"space-before-function-paren": [2, "never"]
}
},
"repository": {
"type": "git",
"url": "git@github.com:mapbox/feature-filter.git"
Expand Down
2 changes: 1 addition & 1 deletion test.js
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ test('!in, $type', function(t) {

test('any', function(t) {
var f1 = filter(['any']);
t.equal(f1({properties: {foo: 1}}), false);
t.equal(f1({properties: {foo: 1}}), true);

var f2 = filter(['any', ['==', 'foo', 1]]);
t.equal(f2({properties: {foo: 1}}), true);
Expand Down

0 comments on commit b4bc18d

Please sign in to comment.