1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
7 * @requires OpenLayers/Util.js
8 * @requires OpenLayers/Feature/Vector.js
12 * Class: OpenLayers.Style
13 * This class represents a UserStyle obtained
14 * from a SLD, containing styling rules.
16 OpenLayers.Style = OpenLayers.Class({
26 * {String} Title of this style (set if included in SLD)
31 * Property: description
32 * {String} Description of this style (set if abstract is included in SLD)
37 * APIProperty: layerName
38 * {<String>} name of the layer that this style belongs to, usually
39 * according to the NamedLayer attribute of an SLD document.
44 * APIProperty: isDefault
51 * {Array(<OpenLayers.Rule>)}
57 * {Object} An optional object with properties that symbolizers' property
58 * values should be evaluated against. If no context is specified,
59 * feature.attributes will be used
64 * Property: defaultStyle
65 * {Object} hash of style properties to use as default for merging
66 * rule-based style symbolizers onto. If no rules are defined,
67 * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
68 * true, the defaultStyle will only be taken into account if there are
74 * Property: defaultsPerSymbolizer
75 * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
76 * of every rule. Properties of the <defaultStyle> will also be used to set
77 * missing symbolizer properties if the symbolizer has stroke, fill or
78 * graphic set to true. Default is false.
80 defaultsPerSymbolizer: false,
83 * Property: propertyStyles
84 * {Hash of Boolean} cache of style properties that need to be parsed for
85 * propertyNames. Property names are keys, values won't be used.
91 * Constructor: OpenLayers.Style
92 * Creates a UserStyle.
95 * style - {Object} Optional hash of style properties that will be
96 * used as default style for this style object. This style
97 * applies if no rules are specified. Symbolizers defined in
98 * rules will extend this default style.
99 * options - {Object} An optional object with properties to set on the
103 * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
107 * {<OpenLayers.Style>}
109 initialize: function(style, options) {
111 OpenLayers.Util.extend(this, options);
113 if(options && options.rules) {
114 this.addRules(options.rules);
117 // use the default style from OpenLayers.Feature.Vector if no style
118 // was given in the constructor
119 this.setDefaultStyle(style ||
120 OpenLayers.Feature.Vector.style["default"]);
126 * nullify references to prevent circular references and memory leaks
128 destroy: function() {
129 for (var i=0, len=this.rules.length; i<len; i++) {
130 this.rules[i].destroy();
131 this.rules[i] = null;
134 this.defaultStyle = null;
138 * Method: createSymbolizer
139 * creates a style by applying all feature-dependent rules to the base
143 * feature - {<OpenLayers.Feature>} feature to evaluate rules for
146 * {Object} symbolizer hash
148 createSymbolizer: function(feature) {
149 var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
150 OpenLayers.Util.extend({}, this.defaultStyle), feature);
152 var rules = this.rules;
156 var appliedRules = false;
157 for(var i=0, len=rules.length; i<len; i++) {
159 // does the rule apply?
160 var applies = rule.evaluate(feature);
163 if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
164 elseRules.push(rule);
167 this.applySymbolizer(rule, style, feature);
172 // if no other rules apply, apply the rules with else filters
173 if(appliedRules == false && elseRules.length > 0) {
175 for(var i=0, len=elseRules.length; i<len; i++) {
176 this.applySymbolizer(elseRules[i], style, feature);
180 // don't display if there were rules but none applied
181 if(rules.length > 0 && appliedRules == false) {
182 style.display = "none";
189 * Method: applySymbolizer
192 * rule - {OpenLayers.Rule}
194 * feature - {<OpenLayer.Feature.Vector>}
197 * {Object} A style with new symbolizer applied.
199 applySymbolizer: function(rule, style, feature) {
200 var symbolizerPrefix = feature.geometry ?
201 this.getSymbolizerPrefix(feature.geometry) :
202 OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
204 var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
206 if(this.defaultsPerSymbolizer === true) {
207 var defaults = this.defaultStyle;
208 OpenLayers.Util.applyDefaults(symbolizer, {
209 pointRadius: defaults.pointRadius
211 if(symbolizer.stroke === true || symbolizer.graphic === true) {
212 OpenLayers.Util.applyDefaults(symbolizer, {
213 strokeWidth: defaults.strokeWidth,
214 strokeColor: defaults.strokeColor,
215 strokeOpacity: defaults.strokeOpacity,
216 strokeDashstyle: defaults.strokeDashstyle,
217 strokeLinecap: defaults.strokeLinecap
220 if(symbolizer.fill === true || symbolizer.graphic === true) {
221 OpenLayers.Util.applyDefaults(symbolizer, {
222 fillColor: defaults.fillColor,
223 fillOpacity: defaults.fillOpacity
226 if(symbolizer.graphic === true) {
227 OpenLayers.Util.applyDefaults(symbolizer, {
228 pointRadius: this.defaultStyle.pointRadius,
229 externalGraphic: this.defaultStyle.externalGraphic,
230 graphicName: this.defaultStyle.graphicName,
231 graphicOpacity: this.defaultStyle.graphicOpacity,
232 graphicWidth: this.defaultStyle.graphicWidth,
233 graphicHeight: this.defaultStyle.graphicHeight,
234 graphicXOffset: this.defaultStyle.graphicXOffset,
235 graphicYOffset: this.defaultStyle.graphicYOffset
240 // merge the style with the current style
241 return this.createLiterals(
242 OpenLayers.Util.extend(style, symbolizer), feature);
246 * Method: createLiterals
247 * creates literals for all style properties that have an entry in
248 * <this.propertyStyles>.
251 * style - {Object} style to create literals for. Will be modified
256 * {Object} the modified style
258 createLiterals: function(style, feature) {
259 var context = this.context || feature.attributes || feature.data;
261 for (var i in this.propertyStyles) {
262 style[i] = OpenLayers.Style.createLiteral(style[i], context, feature);
268 * Method: findPropertyStyles
269 * Looks into all rules for this style and the defaultStyle to collect
270 * all the style hash property names containing ${...} strings that have
271 * to be replaced using the createLiteral method before returning them.
274 * {Object} hash of property names that need createLiteral parsing. The
275 * name of the property is the key, and the value is true;
277 findPropertyStyles: function() {
278 var propertyStyles = {};
280 // check the default style
281 var style = this.defaultStyle;
282 this.addPropertyStyles(propertyStyles, style);
284 // walk through all rules to check for properties in their symbolizer
285 var rules = this.rules;
286 var symbolizer, value;
287 for (var i=0, len=rules.length; i<len; i++) {
288 symbolizer = rules[i].symbolizer;
289 for (var key in symbolizer) {
290 value = symbolizer[key];
291 if (typeof value == "object") {
292 // symbolizer key is "Point", "Line" or "Polygon"
293 this.addPropertyStyles(propertyStyles, value);
295 // symbolizer is a hash of style properties
296 this.addPropertyStyles(propertyStyles, symbolizer);
301 return propertyStyles;
305 * Method: addPropertyStyles
308 * propertyStyles - {Object} hash to add new property styles to. Will be
310 * symbolizer - {Object} search this symbolizer for property styles
313 * {Object} propertyStyles hash
315 addPropertyStyles: function(propertyStyles, symbolizer) {
317 for (var key in symbolizer) {
318 property = symbolizer[key];
319 if (typeof property == "string" &&
320 property.match(/\$\{\w+\}/)) {
321 propertyStyles[key] = true;
324 return propertyStyles;
328 * APIMethod: addRules
329 * Adds rules to this style.
332 * rules - {Array(<OpenLayers.Rule>)}
334 addRules: function(rules) {
335 this.rules = this.rules.concat(rules);
336 this.propertyStyles = this.findPropertyStyles();
340 * APIMethod: setDefaultStyle
341 * Sets the default style for this style object.
344 * style - {Object} Hash of style properties
346 setDefaultStyle: function(style) {
347 this.defaultStyle = style;
348 this.propertyStyles = this.findPropertyStyles();
352 * Method: getSymbolizerPrefix
353 * Returns the correct symbolizer prefix according to the
354 * geometry type of the passed geometry
357 * geometry {<OpenLayers.Geometry>}
360 * {String} key of the according symbolizer
362 getSymbolizerPrefix: function(geometry) {
363 var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
364 for (var i=0, len=prefixes.length; i<len; i++) {
365 if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
371 CLASS_NAME: "OpenLayers.Style"
376 * Function: createLiteral
377 * converts a style value holding a combination of PropertyName and Literal
378 * into a Literal, taking the property values from the passed features.
381 * value - {String} value to parse. If this string contains a construct like
382 * "foo ${bar}", then "foo " will be taken as literal, and "${bar}"
383 * will be replaced by the value of the "bar" attribute of the passed
385 * context - {Object} context to take attribute values from
386 * feature - {OpenLayers.Feature.Vector} The feature that will be passed
387 * to <OpenLayers.String.format> for evaluating functions in the context.
390 * {String} the parsed value. In the example of the value parameter above, the
391 * result would be "foo valueOfBar", assuming that the passed feature has an
392 * attribute named "bar" with the value "valueOfBar".
394 OpenLayers.Style.createLiteral = function(value, context, feature) {
395 if (typeof value == "string" && value.indexOf("${") != -1) {
396 value = OpenLayers.String.format(value, context, [feature]);
397 value = (isNaN(value) || !value) ? value : parseFloat(value);
403 * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
404 * {Array} prefixes of the sld symbolizers. These are the
405 * same as the main geometry types
407 OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text'];