From 9ba624b0cf22109be3a7c76aa13ca7cedabdc319 Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 29 Jan 2016 15:51:10 +0200 Subject: [PATCH 1/3] 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; } +} From 6dca54f582eaad04b9af1ae779e13df14952f94b Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 29 Jan 2016 16:09:01 +0200 Subject: [PATCH 2/3] faster filter creation --- index.js | 82 +++++++++++++++++++++++++++++++------------------------- 1 file changed, 45 insertions(+), 37 deletions(-) diff --git a/index.js b/index.js index 8fa3467..1d28a13 100644 --- a/index.js +++ b/index.js @@ -30,7 +30,7 @@ function createFilter(filter) { op === '!in' ? filter.slice(2) : filter.slice(1); var opFn = - op === 'in' ? (key === '$type' ? inType : _in) : + op === 'in' ? (key === '$type' ? inType : inNormal) : op === '!in' ? (key === '$type' ? notInType : notIn) : op === '==' ? (key === '$type' ? equalsType : equals) : op === '!=' ? (key === '$type' ? notEqualsType : notEquals) : @@ -40,44 +40,52 @@ function createFilter(filter) { 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; + if (op === 'any') return createAny(opFn); + if (op === 'all') return createAll(opFn); + if (op === 'none') return createNone(opFn); + return createOp(opFn, key, val); +} + +function createOp(opFn, key, val) { + return function (f) { + return opFn(f.properties || f.tags || {}, f.type, key, val); + } +} +function createAny(opFn) { + return function (f) { + for (var i = 0; i < opFn.length; i++) { + if (opFn[i](f)) 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 false; + } +} +function createAll(opFn) { + return function (f) { + for (var i = 0; i < opFn.length; i++) { + if (!opFn[i](f)) return false; } + return true; } - +} +function createNone(opFn) { 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; } + for (var i = 0; i < opFn.length; i++) { + if (opFn[i](f)) return false; + } + return true; + } } + +function inNormal(p, t, key, val) { return val.indexOf(p[key]) !== -1; } +function inType(p, t, key, val) { return val.indexOf(types[t]) !== -1; } +function notIn(p, t, key, val) { return val.indexOf(p[key]) === -1; } +function notInType(p, t, key, val) { return val.indexOf(types[t]) === -1; } +function equals(p, t, key, val) { return p[key] === val; } +function equalsType(p, t, key, val) { return t === typeLookup[val]; } +function notEquals(p, t, key, val) { return p[key] !== val; } +function notEqualsType(p, t, key, val) { return t !== typeLookup[val]; } +function lesser(p, t, key, val) { return typeof p[key] === typeof val && p[key] < val; } +function greater(p, t, key, val) { return typeof p[key] === typeof val && p[key] > val; } +function lesserEqual(p, t, key, val) { return typeof p[key] === typeof val && p[key] <= val; } +function greaterEqual(p, t, key, val) { return typeof p[key] === typeof val && p[key] >= val; } +function truth() { return true; } From ce44bc7bdfd73b6ee20140a0d785015bd280391e Mon Sep 17 00:00:00 2001 From: Vladimir Agafonkin Date: Fri, 29 Jan 2016 16:14:08 +0200 Subject: [PATCH 3/3] adopt ESLint --- index.js | 18 +++++++++--------- package.json | 9 +++++++++ 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/index.js b/index.js index 1d28a13..16b24ae 100644 --- a/index.js +++ b/index.js @@ -3,7 +3,7 @@ module.exports = createFilter; var types = ['Unknown', 'Point', 'LineString', 'Polygon']; -var typeLookup = {'Point': 1, 'LineString': 2, 'Polygon': 3} +var typeLookup = {'Point': 1, 'LineString': 2, 'Polygon': 3}; /** * Given a filter expressed as nested arrays, return a new function @@ -47,33 +47,33 @@ function createFilter(filter) { } function createOp(opFn, key, val) { - return function (f) { + return function(f) { return opFn(f.properties || f.tags || {}, f.type, key, val); - } + }; } function createAny(opFn) { - return function (f) { + return function(f) { for (var i = 0; i < opFn.length; i++) { if (opFn[i](f)) return true; } return false; - } + }; } function createAll(opFn) { - return function (f) { + return function(f) { for (var i = 0; i < opFn.length; i++) { if (!opFn[i](f)) return false; } return true; - } + }; } function createNone(opFn) { - return function (f) { + return function(f) { for (var i = 0; i < opFn.length; i++) { if (opFn[i](f)) return false; } return true; - } + }; } function inNormal(p, t, key, val) { return val.indexOf(p[key]) !== -1; } diff --git a/package.json b/package.json index 9549e70..ae39c5e 100644 --- a/package.json +++ b/package.json @@ -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"