More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Style.js
blob:a/labs/openlayers/lib/OpenLayers/Style.js -> blob:b/labs/openlayers/lib/OpenLayers/Style.js
  /* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for
  * full list of contributors). Published under the Clear BSD license.
  * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
  * full text of the license. */
   
   
  /**
  * @requires OpenLayers/Util.js
  * @requires OpenLayers/Feature/Vector.js
  */
   
  /**
  * Class: OpenLayers.Style
  * This class represents a UserStyle obtained
  * from a SLD, containing styling rules.
  */
  OpenLayers.Style = OpenLayers.Class({
   
  /**
  * Property: id
  * {String} A unique id for this session.
  */
  id: null,
   
  /**
  * APIProperty: name
  * {String}
  */
  name: null,
   
  /**
  * Property: title
  * {String} Title of this style (set if included in SLD)
  */
  title: null,
   
  /**
  * Property: description
  * {String} Description of this style (set if abstract is included in SLD)
  */
  description: null,
   
  /**
  * APIProperty: layerName
  * {<String>} name of the layer that this style belongs to, usually
  * according to the NamedLayer attribute of an SLD document.
  */
  layerName: null,
   
  /**
  * APIProperty: isDefault
  * {Boolean}
  */
  isDefault: false,
   
  /**
  * Property: rules
  * {Array(<OpenLayers.Rule>)}
  */
  rules: null,
   
  /**
  * Property: context
  * {Object} An optional object with properties that symbolizers' property
  * values should be evaluated against. If no context is specified,
  * feature.attributes will be used
  */
  context: null,
   
  /**
  * Property: defaultStyle
  * {Object} hash of style properties to use as default for merging
  * rule-based style symbolizers onto. If no rules are defined,
  * createSymbolizer will return this style. If <defaultsPerSymbolizer> is set to
  * true, the defaultStyle will only be taken into account if there are
  * rules defined.
  */
  defaultStyle: null,
   
  /**
  * Property: defaultsPerSymbolizer
  * {Boolean} If set to true, the <defaultStyle> will extend the symbolizer
  * of every rule. Properties of the <defaultStyle> will also be used to set
  * missing symbolizer properties if the symbolizer has stroke, fill or
  * graphic set to true. Default is false.
  */
  defaultsPerSymbolizer: false,
   
  /**
  * Property: propertyStyles
  * {Hash of Boolean} cache of style properties that need to be parsed for
  * propertyNames. Property names are keys, values won't be used.
  */
  propertyStyles: null,
   
   
  /**
  * Constructor: OpenLayers.Style
  * Creates a UserStyle.
  *
  * Parameters:
  * style - {Object} Optional hash of style properties that will be
  * used as default style for this style object. This style
  * applies if no rules are specified. Symbolizers defined in
  * rules will extend this default style.
  * options - {Object} An optional object with properties to set on the
  * style.
  *
  * Valid options:
  * rules - {Array(<OpenLayers.Rule>)} List of rules to be added to the
  * style.
  *
  * Return:
  * {<OpenLayers.Style>}
  */
  initialize: function(style, options) {
   
  OpenLayers.Util.extend(this, options);
  this.rules = [];
  if(options && options.rules) {
  this.addRules(options.rules);
  }
   
  // use the default style from OpenLayers.Feature.Vector if no style
  // was given in the constructor
  this.setDefaultStyle(style ||
  OpenLayers.Feature.Vector.style["default"]);
   
  this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_");
  },
   
  /**
  * APIMethod: destroy
  * nullify references to prevent circular references and memory leaks
  */
  destroy: function() {
  for (var i=0, len=this.rules.length; i<len; i++) {
  this.rules[i].destroy();
  this.rules[i] = null;
  }
  this.rules = null;
  this.defaultStyle = null;
  },
   
  /**
  * Method: createSymbolizer
  * creates a style by applying all feature-dependent rules to the base
  * style.
  *
  * Parameters:
  * feature - {<OpenLayers.Feature>} feature to evaluate rules for
  *
  * Returns:
  * {Object} symbolizer hash
  */
  createSymbolizer: function(feature) {
  var style = this.defaultsPerSymbolizer ? {} : this.createLiterals(
  OpenLayers.Util.extend({}, this.defaultStyle), feature);
   
  var rules = this.rules;
   
  var rule, context;
  var elseRules = [];
  var appliedRules = false;
  for(var i=0, len=rules.length; i<len; i++) {
  rule = rules[i];
  // does the rule apply?
  var applies = rule.evaluate(feature);
   
  if(applies) {
  if(rule instanceof OpenLayers.Rule && rule.elseFilter) {
  elseRules.push(rule);
  } else {
  appliedRules = true;
  this.applySymbolizer(rule, style, feature);
  }
  }
  }
   
  // if no other rules apply, apply the rules with else filters
  if(appliedRules == false && elseRules.length > 0) {
  appliedRules = true;
  for(var i=0, len=elseRules.length; i<len; i++) {
  this.applySymbolizer(elseRules[i], style, feature);
  }
  }
   
  // don't display if there were rules but none applied
  if(rules.length > 0 && appliedRules == false) {
  style.display = "none";
  }
   
  return style;
  },
   
  /**
  * Method: applySymbolizer
  *
  * Parameters:
  * rule - {OpenLayers.Rule}
  * style - {Object}
  * feature - {<OpenLayer.Feature.Vector>}
  *
  * Returns:
  * {Object} A style with new symbolizer applied.
  */
  applySymbolizer: function(rule, style, feature) {
  var symbolizerPrefix = feature.geometry ?
  this.getSymbolizerPrefix(feature.geometry) :
  OpenLayers.Style.SYMBOLIZER_PREFIXES[0];
   
  var symbolizer = rule.symbolizer[symbolizerPrefix] || rule.symbolizer;
   
  if(this.defaultsPerSymbolizer === true) {
  var defaults = this.defaultStyle;
  OpenLayers.Util.applyDefaults(symbolizer, {
  pointRadius: defaults.pointRadius
  });
  if(symbolizer.stroke === true || symbolizer.graphic === true) {
  OpenLayers.Util.applyDefaults(symbolizer, {
  strokeWidth: defaults.strokeWidth,
  strokeColor: defaults.strokeColor,
  strokeOpacity: defaults.strokeOpacity,
  strokeDashstyle: defaults.strokeDashstyle,
  strokeLinecap: defaults.strokeLinecap
  });
  }
  if(symbolizer.fill === true || symbolizer.graphic === true) {
  OpenLayers.Util.applyDefaults(symbolizer, {
  fillColor: defaults.fillColor,
  fillOpacity: defaults.fillOpacity
  });
  }
  if(symbolizer.graphic === true) {
  OpenLayers.Util.applyDefaults(symbolizer, {
  pointRadius: this.defaultStyle.pointRadius,
  externalGraphic: this.defaultStyle.externalGraphic,
  graphicName: this.defaultStyle.graphicName,
  graphicOpacity: this.defaultStyle.graphicOpacity,
  graphicWidth: this.defaultStyle.graphicWidth,
  graphicHeight: this.defaultStyle.graphicHeight,
  graphicXOffset: this.defaultStyle.graphicXOffset,
  graphicYOffset: this.defaultStyle.graphicYOffset
  });
  }
  }
   
  // merge the style with the current style
  return this.createLiterals(
  OpenLayers.Util.extend(style, symbolizer), feature);
  },
   
  /**
  * Method: createLiterals
  * creates literals for all style properties that have an entry in
  * <this.propertyStyles>.
  *
  * Parameters:
  * style - {Object} style to create literals for. Will be modified
  * inline.
  * feature - {Object}
  *
  * Returns:
  * {Object} the modified style
  */
  createLiterals: function(style, feature) {
  var context = OpenLayers.Util.extend({}, feature.attributes || feature.data);
  OpenLayers.Util.extend(context, this.context);
   
  for (var i in this.propertyStyles) {
  style[i] = OpenLayers.Style.createLiteral(style[i], context, feature, i);
  }
  return style;
  },
   
  /**
  * Method: findPropertyStyles
  * Looks into all rules for this style and the defaultStyle to collect
  * all the style hash property names containing ${...} strings that have
  * to be replaced using the createLiteral method before returning them.
  *
  * Returns:
  * {Object} hash of property names that need createLiteral parsing. The
  * name of the property is the key, and the value is true;
  */
  findPropertyStyles: function() {
  var propertyStyles = {};
   
  // check the default style
  var style = this.defaultStyle;
  this.addPropertyStyles(propertyStyles, style);
   
  // walk through all rules to check for properties in their symbolizer
  var rules = this.rules;
  var symbolizer, value;
  for (var i=0, len=rules.length; i<len; i++) {
  symbolizer = rules[i].symbolizer;
  for (var key in symbolizer) {
  value = symbolizer[key];
  if (typeof value == "object") {
  // symbolizer key is "Point", "Line" or "Polygon"
  this.addPropertyStyles(propertyStyles, value);
  } else {
  // symbolizer is a hash of style properties
  this.addPropertyStyles(propertyStyles, symbolizer);
  break;
  }
  }
  }
  return propertyStyles;
  },
   
  /**
  * Method: addPropertyStyles
  *
  * Parameters:
  * propertyStyles - {Object} hash to add new property styles to. Will be
  * modified inline
  * symbolizer - {Object} search this symbolizer for property styles
  *
  * Returns:
  * {Object} propertyStyles hash
  */
  addPropertyStyles: function(propertyStyles, symbolizer) {
  var property;
  for (var key in symbolizer) {
  property = symbolizer[key];
  if (typeof property == "string" &&
  property.match(/\$\{\w+\}/)) {
  propertyStyles[key] = true;
  }
  }
  return propertyStyles;
  },
   
  /**
  * APIMethod: addRules
  * Adds rules to this style.
  *
  * Parameters:
  * rules - {Array(<OpenLayers.Rule>)}
  */
  addRules: function(rules) {
  Array.prototype.push.apply(this.rules, rules);
  this.propertyStyles = this.findPropertyStyles();
  },
   
  /**
  * APIMethod: setDefaultStyle
  * Sets the default style for this style object.
  *
  * Parameters:
  * style - {Object} Hash of style properties
  */
  setDefaultStyle: function(style) {
  this.defaultStyle = style;
  this.propertyStyles = this.findPropertyStyles();
  },
   
  /**
  * Method: getSymbolizerPrefix
  * Returns the correct symbolizer prefix according to the
  * geometry type of the passed geometry
  *
  * Parameters:
  * geometry {<OpenLayers.Geometry>}
  *
  * Returns:
  * {String} key of the according symbolizer
  */
  getSymbolizerPrefix: function(geometry) {
  var prefixes = OpenLayers.Style.SYMBOLIZER_PREFIXES;
  for (var i=0, len=prefixes.length; i<len; i++) {
  if (geometry.CLASS_NAME.indexOf(prefixes[i]) != -1) {
  return prefixes[i];
  }
  }
  },
   
  /**