Files
openlayers/lib/OpenLayers/Geometry/Collection.js
ahocevar cc2e19d789 added support for text labels. This also adds getCentroid methods to all
geometries. Thanks crschmidt for the great help with this patch, and 
thanks to camptocamp for the initial work on this and rcoup for creating 
the first patches. r=crschmidt (closes #1895)


git-svn-id: http://svn.openlayers.org/trunk/openlayers@9262 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
2009-04-10 16:05:26 +00:00

500 lines
16 KiB
JavaScript

/* Copyright (c) 2006-2008 MetaCarta, Inc., 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;
},
/**
* 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
*
* Returns:
* {<OpenLayers.Geometry.Point>} The centroid of the collection
*/
getCentroid: function() {
return this.components.length && this.components[0].getCentroid();
/*
var centroid;
for (var i=0, len=this.components.length; i<len; i++) {
if (!centroid) {
centroid = this.components[i].getCentroid();
} else {
centroid.resize(this.components[i].getCentroid(), 0.5);
}
}
return centroid;
*/
},
/**
* 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);
}
},
/**
* APIMethod: resize
* Resize a geometry relative to some origin. Use this method to apply
* a uniform scaling to a geometry.
*
* Parameters:
* scale - {Float} Factor by which to scale the geometry. A scale of 2
* doubles the size of the geometry in each dimension
* (lines, for example, will be twice as long, and polygons
* will have four times the area).
* origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
* ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
*
* Returns:
* {OpenLayers.Geometry} - The current geometry.
*/
resize: function(scale, origin, ratio) {
for(var i=0; i<this.components.length; ++i) {
this.components[i].resize(scale, origin, ratio);
}
return this;
},
/**
* APIMethod: distanceTo
* Calculate the closest distance between two geometries (on the x-y plane).
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} The target geometry.
* options - {Object} Optional properties for configuring the distance
* calculation.
*
* Valid options:
* details - {Boolean} Return details from the distance calculation.
* Default is false.
* edge - {Boolean} Calculate the distance from this geometry to the
* nearest edge of the target geometry. Default is true. If true,
* calling distanceTo from a geometry that is wholly contained within
* the target will result in a non-zero distance. If false, whenever
* geometries intersect, calling distanceTo will return 0. If false,
* details cannot be returned.
*
* Returns:
* {Number | Object} The distance between this geometry and the target.
* If details is true, the return will be an object with distance,
* x0, y0, x1, and y1 properties. The x0 and y0 properties represent
* the coordinates of the closest point on this geometry. The x1 and y1
* properties represent the coordinates of the closest point on the
* target geometry.
*/
distanceTo: function(geometry, options) {
var edge = !(options && options.edge === false);
var details = edge && options && options.details;
var result, best;
var min = Number.POSITIVE_INFINITY;
for(var i=0, len=this.components.length; i<len; ++i) {
result = this.components[i].distanceTo(geometry, options);
distance = details ? result.distance : result;
if(distance < min) {
min = distance;
best = result;
if(min == 0) {
break;
}
}
}
return best;
},
/**
* APIMethod: equals
* Determine whether another geometry is equivalent to this one. Geometries
* are considered equivalent if all components have the same coordinates.
*
* Parameters:
* geom - {<OpenLayers.Geometry>} The geometry to test.
*
* Returns:
* {Boolean} The supplied geometry is equivalent to this geometry.
*/
equals: function(geometry) {
var equivalent = true;
if(!geometry || !geometry.CLASS_NAME ||
(this.CLASS_NAME != geometry.CLASS_NAME)) {
equivalent = false;
} else if(!(geometry.components instanceof Array) ||
(geometry.components.length != this.components.length)) {
equivalent = false;
} else {
for(var i=0, len=this.components.length; i<len; ++i) {
if(!this.components[i].equals(geometry.components[i])) {
equivalent = false;
break;
}
}
}
return equivalent;
},
/**
* APIMethod: transform
* Reproject the components geometry from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>}
* dest - {<OpenLayers.Projection>}
*
* Returns:
* {<OpenLayers.Geometry>}
*/
transform: function(source, dest) {
if (source && dest) {
for (var i=0, len=this.components.length; i<len; i++) {
var component = this.components[i];
component.transform(source, dest);
}
this.bounds = null;
}
return this;
},
/**
* APIMethod: intersects
* Determine if the input geometry intersects this one.
*
* Parameters:
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
*
* Returns:
* {Boolean} The input geometry intersects this one.
*/
intersects: function(geometry) {
var intersect = false;
for(var i=0, len=this.components.length; i<len; ++ i) {
intersect = geometry.intersects(this.components[i]);
if(intersect) {
break;
}
}
return intersect;
},
/**
* APIMethod: getVertices
* Return a list of all points in this geometry.
*
* Parameters:
* nodes - {Boolean} For lines, only return vertices that are
* endpoints. If false, for lines, only vertices that are not
* endpoints will be returned. If not provided, all vertices will
* be returned.
*
* Returns:
* {Array} A list of all vertices in the geometry.
*/
getVertices: function(nodes) {
var vertices = [];
for(var i=0, len=this.components.length; i<len; ++i) {
Array.prototype.push.apply(
vertices, this.components[i].getVertices(nodes)
);
}
return vertices;
},
CLASS_NAME: "OpenLayers.Geometry.Collection"
});