| 1 | /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for |
|---|
| 2 | * full list of contributors). Published under the Clear BSD license. |
|---|
| 3 | * See http://svn.openlayers.org/trunk/openlayers/license.txt for the |
|---|
| 4 | * full text of the license. */ |
|---|
| 5 | |
|---|
| 6 | |
|---|
| 7 | /** |
|---|
| 8 | * @requires OpenLayers/Util.js |
|---|
| 9 | * @requires OpenLayers/Feature/Vector.js |
|---|
| 10 | */ |
|---|
| 11 | |
|---|
| 12 | /** |
|---|
| 13 | * Class: OpenLayers.Style |
|---|
| 14 | * This class represents a UserStyle obtained |
|---|
| 15 | * from a SLD, containing styling rules. |
|---|
| 16 | */ |
|---|
| 17 | OpenLayers.Style = OpenLayers.Class({ |
|---|
| 18 | |
|---|
| 19 | /** |
|---|
| 20 | * Property: id |
|---|
| 21 | * {String} A unique id for this session. |
|---|
| 22 | */ |
|---|
| 23 | id: null, |
|---|
| 24 | |
|---|
| 25 | /** |
|---|
| 26 | * APIProperty: name |
|---|
| 27 | * {String} |
|---|
| 28 | */ |
|---|
| 29 | name: null, |
|---|
| 30 | |
|---|
| 31 | /** |
|---|
| 32 | * Property: title |
|---|
| 33 | * {String} Title of this style (set if included in SLD) |
|---|
| 34 | */ |
|---|
| 35 | title: null, |
|---|
| 36 | |
|---|
| 37 | /** |
|---|
| 38 | * Property: description |
|---|
| 39 | * {String} Description of this style (set if abstract is included in SLD) |
|---|
| 40 | */ |
|---|
| 41 | description: null, |
|---|
| 42 | |
|---|
| 43 | /** |
|---|
| 44 | * APIProperty: layerName |
|---|
| 45 | * {<String>} name of the layer that this style belongs to, usually |
|---|
| 46 | * according to the NamedLayer attribute of an SLD document. |
|---|
| 47 | */ |
|---|
| 48 | layerName: null, |
|---|
| 49 | |
|---|
| 50 | /** |
|---|
| 51 | * APIProperty: isDefault |
|---|
| 52 | * {Boolean} |
|---|
| 53 | */ |
|---|
| 54 | isDefault: false, |
|---|
| 55 | |
|---|
| 56 | /** |
|---|
| 57 | * Property: rules |
|---|
| 58 | * {Array(<OpenLayers.Rule>)} |
|---|
| 59 | */ |
|---|
| 60 | rules: null, |
|---|
| 61 | |
|---|
| 62 | /** |
|---|
| 63 | * Property: context |
|---|
| 64 | * {Object} An optional object with properties that symbolizers' property |
|---|
| 65 | * values should be evaluated against. If no context is specified, |
|---|
| 66 | * feature.attributes will be used |
|---|
| 67 | */ |
|---|
| 68 | context: null, |
|---|
| 69 | |
|---|
| 70 | /** |
|---|
| 71 | * Property: defaultStyle |
|---|
| 72 | * {Object} hash of style properties to use as default for merging |
|---|
| 73 | * rule-based style symbolizers onto. If no rules are defined, |
|---|
| 74 | * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to |
|---|
| 75 | * true, the defaultStyle will only be taken into account if there are |
|---|
| 76 | * rules defined. |
|---|
| 77 | */ |
|---|
| 78 | defaultStyle: null, |
|---|
| 79 | |
|---|
| 80 | /** |
|---|
| 81 | * Property: defaultsPerSymbolizer |
|---|
| 82 | * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer |
|---|
| 83 | * of every rule. Properties of the <defaultStyle> will also be used to set |
|---|
| 84 | * missing symbolizer properties if the symbolizer has stroke, fill or |
|---|
| 85 | * graphic set to true. Default is false. |
|---|
| 86 | */ |
|---|
| 87 | defaultsPerSymbolizer: false, |
|---|
| 88 | |
|---|
| 89 | /** |
|---|
| 90 | * Property: propertyStyles |
|---|
| 91 | * {Hash of Boolean} cache of style properties that need to be parsed for |
|---|
| 92 | * propertyNames. Property names are keys, values won't be used. |
|---|
| 93 | */ |
|---|
| 94 | propertyStyles: null, |
|---|
| 95 | |
|---|
| 96 | |
|---|
| 97 | /** |
|---|
| 98 | * Constructor: OpenLayers.Style |
|---|
| 99 | * Creates a UserStyle. |
|---|
| 100 | * |
|---|
| 101 | * Parameters: |
|---|
| 102 | * style - {Object} Optional hash of style properties that will be |
|---|
| 103 | * used as default style for this style object. This style |
|---|
| 104 | * applies if no rules are specified. Symbolizers defined in |
|---|
| 105 | * rules will extend this default style. |
|---|
| 106 | * options - {Object} An optional object with properties to set on the |
|---|
| 107 | * style. |
|---|
| 108 | * |
|---|
| 109 | * Valid options: |
|---|
| 110 | * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the |
|---|
| 111 | * style. |
|---|
| 112 | * |
|---|
| 113 | * Return: |
|---|
| 114 | * {<OpenLayers.Style>} |
|---|
| 115 | */ |
|---|
| 116 | initialize: function(style, options) { |
|---|
| 117 | |
|---|
| 118 | OpenLayers.Util.extend(this, options); |
|---|
| 119 | this.rules = []; |
|---|
| 120 | if(options && options.rules) { |
|---|
| 121 | this.addRules(options.rules); |
|---|
| 122 | } |
|---|
| 123 | |
|---|
| 124 | // use the default style from OpenLayers.Feature.Vector if no style |
|---|
| 125 | // was given in the constructor |
|---|
| 126 | this.setDefaultStyle(style || |
|---|
| 127 | OpenLayers.Feature.Vector.style["default"]); |
|---|
| 128 | |
|---|
| 129 | this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); |
|---|
| 130 | }, |
|---|
| 131 | |
|---|
| 132 | /** |
|---|
| 133 | * APIMethod: destroy |
|---|
| 134 | * nullify references to prevent circular references and memory leaks |
|---|
| 135 | */ |
|---|
| 136 | destroy: function() { |
|---|
| 137 | for (var i=0, len=this.rules.length; i<len; i++) { |
|---|
| 138 | this.rules[i].destroy(); |
|---|
| 139 | this.rules[i] = null; |
|---|
| 140 | } |
|---|
| 141 | this.rules = null; |
|---|
| 142 | this.defaultStyle = null; |
|---|
| 143 | }, |
|---|
| 144 | |
|---|
| 145 | /** |
|---|
| 146 | * Method: createSymbolizer |
|---|
| 147 | * creates a style by applying all feature-dependent rules to the base |
|---|
| 148 | * style. |
|---|
| 149 | * |
|---|
| 150 | * Parameters: |
|---|
| 151 | * feature - {<OpenLayers.Feature>} feature to evaluate rules for |
|---|
| 152 | * |
|---|
| 153 | * Returns: |
|---|
| 154 | * {Object} symbolizer hash |
|---|
| 155 | */ |
|---|
| 156 | createSymbolizer: function(feature) { |
|---|
| 157 | var style = this.defaultsPerSymbolizer ? {} : this.createLiterals( |
|---|
| 158 | OpenLayers.Util.extend({}, this.defaultStyle), feature); |
|---|
| 159 | |
|---|
| 160 | var rules = this.rules; |
|---|
| 161 | |
|---|
| 162 | var rule, context; |
|---|
| 163 | var elseRules = []; |
|---|
| 164 | var appliedRules = false; |
|---|
| 165 | for(var i=0, len=rules.length; i<len; i++) { |
|---|
| 166 | rule = rules[i]; |
|---|
| 167 | // does the rule apply? |
|---|
| 168 | var applies = rule.evaluate(feature); |
|---|
| 169 | |
|---|
| 170 | if(applies) { |
|---|
| 171 | if(rule instanceof OpenLayers.Rule && rule.elseFilter) { |
|---|
| 172 | elseRules.push(rule); |
|---|
| 173 | } else { |
|---|
| 174 | appliedRules = true; |
|---|
| 175 | this.applySymbolizer(rule, style, feature); |
|---|
| 176 | } |
|---|
| 177 | } |
|---|
| 178 | } |
|---|
| 179 | |
|---|
| 180 | // if no other rules apply, apply the rules with else filters |
|---|
| 181 | if(appliedRules == false && elseRules.length > 0) { |
|---|
| 182 | appliedRules = true; |
|---|
| 183 | for(var i=0, len=elseRules.length; i<len; i++) { |
|---|
| 184 | this.applySymbolizer(elseRules[i], style, feature); |
|---|
| 185 | } |
|---|
| 186 | } |
|---|
| 187 | |
|---|
| 188 | // don't display if there were rules but none applied |
|---|
| 189 | if(rules.length > 0 && appliedRules == false) { |
|---|
| 190 | style.display = "none"; |
|---|
| 191 | } |
|---|
| 192 | |
|---|
| 193 | return style; |
|---|
| 194 | }, |
|---|
| 195 | |
|---|
| 196 | /** |
|---|
| 197 | * Method: applySymbolizer |
|---|
| 198 | * |
|---|
| 199 | * Parameters: |
|---|
| 200 | * rule - {OpenLayers.Rule} |
|---|
| 201 | * style - {Object} |
|---|
| 202 | * feature - {<OpenLayer.Feature.Vector>} |
|---|
| 203 | * |
|---|
| 204 | * Returns: |
|---|
| 205 | * {Object} A style with new symbolizer applied. |
|---|
| 206 | */ |
|---|
| 207 | applySymbolizer: function(rule, style, feature) { |
|---|
| 208 | var symbolizerPrefix = feature.geometry ? |
|---|
| 209 | this.getSymbolizerPrefix(feature.geometry) : |
|---|
| 210 | OpenLayers.Style.SYMBOLIZER_PREFIXES[0]; |
|---|
| 211 | |
|---|
| 212 | var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer; |
|---|
| 213 | |
|---|
| 214 | if(this.defaultsPerSymbolizer === true) { |
|---|
| 215 | var defaults = this.defaultStyle; |
|---|
| 216 | OpenLayers.Util.applyDefaults(symbolizer, { |
|---|
| 217 | pointRadius: defaults.pointRadius |
|---|
| 218 | }); |
|---|
| 219 | if(symbolizer.stroke === true || symbolizer.graphic === true) { |
|---|
| 220 | OpenLayers.Util.applyDefaults(symbolizer, { |
|---|
| 221 | strokeWidth: defaults.strokeWidth, |
|---|
| 222 | strokeColor: defaults.strokeColor, |
|---|
| 223 | strokeOpacity: defaults.strokeOpacity, |
|---|
| 224 | strokeDashstyle: defaults.strokeDashstyle, |
|---|
| 225 | strokeLinecap: defaults.strokeLinecap |
|---|
| 226 | }); |
|---|
| 227 | } |
|---|
| 228 | if(symbolizer.fill === true || symbolizer.graphic === true) { |
|---|
| 229 | OpenLayers.Util.applyDefaults(symbolizer, { |
|---|
| 230 | fillColor: defaults.fillColor, |
|---|
| 231 | fillOpacity: defaults.fillOpacity |
|---|
| 232 | }); |
|---|
| 233 | } |
|---|
| 234 | if(symbolizer.graphic === true) { |
|---|
| 235 | OpenLayers.Util.applyDefaults(symbolizer, { |
|---|
| 236 | pointRadius: this.defaultStyle.pointRadius, |
|---|
| 237 | externalGraphic: this.defaultStyle.externalGraphic, |
|---|
| 238 | graphicName: this.defaultStyle.graphicName, |
|---|
| 239 | graphicOpacity: this.defaultStyle.graphicOpacity, |
|---|
| 240 | graphicWidth: this.defaultStyle.graphicWidth, |
|---|
| 241 | graphicHeight: this.defaultStyle.graphicHeight, |
|---|
| 242 | graphicXOffset: this.defaultStyle.graphicXOffset, |
|---|
| 243 | graphicYOffset: this.defaultStyle.graphicYOffset |
|---|
| 244 | }); |
|---|
| 245 | } |
|---|
| 246 | } |
|---|
| 247 | |
|---|
| 248 | // merge the style with the current style |
|---|
| 249 | return this.createLiterals( |
|---|
| 250 | OpenLayers.Util.extend(style, symbolizer), feature); |
|---|
| 251 | }, |
|---|
| 252 | |
|---|
| 253 | /** |
|---|
| 254 | * Method: createLiterals |
|---|
| 255 | * creates literals for all style properties that have an entry in |
|---|
| 256 | * <this.propertyStyles>. |
|---|
| 257 | * |
|---|
| 258 | * Parameters: |
|---|
| 259 | * style - {Object} style to create literals for. Will be modified |
|---|
| 260 | * inline. |
|---|
| 261 | * feature - {Object} |
|---|
| 262 | * |
|---|
| 263 | * Returns: |
|---|
| 264 | * {Object} the modified style |
|---|
| 265 | */ |
|---|
| 266 | createLiterals: function(style, feature) { |
|---|
| 267 | var context = OpenLayers.Util.extend({}, feature.attributes || feature.data); |
|---|
| 268 | OpenLayers.Util.extend(context, this.context); |
|---|
| 269 | |
|---|
| 270 | for (var i in this.propertyStyles) { |
|---|
| 271 | style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i); |
|---|
| 272 | } |
|---|
| 273 | return style; |
|---|
| 274 | }, |
|---|
| 275 | |
|---|
| 276 | /** |
|---|
| 277 | * Method: findPropertyStyles |
|---|
| 278 | * Looks into all rules for this style and the defaultStyle to collect |
|---|
| 279 | * all the style hash property names containing ${...} strings that have |
|---|
| 280 | * to be replaced using the createLiteral method before returning them. |
|---|
| 281 | * |
|---|
| 282 | * Returns: |
|---|
| 283 | * {Object} hash of property names that need createLiteral parsing. The |
|---|
| 284 | * name of the property is the key, and the value is true; |
|---|
| 285 | */ |
|---|
| 286 | findPropertyStyles: function() { |
|---|
| 287 | var propertyStyles = {}; |
|---|
| 288 | |
|---|
| 289 | // check the default style |
|---|
| 290 | var style = this.defaultStyle; |
|---|
| 291 | this.addPropertyStyles(propertyStyles, style); |
|---|
| 292 | |
|---|
| 293 | // walk through all rules to check for properties in their symbolizer |
|---|
| 294 | var rules = this.rules; |
|---|
| 295 | var symbolizer, value; |
|---|
| 296 | for (var i=0, len=rules.length; i<len; i++) { |
|---|
| 297 | symbolizer = rules[i].symbolizer; |
|---|
| 298 | for (var key in symbolizer) { |
|---|
| 299 | value = symbolizer[key]; |
|---|
| 300 | if (typeof value == "object") { |
|---|
| 301 | // symbolizer key is "Point", "Line" or "Polygon" |
|---|
| 302 | this.addPropertyStyles(propertyStyles, value); |
|---|
| 303 | } else { |
|---|
| 304 | // symbolizer is a hash of style properties |
|---|
| 305 | this.addPropertyStyles(propertyStyles, symbolizer); |
|---|
| 306 | break; |
|---|
| 307 | } |
|---|
| 308 | } |
|---|
| 309 | } |
|---|
| 310 | return propertyStyles; |
|---|
| 311 | }, |
|---|
| 312 | |
|---|
| 313 | /** |
|---|
| 314 | * Method: addPropertyStyles |
|---|
| 315 | * |
|---|
| 316 | * Parameters: |
|---|
| 317 | * propertyStyles - {Object} hash to add new property styles to. Will be |
|---|
| 318 | * modified inline |
|---|
| 319 | * symbolizer - {Object} search this symbolizer for property styles |
|---|
| 320 | * |
|---|
| 321 | * Returns: |
|---|
| 322 | * {Object} propertyStyles hash |
|---|
| 323 | */ |
|---|
| 324 | addPropertyStyles: function(propertyStyles, symbolizer) { |
|---|
| 325 | var property; |
|---|
| 326 | for (var key in symbolizer) { |
|---|
| 327 | property = symbolizer[key]; |
|---|
| 328 | if (typeof property == "string" && |
|---|
| 329 | property.match(/\$\{\w+\}/)) { |
|---|
| 330 | propertyStyles[key] = true; |
|---|
| 331 | } |
|---|
| 332 | } |
|---|
| 333 | return propertyStyles; |
|---|
| 334 | }, |
|---|
| 335 | |
|---|
| 336 | /** |
|---|
| 337 | * APIMethod: addRules |
|---|
| 338 | * Adds rules to this style. |
|---|
| 339 | * |
|---|
| 340 | * Parameters: |
|---|
| 341 | * rules - {Array(<OpenLayers.Rule>)} |
|---|
| 342 | */ |
|---|
| 343 | addRules: function(rules) { |
|---|
| 344 | Array.prototype.push.apply(this.rules, rules); |
|---|
| 345 | this.propertyStyles = this.findPropertyStyles(); |
|---|
| 346 | }, |
|---|
| 347 | |
|---|
| 348 | /** |
|---|
| 349 | * APIMethod: setDefaultStyle |
|---|
| 350 | * Sets the default style for this style object. |
|---|
| 351 | * |
|---|
| 352 | * Parameters: |
|---|
| 353 | * style - {Object} Hash of style properties |
|---|
| 354 | */ |
|---|
| 355 | setDefaultStyle: function(style) { |
|---|
| 356 | this.defaultStyle = style; |
|---|
| 357 | this.propertyStyles = this.findPropertyStyles(); |
|---|
| 358 | }, |
|---|
| 359 | |
|---|
| 360 | /** |
|---|
| 361 | * Method: getSymbolizerPrefix |
|---|
| 362 | * Returns the correct symbolizer prefix according to the |
|---|
| 363 | * geometry type of the passed geometry |
|---|
| 364 | * |
|---|
| 365 | * Parameters: |
|---|
| 366 | * geometry {<OpenLayers.Geometry>} |
|---|
| 367 | * |
|---|
| 368 | * Returns: |
|---|
| 369 | * {String} key of the according symbolizer |
|---|
| 370 | */ |
|---|
| 371 | getSymbolizerPrefix: function(geometry) { |
|---|
| 372 | var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES; |
|---|
| 373 | for (var i=0, len=prefixes.length; i<len; i++) { |
|---|
| 374 | if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) { |
|---|
| 375 | return prefixes[i]; |
|---|
| 376 | } |
|---|
| 377 | } |
|---|
| 378 | }, |
|---|
| 379 | |
|---|
| 380 | /** |
|---|
| 381 | * APIMethod: clone |
|---|
| 382 | * Clones this style. |
|---|
| 383 | * |
|---|
| 384 | * Returns: |
|---|
| 385 | * {<OpenLayers.Style>} Clone of this style. |
|---|
| 386 | */ |
|---|
| 387 | clone: function() { |
|---|
| 388 | var options = OpenLayers.Util.extend({}, this); |
|---|
| 389 | // clone rules |
|---|
| 390 | if(this.rules) { |
|---|
| 391 | options.rules = []; |
|---|
| 392 | for(var i=0, len=this.rules.length; i<len; ++i) { |
|---|
| 393 | options.rules.push(this.rules[i].clone()); |
|---|
| 394 | } |
|---|
| 395 | } |
|---|
| 396 | // clone context |
|---|
| 397 | options.context = this.context && OpenLayers.Util.extend({}, this.context); |
|---|
| 398 | //clone default style |
|---|
| 399 | var defaultStyle = OpenLayers.Util.extend({}, this.defaultStyle); |
|---|
| 400 | return new OpenLayers.Style(defaultStyle, options); |
|---|
| 401 | }, |
|---|
| 402 | |
|---|
| 403 | CLASS_NAME: "OpenLayers.Style" |
|---|
| 404 | }); |
|---|
| 405 | |
|---|
| 406 | |
|---|
| 407 | /** |
|---|
| 408 | * Function: createLiteral |
|---|
| 409 | * converts a style value holding a combination of PropertyName and Literal |
|---|
| 410 | * into a Literal, taking the property values from the passed features. |
|---|
| 411 | * |
|---|
| 412 | * Parameters: |
|---|
| 413 | * value - {String} value to parse. If this string contains a construct like |
|---|
| 414 | * "foo ${bar}", then "foo " will be taken as literal, and "${bar}" |
|---|
| 415 | * will be replaced by the value of the "bar" attribute of the passed |
|---|
| 416 | * feature. |
|---|
| 417 | * context - {Object} context to take attribute values from |
|---|
| 418 | * feature - {<OpenLayers.Feature.Vector>} optional feature to pass to |
|---|
| 419 | * <OpenLayers.String.format> for evaluating functions in the |
|---|
| 420 | * context. |
|---|
| 421 | * property - {String} optional, name of the property for which the literal is |
|---|
| 422 | * being created for evaluating functions in the context. |
|---|
| 423 | * |
|---|
| 424 | * Returns: |
|---|
| 425 | * {String} the parsed value. In the example of the value parameter above, the |
|---|
| 426 | * result would be "foo valueOfBar", assuming that the passed feature has an |
|---|
| 427 | * attribute named "bar" with the value "valueOfBar". |
|---|
| 428 | */ |
|---|
| 429 | OpenLayers.Style.createLiteral = function(value, context, feature, property) { |
|---|
| 430 | if (typeof value == "string" && value.indexOf("${") != -1) { |
|---|
| 431 | value = OpenLayers.String.format(value, context, [feature, property]); |
|---|
| 432 | value = (isNaN(value) || !value) ? value : parseFloat(value); |
|---|
| 433 | } |
|---|
| 434 | return value; |
|---|
| 435 | }; |
|---|
| 436 | |
|---|
| 437 | /** |
|---|
| 438 | * Constant: OpenLayers.Style.SYMBOLIZER_PREFIXES |
|---|
| 439 | * {Array} prefixes of the sld symbolizers. These are the |
|---|
| 440 | * same as the main geometry types |
|---|
| 441 | */ |
|---|
| 442 | OpenLayers.Style.SYMBOLIZER_PREFIXES = ['Point', 'Line', 'Polygon', 'Text', |
|---|
| 443 | 'Raster']; |
|---|