More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Format / KML.js
blob:a/labs/openlayers/lib/OpenLayers/Format/KML.js -> blob:b/labs/openlayers/lib/OpenLayers/Format/KML.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/Format/XML.js
  * @requires OpenLayers/Feature/Vector.js
  * @requires OpenLayers/Geometry/Point.js
  * @requires OpenLayers/Geometry/LineString.js
  * @requires OpenLayers/Geometry/Polygon.js
  * @requires OpenLayers/Geometry/Collection.js
  * @requires OpenLayers/Request/XMLHttpRequest.js
  * @requires OpenLayers/Console.js
  * @requires OpenLayers/Projection.js
  */
   
  /**
  * Class: OpenLayers.Format.KML
  * Read/Write KML. Create a new instance with the <OpenLayers.Format.KML>
  * constructor.
  *
  * Inherits from:
  * - <OpenLayers.Format.XML>
  */
  OpenLayers.Format.KML = OpenLayers.Class(OpenLayers.Format.XML, {
   
  /**
  * Property: namespaces
  * {Object} Mapping of namespace aliases to namespace URIs.
  */
  namespaces: {
  kml: "http://www.opengis.net/kml/2.2",
  gx: "http://www.google.com/kml/ext/2.2"
  },
   
  /**
  * APIProperty: kmlns
  * {String} KML Namespace to use. Defaults to 2.0 namespace.
  */
  kmlns: "http://earth.google.com/kml/2.0",
   
  /**
  * APIProperty: placemarksDesc
  * {String} Name of the placemarks. Default is "No description available".
  */
  placemarksDesc: "No description available",
   
  /**
  * APIProperty: foldersName
  * {String} Name of the folders. Default is "OpenLayers export".
  * If set to null, no name element will be created.
  */
  foldersName: "OpenLayers export",
   
  /**
  * APIProperty: foldersDesc
  * {String} Description of the folders. Default is "Exported on [date]."
  * If set to null, no description element will be created.
  */
  foldersDesc: "Exported on " + new Date(),
   
  /**
  * APIProperty: extractAttributes
  * {Boolean} Extract attributes from KML. Default is true.
  * Extracting styleUrls requires this to be set to true
  */
  extractAttributes: true,
   
  /**
  * Property: extractStyles
  * {Boolean} Extract styles from KML. Default is false.
  * Extracting styleUrls also requires extractAttributes to be
  * set to true
  */
  extractStyles: false,
   
  /**
  * APIProperty: extractTracks
  * {Boolean} Extract gx:Track elements from Placemark elements. Default
  * is false. If true, features will be generated for all points in
  * all gx:Track elements. Features will have a when (Date) attribute
  * based on when elements in the track. If tracks include angle
  * elements, features will have heading, tilt, and roll attributes.
  * If track point coordinates have three values, features will have
  * an altitude attribute with the third coordinate value.
  */
  extractTracks: false,
   
  /**
  * APIProperty: trackAttributes
  * {Array} If <extractTracks> is true, points within gx:Track elements will
  * be parsed as features with when, heading, tilt, and roll attributes.
  * Any additional attribute names can be provided in <trackAttributes>.
  */
  trackAttributes: null,
   
  /**
  * Property: internalns
  * {String} KML Namespace to use -- defaults to the namespace of the
  * Placemark node being parsed, but falls back to kmlns.
  */
  internalns: null,
   
  /**
  * Property: features
  * {Array} Array of features
  *
  */
  features: null,
   
  /**
  * Property: styles
  * {Object} Storage of style objects
  *
  */
  styles: null,
   
  /**
  * Property: styleBaseUrl
  * {String}
  */
  styleBaseUrl: "",
   
  /**
  * Property: fetched
  * {Object} Storage of KML URLs that have been fetched before
  * in order to prevent reloading them.
  */
  fetched: null,
   
  /**
  * APIProperty: maxDepth
  * {Integer} Maximum depth for recursive loading external KML URLs
  * Defaults to 0: do no external fetching
  */
  maxDepth: 0,
   
  /**
  * Constructor: OpenLayers.Format.KML
  * Create a new parser for KML.
  *
  * Parameters:
  * options - {Object} An optional object whose properties will be set on
  * this instance.
  */
  initialize: function(options) {
  // compile regular expressions once instead of every time they are used
  this.regExes = {
  trimSpace: (/^\s*|\s*$/g),
  removeSpace: (/\s*/g),
  splitSpace: (/\s+/),
  trimComma: (/\s*,\s*/g),
  kmlColor: (/(\w{2})(\w{2})(\w{2})(\w{2})/),
  kmlIconPalette: (/root:\/\/icons\/palette-(\d+)(\.\w+)/),
  straightBracket: (/\$\[(.*?)\]/g)
  };
  // KML coordinates are always in longlat WGS84
  this.externalProjection = new OpenLayers.Projection("EPSG:4326");
   
  OpenLayers.Format.XML.prototype.initialize.apply(this, [options]);
  },
   
  /**
  * APIMethod: read
  * Read data from a string, and return a list of features.
  *
  * Parameters:
  * data - {String} or {DOMElement} data to read/parse.
  *
  * Returns:
  * {Array(<OpenLayers.Feature.Vector>)} List of features.
  */
  read: function(data) {
  this.features = [];
  this.styles = {};
  this.fetched = {};
   
  // Set default options
  var options = {
  depth: 0,
  styleBaseUrl: this.styleBaseUrl
  };
   
  return this.parseData(data, options);
  },
   
  /**
  * Method: parseData
  * Read data from a string, and return a list of features.
  *
  * Parameters:
  * data - {String} or {DOMElement} data to read/parse.
  * options - {Object} Hash of options
  *
  * Returns:
  * {Array(<OpenLayers.Feature.Vector>)} List of features.
  */
  parseData: function(data, options) {
  if(typeof data == "string") {
  data = OpenLayers.Format.XML.prototype.read.apply(this, [data]);
  }
   
  // Loop throught the following node types in this order and
  // process the nodes found
  var types = ["Link", "NetworkLink", "Style", "StyleMap", "Placemark"];
  for(var i=0, len=types.length; i<len; ++i) {
  var type = types[i];
   
  var nodes = this.getElementsByTagNameNS(data, "*", type);
   
  // skip to next type if no nodes are found
  if(nodes.length == 0) {
  continue;
  }
   
  switch (type.toLowerCase()) {
   
  // Fetch external links
  case "link":
  case "networklink":
  this.parseLinks(nodes, options);
  break;
   
  // parse style information
  case "style":
  if (this.extractStyles) {
  this.parseStyles(nodes, options);
  }
  break;
  case "stylemap":
  if (this.extractStyles) {
  this.parseStyleMaps(nodes, options);
  }
  break;
   
  // parse features
  case "placemark":
  this.parseFeatures(nodes, options);
  break;
  }
  }
   
  return this.features;
  },
   
  /**
  * Method: parseLinks
  * Finds URLs of linked KML documents and fetches them
  *
  * Parameters:
  * nodes - {Array} of {DOMElement} data to read/parse.
  * options - {Object} Hash of options
  *
  */
  parseLinks: function(nodes, options) {
   
  // Fetch external links <NetworkLink> and <Link>
  // Don't do anything if we have reached our maximum depth for recursion
  if (options.depth >= this.maxDepth) {
  return false;
  }
   
  // increase depth
  var newOptions = OpenLayers.Util.extend({}, options);
  newOptions.depth++;
   
  for(var i=0, len=nodes.length; i<len; i++) {
  var href = this.parseProperty(nodes[i], "*", "href");
  if(href && !this.fetched[href]) {
  this.fetched[href] = true; // prevent reloading the same urls
  var data = this.fetchLink(href);
  if (data) {
  this.parseData(data, newOptions);
  }
  }
  }
   
  },
   
  /**
  * Method: fetchLink
  * Fetches a URL and returns the result
  *
  * Parameters:
  * href - {String} url to be fetched
  *
  */
  fetchLink: function(href) {
  var request = OpenLayers.Request.GET({url: href, async: false});
  if (request) {
  return request.responseText;
  }
  },
   
  /**
  * Method: parseStyles
  * Looks for <Style> nodes in the data and parses them
  * Also parses <StyleMap> nodes, but only uses the 'normal' key
  *
  * Parameters:
  * nodes - {Array} of {DOMElement} data to read/parse.
  * options - {Object} Hash of options
  *
  */
  parseStyles: function(nodes, options) {
  for(var i=0, len=nodes.length; i<len; i++) {
  var style = this.parseStyle(nodes[i]);
  if(style) {
  var styleName = (options.styleBaseUrl || "") + "#" + style.id;
   
  this.styles[styleName] = style;
  }
  }
  },
   
  /**
  * Method: parseKmlColor
  * Parses a kml color (in 'aabbggrr' format) and returns the corresponding
  * color and opacity or null if the color is invalid.
  *
  * Parameters:
  * kmlColor - {String} a kml formated color
  *
  * Returns:
  * {Object}
  */
  parseKmlColor: function(kmlColor) {
  var color = null;
  if (kmlColor) {
  var matches = kmlColor.match(this.regExes.kmlColor);
  if (matches) {
  color = {
  color: '#' + matches[4] + matches[3] + matches[2],
  opacity: parseInt(matches[1], 16) / 255
  };
  }
  }
  return color;
  },
   
  /**
  * Method: parseStyle
  * Parses the children of a <Style> node and builds the style hash
  * accordingly
  *
  * Parameters:
  * node - {DOMElement} <Style> node
  *
  */
  parseStyle: function(node) {
  var style = {};
   
  var types = ["LineStyle", "PolyStyle", "IconStyle", "BalloonStyle",
  "LabelStyle"];
  var type, nodeList, geometry, parser;
  for(var i=0, len=types.length; i<len; ++i) {
  type = types[i];
  styleTypeNode = this.getElementsByTagNameNS(node,
  "*", type)[0];
  if(!styleTypeNode) {
  continue;
  }
   
  // only deal with first geometry of this type
  switch (type.toLowerCase()) {
  case "linestyle":
  var kmlColor = this.parseProperty(styleTypeNode, "*", "color");
  var color = this.parseKmlColor(kmlColor);
  if (color) {
  style["strokeColor"] = color.color;
  style["strokeOpacity"] = color.opacity;
  }
   
  var width = this.parseProperty(styleTypeNode, "*", "width");
  if (width) {
  style["strokeWidth"] = width;
  }
  break;