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

View File

@@ -20,6 +20,14 @@ OpenLayers.Rule = OpenLayers.Class({
*/ */
name: 'default', 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 * Property: elseFilter
* {Boolean} Determines whether this rule is only to be applied only if * {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. * This rule is the default rule and always returns true.
*/ */
evaluate: function(feature) { evaluate: function(feature) {
// Default rule always applies. Subclasses will want to override this. var context = this.getContext(feature);
return true; 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" CLASS_NAME: "OpenLayers.Rule"

View File

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

View File

@@ -20,7 +20,7 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* APIProperty: children * APIProperty: children
* {Array(<OpenLayers.Rule>)} child rules for this rule * {Array(<OpenLayers.Rule>)} child rules for this rule
*/ */
children: null, rules: null,
/** /**
* APIProperty: type * APIProperty: type
@@ -43,7 +43,7 @@ OpenLayers.Rule.Logical = OpenLayers.Class(OpenLayers.Rule, {
* {<OpenLayers.Rule.Logical>} * {<OpenLayers.Rule.Logical>}
*/ */
initialize: function(options) { initialize: function(options) {
this.children = []; this.rules = [];
OpenLayers.Rule.prototype.initialize.apply(this, [options]); 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 * nullify references to prevent circular references and memory leaks
*/ */
destroy: function() { destroy: function() {
for (var i=0; i<this.children.length; i++) { for (var i=0; i<this.rules.length; i++) {
this.children[i].destroy(); this.rules[i].destroy();
} }
this.children = null; this.rules = null;
OpenLayers.Rule.prototype.destroy.apply(this, arguments); 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 * {boolean} true if the rule applies, false if it does not
*/ */
evaluate: function(feature) { evaluate: function(feature) {
if (!OpenLayers.Rule.prototype.evaluate.apply(this, arguments)) {
return false;
}
switch(this.type) { switch(this.type) {
case OpenLayers.Rule.Logical.AND: case OpenLayers.Rule.Logical.AND:
for (var i=0; i<this.children.length; i++) { for (var i=0; i<this.rules.length; i++) {
if (this.children[i].evaluate(feature) == false) { if (this.rules[i].evaluate(feature) == false) {
return false; return false;
} }
} }
return true; return true;
case OpenLayers.Rule.Logical.OR: case OpenLayers.Rule.Logical.OR:
for (var i=0; i<this.children.length; i++) { for (var i=0; i<this.rules.length; i++) {
if (this.children[i].evaluate(feature) == true) { if (this.rules[i].evaluate(feature) == true) {
return true; return true;
} }
} }
return false; return false;
case OpenLayers.Rule.Logical.NOT: 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 rules = this.rules;
var rule; var rule, context;
var elseRules = []; var elseRules = [];
var appliedRules = false; var appliedRules = false;
for(var i=0; i<rules.length; i++) { for(var i=0; i<rules.length; i++) {
rule = rules[i]; rule = rules[i];
context = rule.context;
if (!context) {
context = feature.attributes || feature.data;
}
// does the rule apply? // does the rule apply?
var applies = rule.evaluate(feature); 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(applies) {
if(rule instanceof OpenLayers.Rule && rule.elseFilter) { if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
elseRules.push(rule); elseRules.push(rule);
} else { } else {
appliedRules = true; 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) { if(appliedRules == false && elseRules.length > 0) {
appliedRules = true; appliedRules = true;
for(var i=0; i<elseRules.length; i++) { 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 // don't display if there were rules but none applied
if(rules.length > 0 && appliedRules == false) { if(rules.length > 0 && appliedRules == false) {
style.display = "none"; style.display = "none";
@@ -172,18 +159,21 @@ OpenLayers.Style = OpenLayers.Class({
* rule - {OpenLayers.Rule} * rule - {OpenLayers.Rule}
* style - {Object} * style - {Object}
* feature - {<OpenLayer.Feature.Vector>} * feature - {<OpenLayer.Feature.Vector>}
* context - {Object}
* *
* Returns: * Returns:
* {Object} A style with new symbolizer applied. * {Object} A style with new symbolizer applied.
*/ */
applySymbolizer: function(rule, style, feature) { applySymbolizer: function(rule, style, feature, context) {
var symbolizerPrefix = feature.geometry ? var symbolizerPrefix = feature.geometry ?
this.getSymbolizerPrefix(feature.geometry) : this.getSymbolizerPrefix(feature.geometry) :
OpenLayers.Style.SYMBOLIZER_PREFIXES[0]; OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
// merge the style with the current style
var symbolizer = rule.symbolizer[symbolizerPrefix]; 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: * Parameters:
* style - {Object} style to create literals for. Will be modified * style - {Object} style to create literals for. Will be modified
* inline. * 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; * Returns;
* {Object} the modified style * {Object} the modified style
*/ */
createLiterals: function(style, feature) { createLiterals: function(style, context) {
for (var i in this.propertyStyles) { for (var i in this.propertyStyles) {
style[i] = OpenLayers.Style.createLiteral(style[i], feature); style[i] = OpenLayers.Style.createLiteral(style[i], context);
} }
return style; return style;
}, },
@@ -302,17 +294,16 @@ OpenLayers.Style = OpenLayers.Class({
* "foo ${bar}", then "foo " will be taken as literal, and "${bar}" * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
* will be replaced by the value of the "bar" attribute of the passed * will be replaced by the value of the "bar" attribute of the passed
* feature. * feature.
* feature {<OpenLayers.Feature>} feature to take attribute values from * context {Object} context to take attribute values from
* *
* Returns: * Returns:
* {String} the parsed value. In the example of the value parameter above, the * {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 * result would be "foo valueOfBar", assuming that the passed feature has an
* attribute named "bar" with the value "valueOfBar". * 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) { if (typeof value == "string" && value.indexOf("${") != -1) {
var attributes = feature.attributes || feature.data; value = OpenLayers.String.format(value, context)
value = OpenLayers.String.format(value, attributes)
value = isNaN(value) ? value : parseFloat(value); value = isNaN(value) ? value : parseFloat(value);
} }
return value; return value;

View File

@@ -19,7 +19,7 @@
var rule = new OpenLayers.Rule.Logical(); var rule = new OpenLayers.Rule.Logical();
rule.destroy(); 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) { function test_Logical_evaluate(t) {
@@ -27,7 +27,7 @@
var rule = new OpenLayers.Rule.Logical({ var rule = new OpenLayers.Rule.Logical({
type: OpenLayers.Rule.Logical.NOT}); type: OpenLayers.Rule.Logical.NOT});
rule.children.push(new OpenLayers.Rule()); rule.rules.push(new OpenLayers.Rule());
var feature = new OpenLayers.Feature.Vector(); var feature = new OpenLayers.Feature.Vector();

View File

@@ -111,8 +111,23 @@
t.eq(r.id, elseRule.id, "(else) applySymbolizer called with correct rule"); t.eq(r.id, elseRule.id, "(else) applySymbolizer called with correct rule");
} }
style.createStyle(new OpenLayers.Feature.Vector()); 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) { function test_Style_destroy(t) {