From 9ba624b0cf22109be3a7c76aa13ca7cedabdc319 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 29 Jan 2016 15:51:10 +0200 Subject: [PATCH] fast eval-less filter implementation Closes #6, closes #8, closes #10. --- index.js | 135 ++++++++++++++++++++++++++++--------------------------- 1 file changed, 70 insertions(+), 65 deletions(-) diff --git a/index.js b/index.js index b54dc4b..8fa3467 100644 --- a/index.js +++ b/index.js @@ -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 @@ -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; } +}