Skip to content

Commit

Permalink
fast eval-less filter implementation
Browse files Browse the repository at this point in the history
Closes #6, closes #8, closes #10.
  • Loading branch information
mourner committed Jan 29, 2016
1 parent d2e04ec commit 9ba624b
Showing 1 changed file with 70 additions and 65 deletions.
135 changes: 70 additions & 65 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,66 +1,9 @@
'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'];
var typeLookup = {'Point': 1, 'LineString': 2, 'Polygon': 3}

/**
* Given a filter expressed as nested arrays, return a new function
Expand All @@ -70,9 +13,71 @@ function truth() {
* @param {Array} filter mapbox gl filter
* @returns {Function} filter-evaluating function
*/
module.exports = function (filter) {
function createFilter(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);
};

var op = filter[0];
var key = filter[1];

var val =
op === '==' ||
op === '!=' ||
op === '>' ||
op === '<' ||
op === '<=' ||
op === '>=' ? filter[2] :
op === 'in' ||
op === '!in' ? filter.slice(2) : filter.slice(1);

var opFn =
op === 'in' ? (key === '$type' ? inType : _in) :
op === '!in' ? (key === '$type' ? notInType : notIn) :
op === '==' ? (key === '$type' ? equalsType : equals) :
op === '!=' ? (key === '$type' ? notEqualsType : notEquals) :
op === '<' ? lesser :
op === '>' ? greater :
op === '<=' ? lesserEqual :
op === '>=' ? greaterEqual :
val.map(createFilter);

if (op === 'any') {
return function (f) {
for (var i = 0; i < opFn.length; i++) {
if (opFn[i](f)) return true;
}
return false;
}
} else if (op === 'all') {
return function (f) {
for (var i = 0; i < opFn.length; i++) {
if (!opFn[i](f)) return false;
}
return true;
}
} else if (op === 'none') {
return function (f) {
for (var i = 0; i < opFn.length; i++) {
if (opFn[i](f)) return false;
}
return true;
}
}

return function (f) {
return opFn(f.properties || f.tags || {}, f.type);
};

function _in(p, t) { return val.indexOf(p[key]) !== -1; }
function inType(p, t) { return val.indexOf(types[t]) !== -1; }
function notIn(p, t) { return val.indexOf(p[key]) === -1; }
function notInType(p, t) { return val.indexOf(types[t]) === -1; }
function equals(p, t) { return p[key] === val; }
function equalsType(p, t) { return t === typeLookup[val]; }
function notEquals(p, t) { return p[key] !== val; }
function notEqualsType(p, t) { return t !== typeLookup[val]; }
function lesser(p, t) { return typeof p[key] === typeof val && p[key] < val; }
function greater(p, t) { return typeof p[key] === typeof val && p[key] > val; }
function lesserEqual(p, t) { return typeof p[key] === typeof val && p[key] <= val; }
function greaterEqual(p, t) { return typeof p[key] === typeof val && p[key] >= val; }
function truth() { return true; }
}

0 comments on commit 9ba624b

Please sign in to comment.