]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Style.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Style.js
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. */
4
5
6 /**
7  * @requires OpenLayers/Util.js
8  * @requires OpenLayers/Feature/Vector.js
9  */
10
11 /**
12  * Class: OpenLayers.Style
13  * This class represents a UserStyle obtained
14  *     from a SLD, containing styling rules.
15  */
16 OpenLayers.Style = OpenLayers.Class({
17
18     /**
19      * APIProperty: name
20      * {String}
21      */
22     name: null,
23     
24     /**
25      * Property: title
26      * {String} Title of this style (set if included in SLD)
27      */
28     title: null,
29     
30     /**
31      * Property: description
32      * {String} Description of this style (set if abstract is included in SLD)
33      */
34     description: null,
35
36     /**
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.
40      */
41     layerName: null,
42     
43     /**
44      * APIProperty: isDefault
45      * {Boolean}
46      */
47     isDefault: false,
48      
49     /** 
50      * Property: rules 
51      * {Array(<OpenLayers.Rule>)}
52      */
53     rules: null,
54     
55     /**
56      * Property: context
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
60      */
61     context: null,
62
63     /**
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
69      * rules defined.
70      */
71     defaultStyle: null,
72     
73     /**
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.
79      */
80     defaultsPerSymbolizer: false,
81     
82     /**
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.
86      */
87     propertyStyles: null,
88     
89
90     /** 
91      * Constructor: OpenLayers.Style
92      * Creates a UserStyle.
93      *
94      * Parameters:
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
100      *     style.
101      *
102      * Valid options:
103      * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
104      *     style.
105      * 
106      * Return:
107      * {<OpenLayers.Style>}
108      */
109     initialize: function(style, options) {
110
111         OpenLayers.Util.extend(this, options);
112         this.rules = [];
113         if(options && options.rules) {
114             this.addRules(options.rules);
115         }
116
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"]);
121
122     },
123
124     /** 
125      * APIMethod: destroy
126      * nullify references to prevent circular references and memory leaks
127      */
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;
132         }
133         this.rules = null;
134         this.defaultStyle = null;
135     },
136     
137     /**
138      * Method: createSymbolizer
139      * creates a style by applying all feature-dependent rules to the base
140      * style.
141      * 
142      * Parameters:
143      * feature - {<OpenLayers.Feature>} feature to evaluate rules for
144      * 
145      * Returns:
146      * {Object} symbolizer hash
147      */
148     createSymbolizer: function(feature) {
149         var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
150             OpenLayers.Util.extend({}, this.defaultStyle), feature);
151         
152         var rules = this.rules;
153
154         var rule, context;
155         var elseRules = [];
156         var appliedRules = false;
157         for(var i=0, len=rules.length; i<len; i++) {
158             rule = rules[i];
159             // does the rule apply?
160             var applies = rule.evaluate(feature);
161             
162             if(applies) {
163                 if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
164                     elseRules.push(rule);
165                 } else {
166                     appliedRules = true;
167                     this.applySymbolizer(rule, style, feature);
168                 }
169             }
170         }
171         
172         // if no other rules apply, apply the rules with else filters
173         if(appliedRules == false && elseRules.length > 0) {
174             appliedRules = true;
175             for(var i=0, len=elseRules.length; i<len; i++) {
176                 this.applySymbolizer(elseRules[i], style, feature);
177             }
178         }
179
180         // don't display if there were rules but none applied
181         if(rules.length > 0 && appliedRules == false) {
182             style.display = "none";
183         }
184         
185         return style;
186     },
187     
188     /**
189      * Method: applySymbolizer
190      *
191      * Parameters:
192      * rule - {OpenLayers.Rule}
193      * style - {Object}
194      * feature - {<OpenLayer.Feature.Vector>}
195      *
196      * Returns:
197      * {Object} A style with new symbolizer applied.
198      */
199     applySymbolizer: function(rule, style, feature) {
200         var symbolizerPrefix = feature.geometry ?
201                 this.getSymbolizerPrefix(feature.geometry) :
202                 OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
203
204         var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
205         
206         if(this.defaultsPerSymbolizer === true) {
207             var defaults = this.defaultStyle;
208             OpenLayers.Util.applyDefaults(symbolizer, {
209                 pointRadius: defaults.pointRadius
210             });
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
218                 });
219             }
220             if(symbolizer.fill === true || symbolizer.graphic === true) {
221                 OpenLayers.Util.applyDefaults(symbolizer, {
222                     fillColor: defaults.fillColor,
223                     fillOpacity: defaults.fillOpacity
224                 });
225             }
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
236                 });
237             }
238         }
239
240         // merge the style with the current style
241         return this.createLiterals(
242                 OpenLayers.Util.extend(style, symbolizer), feature);
243     },
244     
245     /**
246      * Method: createLiterals
247      * creates literals for all style properties that have an entry in
248      * <this.propertyStyles>.
249      * 
250      * Parameters:
251      * style   - {Object} style to create literals for. Will be modified
252      *           inline.
253      * feature - {Object}
254      * 
255      * Returns:
256      * {Object} the modified style
257      */
258     createLiterals: function(style, feature) {
259         var context = this.context || feature.attributes || feature.data;
260         
261         for (var i in this.propertyStyles) {
262             style[i] = OpenLayers.Style.createLiteral(style[i], context, feature);
263         }
264         return style;
265     },
266     
267     /**
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.
272      * 
273      * Returns:
274      * {Object} hash of property names that need createLiteral parsing. The
275      * name of the property is the key, and the value is true;
276      */
277     findPropertyStyles: function() {
278         var propertyStyles = {};
279
280         // check the default style
281         var style = this.defaultStyle;
282         this.addPropertyStyles(propertyStyles, style);
283
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);
294                 } else {
295                     // symbolizer is a hash of style properties
296                     this.addPropertyStyles(propertyStyles, symbolizer);
297                     break;
298                 }
299             }
300         }
301         return propertyStyles;
302     },
303     
304     /**
305      * Method: addPropertyStyles
306      * 
307      * Parameters:
308      * propertyStyles - {Object} hash to add new property styles to. Will be
309      *                  modified inline
310      * symbolizer     - {Object} search this symbolizer for property styles
311      * 
312      * Returns:
313      * {Object} propertyStyles hash
314      */
315     addPropertyStyles: function(propertyStyles, symbolizer) {
316         var property;
317         for (var key in symbolizer) {
318             property = symbolizer[key];
319             if (typeof property == "string" &&
320                     property.match(/\$\{\w+\}/)) {
321                 propertyStyles[key] = true;
322             }
323         }
324         return propertyStyles;
325     },
326     
327     /**
328      * APIMethod: addRules
329      * Adds rules to this style.
330      * 
331      * Parameters:
332      * rules - {Array(<OpenLayers.Rule>)}
333      */
334     addRules: function(rules) {
335         this.rules = this.rules.concat(rules);
336         this.propertyStyles = this.findPropertyStyles();
337     },
338     
339     /**
340      * APIMethod: setDefaultStyle
341      * Sets the default style for this style object.
342      * 
343      * Parameters:
344      * style - {Object} Hash of style properties
345      */
346     setDefaultStyle: function(style) {
347         this.defaultStyle = style; 
348         this.propertyStyles = this.findPropertyStyles();
349     },
350         
351     /**
352      * Method: getSymbolizerPrefix
353      * Returns the correct symbolizer prefix according to the
354      * geometry type of the passed geometry
355      * 
356      * Parameters:
357      * geometry {<OpenLayers.Geometry>}
358      * 
359      * Returns:
360      * {String} key of the according symbolizer
361      */
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) {
366                 return prefixes[i];
367             }
368         }
369     },
370     
371     CLASS_NAME: "OpenLayers.Style"
372 });
373
374
375 /**
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.
379  * 
380  * Parameters:
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
384  *         feature.
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.
388  * 
389  * Returns:
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".
393  */
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);
398     }
399     return value;
400 };
401     
402 /**
403  * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES
404  * {Array} prefixes of the sld symbolizers. These are the
405  * same as the main geometry types
406  */
407 OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text'];