More trip planner testing with colors
[busui.git] / labs / openlayers / lib / OpenLayers / Geometry / Collection.js
blob:a/labs/openlayers/lib/OpenLayers/Geometry/Collection.js -> blob:b/labs/openlayers/lib/OpenLayers/Geometry/Collection.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/Geometry.js
  */
   
  /**
  * Class: OpenLayers.Geometry.Collection
  * A Collection is exactly what it sounds like: A collection of different
  * Geometries. These are stored in the local parameter <components> (which
  * can be passed as a parameter to the constructor).
  *
  * As new geometries are added to the collection, they are NOT cloned.
  * When removing geometries, they need to be specified by reference (ie you
  * have to pass in the *exact* geometry to be removed).
  *
  * The <getArea> and <getLength> functions here merely iterate through
  * the components, summing their respective areas and lengths.
  *
  * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
  *
  * Inerhits from:
  * - <OpenLayers.Geometry>
  */
  OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
   
  /**
  * APIProperty: components
  * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
  */
  components: null,
   
  /**
  * Property: componentTypes
  * {Array(String)} An array of class names representing the types of
  * components that the collection can include. A null value means the
  * component types are not restricted.
  */
  componentTypes: null,
   
  /**
  * Constructor: OpenLayers.Geometry.Collection
  * Creates a Geometry Collection -- a list of geoms.
  *
  * Parameters:
  * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
  *
  */
  initialize: function (components) {
  OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
  this.components = [];
  if (components != null) {
  this.addComponents(components);
  }
  },
   
  /**
  * APIMethod: destroy
  * Destroy this geometry.
  */
  destroy: function () {
  this.components.length = 0;
  this.components = null;
  OpenLayers.Geometry.prototype.destroy.apply(this, arguments);
  },
   
  /**
  * APIMethod: clone
  * Clone this geometry.
  *
  * Returns:
  * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
  */
  clone: function() {
  var geometry = eval("new " + this.CLASS_NAME + "()");
  for(var i=0, len=this.components.length; i<len; i++) {
  geometry.addComponent(this.components[i].clone());
  }
   
  // catch any randomly tagged-on properties
  OpenLayers.Util.applyDefaults(geometry, this);
   
  return geometry;
  },
   
  /**
  * Method: getComponentsString
  * Get a string representing the components for this collection
  *
  * Returns:
  * {String} A string representation of the components of this geometry
  */
  getComponentsString: function(){
  var strings = [];
  for(var i=0, len=this.components.length; i<len; i++) {
  strings.push(this.components[i].toShortString());
  }
  return strings.join(",");
  },
   
  /**
  * APIMethod: calculateBounds
  * Recalculate the bounds by iterating through the components and
  * calling calling extendBounds() on each item.
  */
  calculateBounds: function() {
  this.bounds = null;
  if ( this.components && this.components.length > 0) {
  this.setBounds(this.components[0].getBounds());
  for (var i=1, len=this.components.length; i<len; i++) {
  this.extendBounds(this.components[i].getBounds());
  }
  }
  },
   
  /**
  * APIMethod: addComponents
  * Add components to this geometry.
  *
  * Parameters:
  * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
  */
  addComponents: function(components){
  if(!(components instanceof Array)) {
  components = [components];
  }
  for(var i=0, len=components.length; i<len; i++) {
  this.addComponent(components[i]);
  }
  },
   
  /**
  * Method: addComponent
  * Add a new component (geometry) to the collection. If this.componentTypes
  * is set, then the component class name must be in the componentTypes array.
  *
  * The bounds cache is reset.
  *
  * Parameters:
  * component - {<OpenLayers.Geometry>} A geometry to add
  * index - {int} Optional index into the array to insert the component
  *
  * Returns:
  * {Boolean} The component geometry was successfully added
  */
  addComponent: function(component, index) {
  var added = false;
  if(component) {
  if(this.componentTypes == null ||
  (OpenLayers.Util.indexOf(this.componentTypes,
  component.CLASS_NAME) > -1)) {
   
  if(index != null && (index < this.components.length)) {
  var components1 = this.components.slice(0, index);
  var components2 = this.components.slice(index,
  this.components.length);
  components1.push(component);
  this.components = components1.concat(components2);
  } else {
  this.components.push(component);
  }
  component.parent = this;
  this.clearBounds();
  added = true;
  }
  }
  return added;
  },
   
  /**
  * APIMethod: removeComponents
  * Remove components from this geometry.
  *
  * Parameters:
  * components - {Array(<OpenLayers.Geometry>)} The components to be removed
  */
  removeComponents: function(components) {
  if(!(components instanceof Array)) {
  components = [components];
  }
  for(var i=components.length-1; i>=0; --i) {
  this.removeComponent(components[i]);
  }
  },
   
  /**
  * Method: removeComponent
  * Remove a component from this geometry.
  *
  * Parameters:
  * component - {<OpenLayers.Geometry>}
  */
  removeComponent: function(component) {
   
  OpenLayers.Util.removeItem(this.components, component);
   
  // clearBounds() so that it gets recalculated on the next call
  // to this.getBounds();
  this.clearBounds();
  },
   
  /**
  * APIMethod: getLength
  * Calculate the length of this geometry
  *
  * Returns:
  * {Float} The length of the geometry
  */
  getLength: function() {
  var length = 0.0;
  for (var i=0, len=this.components.length; i<len; i++) {
  length += this.components[i].getLength();
  }
  return length;
  },
   
  /**
  * APIMethod: getArea
  * Calculate the area of this geometry. Note how this function is overridden
  * in <OpenLayers.Geometry.Polygon>.
  *
  * Returns:
  * {Float} The area of the collection by summing its parts
  */
  getArea: function() {
  var area = 0.0;
  for (var i=0, len=this.components.length; i<len; i++) {
  area += this.components[i].getArea();
  }
  return area;
  },
   
  /**
  * APIMethod: getGeodesicArea
  * Calculate the approximate area of the polygon were it projected onto
  * the earth.
  *
  * Parameters:
  * projection - {<OpenLayers.Projection>} The spatial reference system
  * for the geometry coordinates. If not provided, Geographic/WGS84 is
  * assumed.
  *
  * Reference:
  * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
  * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
  * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
  *
  * Returns:
  * {float} The approximate geodesic area of the geometry in square meters.
  */
  getGeodesicArea: function(projection) {
  var area = 0.0;
  for(var i=0, len=this.components.length; i<len; i++) {
  area += this.components[i].getGeodesicArea(projection);
  }
  return area;
  },
   
  /**
  * APIMethod: getCentroid
  *
  * Compute the centroid for this geometry collection.
  *
  * Parameters:
  * weighted - {Boolean} Perform the getCentroid computation recursively,
  * returning an area weighted average of all geometries in this collection.
  *
  * Returns:
  * {<OpenLayers.Geometry.Point>} The centroid of the collection
  */
  getCentroid: function(weighted) {
  if (!weighted) {
  return this.components.length && this.components[0].getCentroid();
  }
  var len = this.components.length;
  if (!len) {
  return false;
  }
   
  var areas = [];
  var centroids = [];
  var areaSum = 0;
  var minArea = Number.MAX_VALUE;
  var component;
  for (var i=0; i<len; ++i) {
  component = this.components[i];
  var area = component.getArea();
  var centroid = component.getCentroid(true);
  if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
  continue;
  }
  areas.push(area);
  areaSum += area;
  minArea = (area < minArea && area > 0) ? area : minArea;
  centroids.push(centroid);
  }
  len = areas.length;
  if (areaSum === 0) {
  // all the components in this collection have 0 area
  // probably a collection of points -- weight all the points the same
  for (var i=0; i<len; ++i) {
  areas[i] = 1;
  }
  areaSum = areas.length;
  } else {
  // normalize all the areas where the smallest area will get
  // a value of 1
  for (var i=0; i<len; ++i) {
  areas[i] /= minArea;
  }
  areaSum /= minArea;
  }
   
  var xSum = 0, ySum = 0, centroid, area;
  for (var i=0; i<len; ++i) {
  centroid = centroids[i];
  area = areas[i];
  xSum += centroid.x * area;
  ySum += centroid.y * area;
  }
   
  return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
  },
   
  /**
  * APIMethod: getGeodesicLength
  * Calculate the approximate length of the geometry were it projected onto
  * the earth.
  *
  * projection - {<OpenLayers.Projection>} The spatial reference system
  * for the geometry coordinates. If not provided, Geographic/WGS84 is
  * assumed.
  *
  * Returns:
  * {Float} The appoximate geodesic length of the geometry in meters.
  */
  getGeodesicLength: function(projection) {
  var length = 0.0;
  for(var i=0, len=this.components.length; i<len; i++) {
  length += this.components[i].getGeodesicLength(projection);
  }
  return length;
  },
   
  /**
  * APIMethod: move
  * Moves a geometry by the given displacement along positive x and y axes.
  * This modifies the position of the geometry and clears the cached
  * bounds.
  *
  * Parameters:
  * x - {Float} Distance to move geometry in positive x direction.
  * y - {Float} Distance to move geometry in positive y direction.
  */
  move: function(x, y) {
  for(var i=0, len=this.components.length; i<len; i++) {
  this.components[i].move(x, y);
  }
  },
   
  /**
  * APIMethod: rotate
  * Rotate a geometry around some origin
  *
  * Parameters:
  * angle - {Float} Rotation angle in degrees (measured counterclockwise
  * from the positive x-axis)
  * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
  */
  rotate: function(angle, origin) {
  for(var i=0, len=this.components.length; i<len; ++i) {
  this.components[i].rotate(angle, origin);
  }
  },