Add optional context property to OpenLayers.Rule, so rules can now be evaluated against diffent contexts than feature.attributes. This changeset also renames Rule.Logical.children to Rule.Logical.rules, to make it more consistent with OL.Style. r=crschmidt (closes #1331)

git-svn-id: http://svn.openlayers.org/trunk/openlayers@6116 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
This commit is contained in:
ahocevar
2008-02-08 16:56:48 +00:00
parent bb26a2601d
commit 3581276835
8 changed files with 1622 additions and 1568 deletions

View File

@@ -343,7 +343,7 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
var filters = filter[0].childNodes;
for (var i=0; i<filters.length; i++) {
if (filters[i].nodeType == 1) {
rule.children.push(this.parseFilter(filters[i]));
rule.rules.push(this.parseFilter(filters[i]));
}
}
return rule;
@@ -357,7 +357,7 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
var filters = filter[0].childNodes;
for (var i=0; i<filters.length; i++) {
if (filters[i].nodeType == 1) {
rule.children.push(this.parseFilter(filters[i]));
rule.rules.push(this.parseFilter(filters[i]));
}
}
return rule;
@@ -368,7 +368,7 @@ OpenLayers.Format.SLD = OpenLayers.Class(OpenLayers.Format.XML, {
if (filter) {
var rule = new OpenLayers.Rule.Logical(
{type: OpenLayers.Rule.Logical.NOT});
rule.children.push(this.parseFilter(filter[0]));
rule.rules.push(this.parseFilter(filter[0]));
return rule;
}

View File

@@ -20,6 +20,14 @@ OpenLayers.Rule = OpenLayers.Class({
*/
name: 'default',
/**
* Property: context
* {Object} An optional object with properties that the rule and its
* symbolizers' property values should be evaluatad against. If no
* context is specified, feature.attributes will be used
*/
context: null,
/**
* Property: elseFilter
* {Boolean} Determines whether this rule is only to be applied only if
@@ -93,8 +101,40 @@ OpenLayers.Rule = OpenLayers.Class({
* This rule is the default rule and always returns true.
*/
evaluate: function(feature) {
// Default rule always applies. Subclasses will want to override this.
return true;
var context = this.getContext(feature);
var applies = true;
if (this.minScaleDenominator || this.maxScaleDenominator) {
var scale = feature.layer.map.getScale();
}
// check if within minScale/maxScale bounds
if (this.minScaleDenominator) {
applies = scale >= OpenLayers.Style.createLiteral(
this.minScaleDenominator, context);
}
if (applies && this.maxScaleDenominator) {
applies = scale < OpenLayers.Style.createLiteral(
this.maxScaleDenominator, context);
}
return applies;
},
/**
* Method: getContext
* Gets the context for evaluating this rule
*
* Paramters:
* feature - {<OpenLayers.Feature>} feature to take the context from if
* none is specified.
*/
getContext: function(feature) {
var context = this.context;
if (!context) {
context = feature.attributes || feature.data;
}
return context;
},
CLASS_NAME: "OpenLayers.Rule"

View File

@@ -33,7 +33,7 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
/**
* APIProperty: property
* {String}
* name of the feature attribute to compare
* name of the context property to compare
*/
property: null,
@@ -84,34 +84,37 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
/**
* APIMethod: evaluate
* evaluates this rule for a specific feature
* evaluates this rule for a specific context
*
* Parameters:
* feature - {<OpenLayers.Feature>} feature to apply the rule to.
* context - {Object} context to apply the rule to.
*
* Returns:
* {boolean} true if the rule applies, false if it does not
*/
evaluate: function(feature) {
var attributes = feature.attributes || feature.data;
if (!OpenLayers.Rule.prototype.evaluate.apply(this, arguments)) {
return false;
}
var context = this.getContext(feature);
switch(this.type) {
case OpenLayers.Rule.Comparison.EQUAL_TO:
case OpenLayers.Rule.Comparison.LESS_THAN:
case OpenLayers.Rule.Comparison.GREATER_THAN:
case OpenLayers.Rule.Comparison.LESS_THAN_OR_EQUAL_TO:
case OpenLayers.Rule.Comparison.GREATER_THAN_OR_EQUAL_TO:
return this.binaryCompare(feature, this.property, this.value);
return this.binaryCompare(context, this.property, this.value);
case OpenLayers.Rule.Comparison.BETWEEN:
var result =
attributes[this.property] > this.lowerBoundary;
context[this.property] > this.lowerBoundary;
result = result &&
attributes[this.property] < this.upperBoundary;
context[this.property] < this.upperBoundary;
return result;
case OpenLayers.Rule.Comparison.LIKE:
var regexp = new RegExp(this.value,
"gi");
return regexp.test(attributes[this.property]);
return regexp.test(context[this.property]);
}
},
@@ -165,28 +168,27 @@ OpenLayers.Rule.Comparison = OpenLayers.Class(OpenLayers.Rule, {
* Compares a feature property to a rule value
*
* Parameters:
* feature - {<OpenLayers.Feature>}
* context - {Object}
* property - {String} or {Number}
* value - {String} or {Number}, same as property
*
* Returns:
* {boolean}
*/
binaryCompare: function(feature, property, value) {
var attributes = feature.attributes || feature.data;
binaryCompare: function(context, property, value) {
switch (this.type) {
case OpenLayers.Rule.Comparison.EQUAL_TO:
return attributes[property] == value;
return context[property] == value;
case OpenLayers.Rule.Comparison.NOT_EQUAL_TO:
return attributes[property] != value;
return context[property] != value;
case OpenLayers.Rule.Comparison.LESS_THAN:
return attributes[property] < value;
return context[property] < value;
case OpenLayers.Rule.Comparison.GREATER_THAN:
return attributes[property] > value;
return context[property] > value;
case OpenLayers.Rule.Comparison.LESS_THAN_OR_EQUAL_TO:
return attributes[property] <= value;
return context[property] <= value;
case OpenLayers.Rule.Comparison.GREATER_THAN_OR_EQUAL_TO:
return attributes[property] >= value;
return context[property] >= value;
}
},

View File

@@ -53,6 +53,9 @@ OpenLayers.Rule.FeatureId = OpenLayers.Class(OpenLayers.Rule, {
* {boolean} true if the rule applies, false if it does not
*/
evaluate: function(feature) {
if (!OpenLayers.Rule.prototype.evaluate.apply(this, arguments)) {
return false;
}
for (var i=0; i<this.fids.length; i++) {
var fid = feature.fid || feature.id;
if (fid == this.fids[i]) {

View File

@@ -20,7 +20,7 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* APIProperty: children
* {Array(<OpenLayers.Rule>)} child rules for this rule
*/
children: null,
rules: null,
/**
* APIProperty: type
@@ -43,7 +43,7 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* {<OpenLayers.Rule.Logical>}
*/
initialize: function(options) {
this.children = [];
this.rules = [];
OpenLayers.Rule.prototype.initialize.apply(this, [options]);
},
@@ -52,10 +52,10 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* nullify references to prevent circular references and memory leaks
*/
destroy: function() {
for (var i=0; i<this.children.length; i++) {
this.children[i].destroy();
for (var i=0; i<this.rules.length; i++) {
this.rules[i].destroy();
}
this.children = null;
this.rules = null;
OpenLayers.Rule.prototype.destroy.apply(this, arguments);
},
@@ -70,25 +70,28 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* {boolean} true if the rule applies, false if it does not
*/
evaluate: function(feature) {
if (!OpenLayers.Rule.prototype.evaluate.apply(this, arguments)) {
return false;
}
switch(this.type) {
case OpenLayers.Rule.Logical.AND:
for (var i=0; i<this.children.length; i++) {
if (this.children[i].evaluate(feature) == false) {
for (var i=0; i<this.rules.length; i++) {
if (this.rules[i].evaluate(feature) == false) {
return false;
}
}
return true;
case OpenLayers.Rule.Logical.OR:
for (var i=0; i<this.children.length; i++) {
if (this.children[i].evaluate(feature) == true) {
for (var i=0; i<this.rules.length; i++) {
if (this.rules[i].evaluate(feature) == true) {
return true;
}
}
return false;
case OpenLayers.Rule.Logical.NOT:
return (!this.children[0].evaluate(feature));
return (!this.rules[0].evaluate(feature));
}
},

View File

@@ -112,34 +112,24 @@ OpenLayers.Style = OpenLayers.Class({
var rules = this.rules;
var rule;
var rule, context;
var elseRules = [];
var appliedRules = false;
for(var i=0; i<rules.length; i++) {
rule = rules[i];
context = rule.context;
if (!context) {
context = feature.attributes || feature.data;
}
// does the rule apply?
var applies = rule.evaluate(feature);
if (rule.minScaleDenominator || rule.maxScaleDenominator) {
var scale = feature.layer.map.getScale();
}
// check if within minScale/maxScale bounds
if (rule.minScaleDenominator) {
applies = scale >= OpenLayers.Style.createLiteral(
rule.minScaleDenominator, feature);
}
if (applies && rule.maxScaleDenominator) {
applies = scale < OpenLayers.Style.createLiteral(
rule.maxScaleDenominator, feature);
}
if(applies) {
if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
elseRules.push(rule);
} else {
appliedRules = true;
this.applySymbolizer(rule, style, feature);
this.applySymbolizer(rule, style, feature, context);
}
}
}
@@ -148,13 +138,10 @@ OpenLayers.Style = OpenLayers.Class({
if(appliedRules == false && elseRules.length > 0) {
appliedRules = true;
for(var i=0; i<elseRules.length; i++) {
this.applySymbolizer(elseRules[i], style, feature);
this.applySymbolizer(elseRules[i], style, feature, context);
}
}
// calculate literals for all styles in the propertyStyles cache
this.createLiterals(style, feature);
// don't display if there were rules but none applied
if(rules.length > 0 && appliedRules == false) {
style.display = "none";
@@ -172,18 +159,21 @@ OpenLayers.Style = OpenLayers.Class({
* rule - {OpenLayers.Rule}
* style - {Object}
* feature - {<OpenLayer.Feature.Vector>}
* context - {Object}
*
* Returns:
* {Object} A style with new symbolizer applied.
*/
applySymbolizer: function(rule, style, feature) {
applySymbolizer: function(rule, style, feature, context) {
var symbolizerPrefix = feature.geometry ?
this.getSymbolizerPrefix(feature.geometry) :
OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
// merge the style with the current style
var symbolizer = rule.symbolizer[symbolizerPrefix];
return OpenLayers.Util.extend(style, symbolizer);
// merge the style with the current style
return this.createLiterals(
OpenLayers.Util.extend(style, symbolizer), context);
},
/**
@@ -194,14 +184,16 @@ OpenLayers.Style = OpenLayers.Class({
* Parameters:
* style - {Object} style to create literals for. Will be modified
* inline.
* feature - {<OpenLayers.Feature.Vector>} feature to take properties from
* context - {Object} context to take property values from. Defaults to
* feature.attributes (or feature.data, if attributes are not
* available)
*
* Returns;
* {Object} the modified style
*/
createLiterals: function(style, feature) {
createLiterals: function(style, context) {
for (var i in this.propertyStyles) {
style[i] = OpenLayers.Style.createLiteral(style[i], feature);
style[i] = OpenLayers.Style.createLiteral(style[i], context);
}
return style;
},
@@ -302,17 +294,16 @@ OpenLayers.Style = OpenLayers.Class({
* "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
* will be replaced by the value of the "bar" attribute of the passed
* feature.
* feature {<OpenLayers.Feature>} feature to take attribute values from
* context {Object} context to take attribute values from
*
* Returns:
* {String} the parsed value. In the example of the value parameter above, the
* result would be "foo valueOfBar", assuming that the passed feature has an
* attribute named "bar" with the value "valueOfBar".
*/
OpenLayers.Style.createLiteral = function(value, feature) {
OpenLayers.Style.createLiteral = function(value, context) {
if (typeof value == "string" && value.indexOf("${") != -1) {
var attributes = feature.attributes || feature.data;
value = OpenLayers.String.format(value, attributes)
value = OpenLayers.String.format(value, context)
value = isNaN(value) ? value : parseFloat(value);
}
return value;

View File

@@ -19,7 +19,7 @@
var rule = new OpenLayers.Rule.Logical();
rule.destroy();
t.eq(rule.children, null, "children array nulled properly");
t.eq(rule.rules, null, "rules array nulled properly");
}
function test_Logical_evaluate(t) {
@@ -27,7 +27,7 @@
var rule = new OpenLayers.Rule.Logical({
type: OpenLayers.Rule.Logical.NOT});
rule.children.push(new OpenLayers.Rule());
rule.rules.push(new OpenLayers.Rule());
var feature = new OpenLayers.Feature.Vector();

View File

@@ -111,8 +111,23 @@
t.eq(r.id, elseRule.id, "(else) applySymbolizer called with correct rule");
}
style.createStyle(new OpenLayers.Feature.Vector());
}
function test_Style_context(t) {
t.plan(1);
var context = {
foo: "bar",
size: 10};
var rule = new OpenLayers.Rule.Comparison({
type: OpenLayers.Rule.Comparison.LESS_THAN,
context: context,
property: "size",
value: 11,
symbolizer: {"Point": {externalGraphic: "${foo}.png"}}});
var style = new OpenLayers.Style();
style.addRules([rule]);
var styleHash = style.createStyle(new OpenLayers.Feature.Vector());
t.eq(styleHash.externalGraphic, "bar.png", "correctly evaluated rule against a custom context");
}
function test_Style_destroy(t) {