More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Format / OSM.js
blob:a/labs/openlayers/lib/OpenLayers/Format/OSM.js -> blob:b/labs/openlayers/lib/OpenLayers/Format/OSM.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/Projection.js
  */
   
  /**
  * Class: OpenLayers.Format.OSM
  * OSM parser. Create a new instance with the
  * <OpenLayers.Format.OSM> constructor.
  *
  * Inherits from:
  * - <OpenLayers.Format.XML>
  */
  OpenLayers.Format.OSM = OpenLayers.Class(OpenLayers.Format.XML, {
   
  /**
  * APIProperty: checkTags
  * {Boolean} Should tags be checked to determine whether something
  * should be treated as a seperate node. Will slow down parsing.
  * Default is false.
  */
  checkTags: false,
   
  /**
  * Property: interestingTagsExclude
  * {Array} List of tags to exclude from 'interesting' checks on nodes.
  * Must be set when creating the format. Will only be used if checkTags
  * is set.
  */
  interestingTagsExclude: null,
   
  /**
  * APIProperty: areaTags
  * {Array} List of tags indicating that something is an area.
  * Must be set when creating the format. Will only be used if
  * checkTags is true.
  */
  areaTags: null,
   
  /**
  * Constructor: OpenLayers.Format.OSM
  * Create a new parser for OSM.
  *
  * Parameters:
  * options - {Object} An optional object whose properties will be set on
  * this instance.
  */
  initialize: function(options) {
  var layer_defaults = {
  'interestingTagsExclude': ['source', 'source_ref',
  'source:ref', 'history', 'attribution', 'created_by'],
  'areaTags': ['area', 'building', 'leisure', 'tourism', 'ruins',
  'historic', 'landuse', 'military', 'natural', 'sport']
  };
   
  layer_defaults = OpenLayers.Util.extend(layer_defaults, options);
   
  var interesting = {};
  for (var i = 0; i < layer_defaults.interestingTagsExclude.length; i++) {
  interesting[layer_defaults.interestingTagsExclude[i]] = true;
  }
  layer_defaults.interestingTagsExclude = interesting;
   
  var area = {};
  for (var i = 0; i < layer_defaults.areaTags.length; i++) {
  area[layer_defaults.areaTags[i]] = true;
  }
  layer_defaults.areaTags = area;
   
  // OSM coordinates are always in longlat WGS84
  this.externalProjection = new OpenLayers.Projection("EPSG:4326");
   
  OpenLayers.Format.XML.prototype.initialize.apply(this, [layer_defaults]);
  },
   
  /**
  * APIMethod: read
  * Return a list of features from a OSM doc
   
  * Parameters:
  * data - {Element}
  *
  * Returns:
  * An Array of <OpenLayers.Feature.Vector>s
  */
  read: function(doc) {
  if (typeof doc == "string") {
  doc = OpenLayers.Format.XML.prototype.read.apply(this, [doc]);
  }
   
  var nodes = this.getNodes(doc);
  var ways = this.getWays(doc);
   
  // Geoms will contain at least ways.length entries.
  var feat_list = new Array(ways.length);
   
  for (var i = 0; i < ways.length; i++) {
  // We know the minimal of this one ahead of time. (Could be -1
  // due to areas/polygons)
  var point_list = new Array(ways[i].nodes.length);
   
  var poly = this.isWayArea(ways[i]) ? 1 : 0;
  for (var j = 0; j < ways[i].nodes.length; j++) {
  var node = nodes[ways[i].nodes[j]];
   
  var point = new OpenLayers.Geometry.Point(node.lon, node.lat);
   
  // Since OSM is topological, we stash the node ID internally.
  point.osm_id = parseInt(ways[i].nodes[j]);
  point_list[j] = point;
   
  // We don't display nodes if they're used inside other
  // elements.
  node.used = true;
  }
  var geometry = null;
  if (poly) {
  geometry = new OpenLayers.Geometry.Polygon(
  new OpenLayers.Geometry.LinearRing(point_list));
  } else {
  geometry = new OpenLayers.Geometry.LineString(point_list);
  }
  if (this.internalProjection && this.externalProjection) {
  geometry.transform(this.externalProjection,
  this.internalProjection);
  }
  var feat = new OpenLayers.Feature.Vector(geometry,
  ways[i].tags);
  feat.osm_id = parseInt(ways[i].id);
  feat.fid = "way." + feat.osm_id;
  feat_list[i] = feat;
  }
  for (var node_id in nodes) {
  var node = nodes[node_id];
  if (!node.used || this.checkTags) {
  var tags = null;
   
  if (this.checkTags) {
  var result = this.getTags(node.node, true);
  if (node.used && !result[1]) {
  continue;
  }
  tags = result[0];
  } else {
  tags = this.getTags(node.node);
  }
   
  var feat = new OpenLayers.Feature.Vector(
  new OpenLayers.Geometry.Point(node['lon'], node['lat']),
  tags);
  if (this.internalProjection && this.externalProjection) {
  feat.geometry.transform(this.externalProjection,
  this.internalProjection);
  }
  feat.osm_id = parseInt(node_id);
  feat.fid = "node." + feat.osm_id;
  feat_list.push(feat);
  }
  // Memory cleanup
  node.node = null;
  }
  return feat_list;
  },
   
  /**
  * Method: getNodes
  * Return the node items from a doc.
  *
  * Parameters:
  * node - {DOMElement} node to parse tags from
  */
  getNodes: function(doc) {
  var node_list = doc.getElementsByTagName("node");
  var nodes = {};
  for (var i = 0; i < node_list.length; i++) {
  var node = node_list[i];
  var id = node.getAttribute("id");
  nodes[id] = {
  'lat': node.getAttribute("lat"),
  'lon': node.getAttribute("lon"),
  'node': node
  };
  }
  return nodes;
  },
   
  /**
  * Method: getWays
  * Return the way items from a doc.
  *
  * Parameters:
  * node - {DOMElement} node to parse tags from
  */
  getWays: function(doc) {
  var way_list = doc.getElementsByTagName("way");
  var return_ways = [];
  for (var i = 0; i < way_list.length; i++) {
  var way = way_list[i];
  var way_object = {
  id: way.getAttribute("id")
  };
   
  way_object.tags = this.getTags(way);
   
  var node_list = way.getElementsByTagName("nd");
   
  way_object.nodes = new Array(node_list.length);
   
  for (var j = 0; j < node_list.length; j++) {
  way_object.nodes[j] = node_list[j].getAttribute("ref");
  }
  return_ways.push(way_object);
  }
  return return_ways;
   
  },
   
  /**
  * Method: getTags
  * Return the tags list attached to a specific DOM element.
  *
  * Parameters:
  * node - {DOMElement} node to parse tags from
  * interesting_tags - {Boolean} whether the return from this function should
  * return a boolean indicating that it has 'interesting tags' --
  * tags like attribution and source are ignored. (To change the list
  * of tags, see interestingTagsExclude)
  *
  * Returns:
  * tags - {Object} hash of tags
  * interesting - {Boolean} if interesting_tags is passed, returns
  * whether there are any interesting tags on this element.
  */
  getTags: function(dom_node, interesting_tags) {
  var tag_list = dom_node.getElementsByTagName("tag");
  var tags = {};
  var interesting = false;
  for (var j = 0; j < tag_list.length; j++) {
  var key = tag_list[j].getAttribute("k");
  tags[key] = tag_list[j].getAttribute("v");
  if (interesting_tags) {
  if (!this.interestingTagsExclude[key]) {
  interesting = true;
  }
  }
  }
  return interesting_tags ? [tags, interesting] : tags;
  },
   
  /**
  * Method: isWayArea
  * Given a way object from getWays, check whether the tags and geometry
  * indicate something is an area.
  *
  * Returns:
  * {Boolean}
  */
  isWayArea: function(way) {
  var poly_shaped = false;
  var poly_tags = false;
   
  if (way.nodes[0] == way.nodes[way.nodes.length - 1]) {
  poly_shaped = true;
  }
  if (this.checkTags) {
  for(var key in way.tags) {
  if (this.areaTags[key]) {
  poly_tags = true;
  break;
  }
  }
  }
  return poly_shaped && (this.checkTags ? poly_tags : true);
  },
   
  /**
  * APIMethod: write
  * Takes a list of features, returns a serialized OSM format file for use
  * in tools like JOSM.
  *
  * Parameters:
  * features - {Array(<OpenLayers.Feature.Vector>)}
  */
  write: function(features) {
  if (!(features instanceof Array)) {
  features = [features];
  }
   
  this.osm_id = 1;
  this.created_nodes = {};
  var root_node = this.createElementNS(null, "osm");
  root_node.setAttribute("version", "0.5");
  root_node.setAttribute("generator", "OpenLayers "+ OpenLayers.VERSION_NUMBER);
   
  // Loop backwards, because the deserializer puts nodes last, and
  // we want them first if possible
  for(var i = features.length - 1; i >= 0; i--) {
  var nodes = this.createFeatureNodes(features[i]);
  for (var j = 0; j < nodes.length; j++) {
  root_node.appendChild(nodes[j]);
  }
  }
  return OpenLayers.Format.XML.prototype.write.apply(this, [root_node]);
  },
   
  /**
  * Method: createFeatureNodes
  * Takes a feature, returns a list of nodes from size 0->n.
  * Will include all pieces of the serialization that are required which
  * have not already been created. Calls out to createXML based on geometry
  * type.
  *
  * Parameters:
  * feature - {<OpenLayers.Feature.Vector>}
  */
  createFeatureNodes: function(feature) {
  var nodes = [];
  var className = feature.geometry.CLASS_NAME;
  var type = className.substring(className.lastIndexOf(".") + 1);
  type = type.toLowerCase();
  var builder = this.createXML[type];
  if (builder) {
  nodes = builder.apply(this, [feature]);
  }
  return nodes;
  },
   
  /**
  * Method: createXML
  * Takes a feature, returns a list of nodes from size 0->n.
  * Will include all pieces of the serialization that are required which
  * have not already been created.
  *
  * Parameters:
  * feature - {<OpenLayers.Feature.Vector>}
  */
  createXML: {
  'point': function(point) {
  var id = null;
  var geometry = point.geometry ? point.geometry : point;
  var already_exists = false; // We don't return anything if the node
  // has already been created
  if (point.osm_id) {
  id = point.osm_id;
  if (this.created_nodes[id]) {
  already_exists = true;
  }
  } else {
  id = -this.osm_id;
  this.osm_id++;
  }
  if (already_exists) {
  node = this.created_nodes[id];
  } else {
  var node = this.createElementNS(null, "node");
  }
  this.created_nodes[id] = node;
  node.setAttribute("id", id);
  node.setAttribute("lon", geometry.x);
  node.setAttribute("lat", geometry.y);
  if (point.attributes) {
  this.serializeTags(point, node);