Style.js 14 KB

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