[76] | 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']; |
---|