diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Geometry/Collection.js')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Geometry/Collection.js | 563 |
1 files changed, 563 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Geometry/Collection.js b/misc/openlayers/lib/OpenLayers/Geometry/Collection.js new file mode 100644 index 0000000..f76cc85 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Geometry/Collection.js @@ -0,0 +1,563 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository 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. + * + * Inherits 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; + var bounds = new OpenLayers.Bounds(); + var components = this.components; + if (components) { + for (var i=0, len=components.length; i<len; i++) { + bounds.extend(components[i].getBounds()); + } + } + // to preserve old behavior, we only set bounds if non-null + // in the future, we could add bounds.isEmpty() + if (bounds.left != null && bounds.bottom != null && + bounds.right != null && bounds.top != null) { + this.setBounds(bounds); + } + }, + + /** + * APIMethod: addComponents + * Add components to this geometry. + * + * Parameters: + * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add + */ + addComponents: function(components){ + if(!(OpenLayers.Util.isArray(components))) { + 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 + * + * Returns: + * {Boolean} A component was removed. + */ + removeComponents: function(components) { + var removed = false; + + if(!(OpenLayers.Util.isArray(components))) { + components = [components]; + } + for(var i=components.length-1; i>=0; --i) { + removed = this.removeComponent(components[i]) || removed; + } + return removed; + }, + + /** + * Method: removeComponent + * Remove a component from this geometry. + * + * Parameters: + * component - {<OpenLayers.Geometry>} + * + * Returns: + * {Boolean} The component was removed. + */ + removeComponent: function(component) { + + OpenLayers.Util.removeItem(this.components, component); + + // clearBounds() so that it gets recalculated on the next call + // to this.getBounds(); + this.clearBounds(); + return true; + }, + + /** + * 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); + } + }, + + /** + * 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, distance; + 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: + * geometry - {<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(!(OpenLayers.Util.isArray(geometry.components)) || + (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" +}); |