--- a/labs/openlayers/lib/OpenLayers/Geometry.js +++ b/labs/openlayers/lib/OpenLayers/Geometry.js @@ -1,1 +1,457 @@ - +/* 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/WKT.js + * @requires OpenLayers/Feature/Vector.js + */ + +/** + * Class: OpenLayers.Geometry + * A Geometry is a description of a geographic object. Create an instance of + * this class with the constructor. This is a base class, + * typical geometry types are described by subclasses of this class. + */ +OpenLayers.Geometry = OpenLayers.Class({ + + /** + * Property: id + * {String} A unique identifier for this geometry. + */ + id: null, + + /** + * Property: parent + * {}This is set when a Geometry is added as component + * of another geometry + */ + parent: null, + + /** + * Property: bounds + * {} The bounds of this geometry + */ + bounds: null, + + /** + * Constructor: OpenLayers.Geometry + * Creates a geometry object. + */ + initialize: function() { + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME+ "_"); + }, + + /** + * Method: destroy + * Destroy this geometry. + */ + destroy: function() { + this.id = null; + this.bounds = null; + }, + + /** + * APIMethod: clone + * Create a clone of this geometry. Does not set any non-standard + * properties of the cloned geometry. + * + * Returns: + * {} An exact clone of this geometry. + */ + clone: function() { + return new OpenLayers.Geometry(); + }, + + /** + * Set the bounds for this Geometry. + * + * Parameters: + * object - {} + */ + setBounds: function(bounds) { + if (bounds) { + this.bounds = bounds.clone(); + } + }, + + /** + * Method: clearBounds + * Nullify this components bounds and that of its parent as well. + */ + clearBounds: function() { + this.bounds = null; + if (this.parent) { + this.parent.clearBounds(); + } + }, + + /** + * Method: extendBounds + * Extend the existing bounds to include the new bounds. + * If geometry's bounds is not yet set, then set a new Bounds. + * + * Parameters: + * newBounds - {} + */ + extendBounds: function(newBounds){ + var bounds = this.getBounds(); + if (!bounds) { + this.setBounds(newBounds); + } else { + this.bounds.extend(newBounds); + } + }, + + /** + * APIMethod: getBounds + * Get the bounds for this Geometry. If bounds is not set, it + * is calculated again, this makes queries faster. + * + * Returns: + * {} + */ + getBounds: function() { + if (this.bounds == null) { + this.calculateBounds(); + } + return this.bounds; + }, + + /** + * APIMethod: calculateBounds + * Recalculate the bounds for the geometry. + */ + calculateBounds: function() { + // + // This should be overridden by subclasses. + // + }, + + /** + * APIMethod: distanceTo + * Calculate the closest distance between two geometries (on the x-y plane). + * + * Parameters: + * geometry - {} The target geometry. + * options - {Object} Optional properties for configuring the distance + * calculation. + * + * Valid options depend on the specific geometry type. + * + * 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 x2 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) { + }, + + /** + * 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) { + }, + + /** + * Method: atPoint + * Note - This is only an approximation based on the bounds of the + * geometry. + * + * Parameters: + * lonlat - {} + * toleranceLon - {float} Optional tolerance in Geometric Coords + * toleranceLat - {float} Optional tolerance in Geographic Coords + * + * Returns: + * {Boolean} Whether or not the geometry is at the specified location + */ + atPoint: function(lonlat, toleranceLon, toleranceLat) { + var atPoint = false; + var bounds = this.getBounds(); + if ((bounds != null) && (lonlat != null)) { + + var dX = (toleranceLon != null) ? toleranceLon : 0; + var dY = (toleranceLat != null) ? toleranceLat : 0; + + var toleranceBounds = + new OpenLayers.Bounds(this.bounds.left - dX, + this.bounds.bottom - dY, + this.bounds.right + dX, + this.bounds.top + dY); + + atPoint = toleranceBounds.containsLonLat(lonlat); + } + return atPoint; + }, + + /** + * Method: getLength + * Calculate the length of this geometry. This method is defined in + * subclasses. + * + * Returns: + * {Float} The length of the collection by summing its parts + */ + getLength: function() { + //to be overridden by geometries that actually have a length + // + return 0.0; + }, + + /** + * Method: getArea + * Calculate the area of this geometry. This method is defined in subclasses. + * + * Returns: + * {Float} The area of the collection by summing its parts + */ + getArea: function() { + //to be overridden by geometries that actually have an area + // + return 0.0; + }, + + /** + * APIMethod: getCentroid + * Calculate the centroid of this geometry. This method is defined in subclasses. + * + * Returns: + * {} The centroid of the collection + */ + getCentroid: function() { + return null; + }, + + /** + * Method: toString + * Returns the Well-Known Text representation of a geometry + * + * Returns: + * {String} Well-Known Text + */ + toString: function() { + return OpenLayers.Format.WKT.prototype.write( + new OpenLayers.Feature.Vector(this) + ); + }, + + CLASS_NAME: "OpenLayers.Geometry" +}); + +/** + * Function: OpenLayers.Geometry.fromWKT + * Generate a geometry given a Well-Known Text string. + * + * Parameters: + * wkt - {String} A string representing the geometry in Well-Known Text. + * + * Returns: + * {} A geometry of the appropriate class. + */ +OpenLayers.Geometry.fromWKT = function(wkt) { + var format = arguments.callee.format; + if(!format) { + format = new OpenLayers.Format.WKT(); + arguments.callee.format = format; + } + var geom; + var result = format.read(wkt); + if(result instanceof OpenLayers.Feature.Vector) { + geom = result.geometry; + } else if(result instanceof Array) { + var len = result.length; + var components = new Array(len); + for(var i=0; i= seg2.x1 || seg2.x2 >= seg1.x1. In those + * obvious cases where there is no intersection, the function should + * not be called. + * + * Parameters: + * seg1 - {Object} Object representing a segment with properties x1, y1, x2, + * and y2. The start point is represented by x1 and y1. The end point + * is represented by x2 and y2. Start and end are ordered so that x1 < x2. + * seg2 - {Object} Object representing a segment with properties x1, y1, x2, + * and y2. The start point is represented by x1 and y1. The end point + * is represented by x2 and y2. Start and end are ordered so that x1 < x2. + * options - {Object} Optional properties for calculating the intersection. + * + * Valid options: + * point - {Boolean} Return the intersection point. If false, the actual + * intersection point will not be calculated. If true and the segments + * intersect, the intersection point will be returned. If true and + * the segments do not intersect, false will be returned. If true and + * the segments are coincident, true will be returned. + * tolerance - {Number} If a non-null value is provided, if the segments are + * within the tolerance distance, this will be considered an intersection. + * In addition, if the point option is true and the calculated intersection + * is within the tolerance distance of an end point, the endpoint will be + * returned instead of the calculated intersection. Further, if the + * intersection is within the tolerance of endpoints on both segments, or + * if two segment endpoints are within the tolerance distance of eachother + * (but no intersection is otherwise calculated), an endpoint on the + * first segment provided will be returned. + * + * Returns: + * {Boolean | } The two segments intersect. + * If the point argument is true, the return will be the intersection + * point or false if none exists. If point is true and the segments + * are coincident, return will be true (and the instersection is equal + * to the shorter segment). + */ +OpenLayers.Geometry.segmentsIntersect = function(seg1, seg2, options) { + var point = options && options.point; + var tolerance = options && options.tolerance; + var intersection = false; + var x11_21 = seg1.x1 - seg2.x1; + var y11_21 = seg1.y1 - seg2.y1; + var x12_11 = seg1.x2 - seg1.x1; + var y12_11 = seg1.y2 - seg1.y1; + var y22_21 = seg2.y2 - seg2.y1; + var x22_21 = seg2.x2 - seg2.x1; + var d = (y22_21 * x12_11) - (x22_21 * y12_11); + var n1 = (x22_21 * y11_21) - (y22_21 * x11_21); + var n2 = (x12_11 * y11_21) - (y12_11 * x11_21); + if(d == 0) { + // parallel + if(n1 == 0 && n2 == 0) { + // coincident + intersection = true; + } + } else { + var along1 = n1 / d; + var along2 = n2 / d; + if(along1 >= 0 && along1 <= 1 && along2 >=0 && along2 <= 1) { + // intersect + if(!point) { + intersection = true; + } else { + // calculate the intersection point + var x = seg1.x1 + (along1 * x12_11); + var y = seg1.y1 + (along1 * y12_11); + intersection = new OpenLayers.Geometry.Point(x, y); + } + } + } + if(tolerance) { + var dist; + if(intersection) { + if(point) { + var segs = [seg1, seg2]; + var seg, x, y; + // check segment endpoints for proximity to intersection + // set intersection to first endpoint within the tolerance + outer: for(var i=0; i<2; ++i) { + seg = segs[i]; + for(var j=1; j<3; ++j) { + x = seg["x" + j]; + y = seg["y" + j]; + dist = Math.sqrt( + Math.pow(x - intersection.x, 2) + + Math.pow(y - intersection.y, 2) + ); + if(dist < tolerance) { + intersection.x = x; + intersection.y = y; + break outer; + } + } + } + + } + } else { + // no calculated intersection, but segments could be within + // the tolerance of one another + var segs = [seg1, seg2]; + var source, target, x, y, p, result; + // check segment endpoints for proximity to intersection + // set intersection to first endpoint within the tolerance + outer: for(var i=0; i<2; ++i) { + source = segs[i]; + target = segs[(i+1)%2]; + for(var j=1; j<3; ++j) { + p = {x: source["x"+j], y: source["y"+j]}; + result = OpenLayers.Geometry.distanceToSegment(p, target); + if(result.distance < tolerance) { + if(point) { + intersection = new OpenLayers.Geometry.Point(p.x, p.y); + } else { + intersection = true; + } + break outer; + } + } + } + } + } + return intersection; +}; + +/** + * Function: OpenLayers.Geometry.distanceToSegment + * + * Parameters: + * point - {Object} An object with x and y properties representing the + * point coordinates. + * segment - {Object} An object with x1, y1, x2, and y2 properties + * representing endpoint coordinates. + * + * Returns: + * {Object} An object with distance, x, and y properties. The distance + * will be the shortest distance between the input point and segment. + * The x and y properties represent the coordinates along the segment + * where the shortest distance meets the segment. + */ +OpenLayers.Geometry.distanceToSegment = function(point, segment) { + var x0 = point.x; + var y0 = point.y; + var x1 = segment.x1; + var y1 = segment.y1; + var x2 = segment.x2; + var y2 = segment.y2; + var dx = x2 - x1; + var dy = y2 - y1; + var along = ((dx * (x0 - x1)) + (dy * (y0 - y1))) / + (Math.pow(dx, 2) + Math.pow(dy, 2)); + var x, y; + if(along <= 0.0) { + x = x1; + y = y1; + } else if(along >= 1.0) { + x = x2; + y = y2; + } else { + x = x1 + along * dx; + y = y1 + along * dy; + } + return { + distance: Math.sqrt(Math.pow(x - x0, 2) + Math.pow(y - y0, 2)), + x: x, y: y + }; +}; +