diff --git a/src/ol/filter/logicalfilter.js b/src/ol/filter/logicalfilter.js
index 89f438fc33..c32b675b1f 100644
--- a/src/ol/filter/logicalfilter.js
+++ b/src/ol/filter/logicalfilter.js
@@ -1,6 +1,10 @@
goog.provide('ol.filter.Logical');
goog.provide('ol.filter.LogicalOperator');
+goog.provide('ol.filter.and');
+goog.provide('ol.filter.not');
+goog.provide('ol.filter.or');
+goog.require('goog.asserts');
goog.require('ol.filter.Filter');
@@ -9,7 +13,7 @@ goog.require('ol.filter.Filter');
* @constructor
* @extends {ol.filter.Filter}
* @param {Array.
} filters Filters to and-combine.
- * @param {!ol.filter.LogicalOperator} operator Operator.
+ * @param {ol.filter.LogicalOperator} operator Operator.
*/
ol.filter.Logical = function(filters, operator) {
goog.base(this);
@@ -19,9 +23,10 @@ ol.filter.Logical = function(filters, operator) {
* @private
*/
this.filters_ = filters;
+ goog.asserts.assert(filters.length > 0, 'Must supply at least one filter');
/**
- * @type {!ol.filter.LogicalOperator}
+ * @type {ol.filter.LogicalOperator}
*/
this.operator = operator;
@@ -35,14 +40,29 @@ goog.inherits(ol.filter.Logical, ol.filter.Filter);
ol.filter.Logical.prototype.applies = function(feature) {
var filters = this.filters_,
i = 0, ii = filters.length,
- operator = this.operator,
- start = operator(true, false),
- result = start;
- while (result === start && i < ii) {
- result = operator(result, filters[i].applies(feature));
- ++i;
+ result;
+ switch (this.operator) {
+ case ol.filter.LogicalOperator.AND:
+ result = true;
+ while (result && i < ii) {
+ result = result && filters[i].applies(feature);
+ ++i;
+ }
+ break;
+ case ol.filter.LogicalOperator.OR:
+ result = false;
+ while (!result && i < ii) {
+ result = result || filters[i].applies(feature);
+ ++i;
+ }
+ break;
+ case ol.filter.LogicalOperator.NOT:
+ result = !filters[i].applies(feature);
+ break;
+ default:
+ goog.asserts.assert(false, 'Unsupported operation: ' + this.operator);
}
- return result;
+ return !!result;
};
@@ -55,9 +75,44 @@ ol.filter.Logical.prototype.getFilters = function() {
/**
- * @enum {!Function}
+ * @enum {string}
*/
ol.filter.LogicalOperator = {
- AND: /** @return {boolean} result. */ function(a, b) { return a && b; },
- OR: /** @return {boolean} result. */ function(a, b) { return a || b; }
+ AND: '&&',
+ OR: '||',
+ NOT: '!'
+};
+
+
+/**
+ * Create a filter that evaluates to true if all of the provided filters
+ * evaluate to true.
+ * @param {...ol.filter.Filter} var_filters Filters.
+ * @return {ol.filter.Logical} A logical AND filter.
+ */
+ol.filter.and = function(var_filters) {
+ var filters = Array.prototype.slice.call(arguments);
+ return new ol.filter.Logical(filters, ol.filter.LogicalOperator.AND);
+};
+
+
+/**
+ * Create a new filter that is the logical compliment of another.
+ * @param {ol.filter.Filter} filter The filter to negate.
+ * @return {ol.filter.Logical} A logical NOT filter.
+ */
+ol.filter.not = function(filter) {
+ return new ol.filter.Logical([filter], ol.filter.LogicalOperator.NOT);
+};
+
+
+/**
+ * Create a filter that evaluates to true if any of the provided filters
+ * evaluate to true.
+ * @param {...ol.filter.Filter} var_filters Filters.
+ * @return {ol.filter.Logical} A logical OR filter.
+ */
+ol.filter.or = function(var_filters) {
+ var filters = Array.prototype.slice.call(arguments);
+ return new ol.filter.Logical(filters, ol.filter.LogicalOperator.OR);
};
diff --git a/test/spec/ol/filter/logicalfilter.test.js b/test/spec/ol/filter/logicalfilter.test.js
new file mode 100644
index 0000000000..970c0d0c10
--- /dev/null
+++ b/test/spec/ol/filter/logicalfilter.test.js
@@ -0,0 +1,144 @@
+goog.provide('ol.test.filter.Logical');
+
+
+describe('ol.filter.Logical', function() {
+
+ var OR = ol.filter.LogicalOperator.OR;
+ var AND = ol.filter.LogicalOperator.AND;
+ var NOT = ol.filter.LogicalOperator.NOT;
+ var include = new ol.filter.Filter(function() {return true});
+ var exclude = new ol.filter.Filter(function() {return false});
+
+ var apple = new ol.Feature({});
+ var orange = new ol.Feature({});
+ var duck = new ol.Feature({});
+
+ var isApple = new ol.filter.Filter(function(feature) {
+ return feature === apple;
+ });
+ var isOrange = new ol.filter.Filter(function(feature) {
+ return feature === orange;
+ });
+ var isDuck = new ol.filter.Filter(function(feature) {
+ return feature === duck;
+ });
+
+ describe('constructor', function() {
+ it('creates a new filter', function() {
+ var filter = new ol.filter.Logical([include, exclude], OR);
+ expect(filter).to.be.a(ol.filter.Logical);
+ });
+ });
+
+ describe('#operator', function() {
+ it('can be OR', function() {
+ var filter = new ol.filter.Logical([include, exclude], OR);
+ expect(filter.operator).to.be(OR);
+ });
+
+ it('can be AND', function() {
+ var filter = new ol.filter.Logical([include, exclude], AND);
+ expect(filter.operator).to.be(AND);
+ });
+
+ it('can be NOT', function() {
+ var filter = new ol.filter.Logical([include], NOT);
+ expect(filter.operator).to.be(NOT);
+ });
+ });
+
+ describe('#applies', function() {
+
+ it('works for OR', function() {
+ var isFruit = new ol.filter.Logical([isApple, isOrange], OR);
+
+ expect(isApple.applies(apple)).to.be(true);
+ expect(isOrange.applies(apple)).to.be(false);
+ expect(isFruit.applies(apple)).to.be(true);
+
+ expect(isApple.applies(duck)).to.be(false);
+ expect(isOrange.applies(duck)).to.be(false);
+ expect(isFruit.applies(duck)).to.be(false);
+ });
+
+ it('works for AND', function() {
+ expect(include.applies(apple)).to.be(true);
+ expect(isApple.applies(apple)).to.be(true);
+ expect(isDuck.applies(apple)).to.be(false);
+
+ var pass = new ol.filter.Logical([include, isApple], AND);
+ expect(pass.applies(apple)).to.be(true);
+
+ var fail = new ol.filter.Logical([isApple, isDuck], AND);
+ expect(fail.applies(apple)).to.be(false);
+ });
+
+ it('works for NOT', function() {
+ expect(isApple.applies(apple)).to.be(true);
+ expect(isDuck.applies(apple)).to.be(false);
+ expect(isDuck.applies(duck)).to.be(true);
+ expect(isDuck.applies(apple)).to.be(false);
+
+ var notApple = new ol.filter.Logical([isApple], NOT);
+ expect(notApple.applies(apple)).to.be(false);
+ expect(notApple.applies(duck)).to.be(true);
+
+ var notDuck = new ol.filter.Logical([isDuck], NOT);
+ expect(notDuck.applies(apple)).to.be(true);
+ expect(notDuck.applies(duck)).to.be(false);
+ });
+ });
+
+});
+
+describe('ol.filter.and', function() {
+ it('creates the a logical AND filter', function() {
+ var a = new ol.filter.Filter();
+ var b = new ol.filter.Filter();
+ var c = new ol.filter.Filter();
+ var and = ol.filter.and(a, b, c);
+ expect(and).to.be.a(ol.filter.Logical);
+ expect(and.operator).to.be(ol.filter.LogicalOperator.AND);
+ var filters = and.getFilters();
+ expect(filters[0]).to.be(a);
+ expect(filters[1]).to.be(b);
+ expect(filters[2]).to.be(c);
+ });
+});
+
+describe('ol.filter.not', function() {
+ it('creates the logical compliment of another filter', function() {
+ var include = new ol.filter.Filter(function() {return true;});
+ var notInclude = ol.filter.not(include);
+ expect(notInclude).to.be.a(ol.filter.Logical);
+ expect(notInclude.applies()).to.be(false);
+
+ var exclude = new ol.filter.Filter(function() {return false;});
+ var notExclude = ol.filter.not(exclude);
+ expect(notExclude).to.be.a(ol.filter.Logical);
+ expect(notExclude.applies()).to.be(true);
+ });
+});
+
+describe('ol.filter.or', function() {
+ it('creates the a logical OR filter', function() {
+ var a = new ol.filter.Filter();
+ var b = new ol.filter.Filter();
+ var c = new ol.filter.Filter();
+ var and = ol.filter.or(a, b, c);
+ expect(and).to.be.a(ol.filter.Logical);
+ expect(and.operator).to.be(ol.filter.LogicalOperator.OR);
+ var filters = and.getFilters();
+ expect(filters[0]).to.be(a);
+ expect(filters[1]).to.be(b);
+ expect(filters[2]).to.be(c);
+ });
+});
+
+goog.require('ol.Feature');
+goog.require('ol.filter.Filter');
+goog.require('ol.filter.Logical');
+goog.require('ol.filter.LogicalOperator');
+goog.require('ol.filter.and');
+goog.require('ol.filter.not');
+goog.require('ol.filter.or');