1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2 * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3 * full text of the license. */
6 * @requires OpenLayers/Geometry/LineString.js
10 * Class: OpenLayers.Geometry.LinearRing
12 * A Linear Ring is a special LineString which is closed. It closes itself
13 * automatically on every addPoint/removePoint by adding a copy of the first
14 * point as the last point.
16 * Also, as it is the first in the line family to close itself, a getArea()
17 * function is defined to calculate the enclosed area of the linearRing
20 * - <OpenLayers.Geometry.LineString>
22 OpenLayers.Geometry.LinearRing = OpenLayers.Class(
23 OpenLayers.Geometry.LineString, {
26 * Property: componentTypes
27 * {Array(String)} An array of class names representing the types of
28 * components that the collection can include. A null
29 * value means the component types are not restricted.
31 componentTypes: ["OpenLayers.Geometry.Point"],
34 * Constructor: OpenLayers.Geometry.LinearRing
35 * Linear rings are constructed with an array of points. This array
36 * can represent a closed or open ring. If the ring is open (the last
37 * point does not equal the first point), the constructor will close
38 * the ring. If the ring is already closed (the last point does equal
39 * the first point), it will be left closed.
42 * points - {Array(<OpenLayers.Geometry.Point>)} points
44 initialize: function(points) {
45 OpenLayers.Geometry.LineString.prototype.initialize.apply(this,
50 * APIMethod: addComponent
51 * Adds a point to geometry components. If the point is to be added to
52 * the end of the components array and it is the same as the last point
53 * already in that array, the duplicate point is not added. This has
54 * the effect of closing the ring if it is not already closed, and
55 * doing the right thing if it is already closed. This behavior can
56 * be overridden by calling the method with a non-null index as the
60 * point - {<OpenLayers.Geometry.Point>}
61 * index - {Integer} Index into the array to insert the component
64 * {Boolean} Was the Point successfully added?
66 addComponent: function(point, index) {
70 var lastPoint = this.components.pop();
72 // given an index, add the point
73 // without an index only add non-duplicate points
74 if(index != null || !point.equals(lastPoint)) {
75 added = OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
79 //append copy of first point
80 var firstPoint = this.components[0];
81 OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
88 * APIMethod: removeComponent
89 * Removes a point from geometry components.
92 * point - {<OpenLayers.Geometry.Point>}
94 removeComponent: function(point) {
95 if (this.components.length > 4) {
98 this.components.pop();
101 OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,
103 //append copy of first point
104 var firstPoint = this.components[0];
105 OpenLayers.Geometry.Collection.prototype.addComponent.apply(this,
112 * Moves a geometry by the given displacement along positive x and y axes.
113 * This modifies the position of the geometry and clears the cached
117 * x - {Float} Distance to move geometry in positive x direction.
118 * y - {Float} Distance to move geometry in positive y direction.
120 move: function(x, y) {
121 for(var i = 0, len=this.components.length; i<len - 1; i++) {
122 this.components[i].move(x, y);
128 * Rotate a geometry around some origin
131 * angle - {Float} Rotation angle in degrees (measured counterclockwise
132 * from the positive x-axis)
133 * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
135 rotate: function(angle, origin) {
136 for(var i=0, len=this.components.length; i<len - 1; ++i) {
137 this.components[i].rotate(angle, origin);
143 * Resize a geometry relative to some origin. Use this method to apply
144 * a uniform scaling to a geometry.
147 * scale - {Float} Factor by which to scale the geometry. A scale of 2
148 * doubles the size of the geometry in each dimension
149 * (lines, for example, will be twice as long, and polygons
150 * will have four times the area).
151 * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
152 * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
155 * {OpenLayers.Geometry} - The current geometry.
157 resize: function(scale, origin, ratio) {
158 for(var i=0, len=this.components.length; i<len - 1; ++i) {
159 this.components[i].resize(scale, origin, ratio);
165 * APIMethod: transform
166 * Reproject the components geometry from source to dest.
169 * source - {<OpenLayers.Projection>}
170 * dest - {<OpenLayers.Projection>}
173 * {<OpenLayers.Geometry>}
175 transform: function(source, dest) {
176 if (source && dest) {
177 for (var i=0, len=this.components.length; i<len - 1; i++) {
178 var component = this.components[i];
179 component.transform(source, dest);
187 * APIMethod: getCentroid
190 * {<OpenLayers.Geometry.Point>} The centroid of the collection
192 getCentroid: function() {
193 if ( this.components && (this.components.length > 2)) {
196 for (var i = 0; i < this.components.length - 1; i++) {
197 var b = this.components[i];
198 var c = this.components[i+1];
199 sumX += (b.x + c.x) * (b.x * c.y - c.x * b.y);
200 sumY += (b.y + c.y) * (b.x * c.y - c.x * b.y);
202 var area = -1 * this.getArea();
203 var x = sumX / (6 * area);
204 var y = sumY / (6 * area);
206 return new OpenLayers.Geometry.Point(x, y);
211 * Note - The area is positive if the ring is oriented CW, otherwise
212 * it will be negative.
215 * {Float} The signed area for a ring.
217 getArea: function() {
219 if ( this.components && (this.components.length > 2)) {
221 for (var i=0, len=this.components.length; i<len - 1; i++) {
222 var b = this.components[i];
223 var c = this.components[i+1];
224 sum += (b.x + c.x) * (c.y - b.y);
232 * APIMethod: getGeodesicArea
233 * Calculate the approximate area of the polygon were it projected onto
234 * the earth. Note that this area will be positive if ring is oriented
235 * clockwise, otherwise it will be negative.
238 * projection - {<OpenLayers.Projection>} The spatial reference system
239 * for the geometry coordinates. If not provided, Geographic/WGS84 is
243 * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
244 * Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
245 * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
248 * {float} The approximate signed geodesic area of the polygon in square
251 getGeodesicArea: function(projection) {
252 var ring = this; // so we can work with a clone if needed
254 var gg = new OpenLayers.Projection("EPSG:4326");
255 if(!gg.equals(projection)) {
256 ring = this.clone().transform(projection, gg);
260 var len = ring.components && ring.components.length;
263 for(var i=0; i<len-1; i++) {
264 p1 = ring.components[i];
265 p2 = ring.components[i+1];
266 area += OpenLayers.Util.rad(p2.x - p1.x) *
267 (2 + Math.sin(OpenLayers.Util.rad(p1.y)) +
268 Math.sin(OpenLayers.Util.rad(p2.y)));
270 area = area * 6378137.0 * 6378137.0 / 2.0;
276 * Method: containsPoint
277 * Test if a point is inside a linear ring. For the case where a point
278 * is coincident with a linear ring edge, returns 1. Otherwise,
282 * point - {<OpenLayers.Geometry.Point>}
285 * {Boolean | Number} The point is inside the linear ring. Returns 1 if
286 * the point is coincident with an edge. Returns boolean otherwise.
288 containsPoint: function(point) {
289 var approx = OpenLayers.Number.limitSigDigs;
291 var px = approx(point.x, digs);
292 var py = approx(point.y, digs);
293 function getX(y, x1, y1, x2, y2) {
294 return (((x1 - x2) * y) + ((x2 * y1) - (x1 * y2))) / (y1 - y2);
296 var numSeg = this.components.length - 1;
297 var start, end, x1, y1, x2, y2, cx, cy;
299 for(var i=0; i<numSeg; ++i) {
300 start = this.components[i];
301 x1 = approx(start.x, digs);
302 y1 = approx(start.y, digs);
303 end = this.components[i + 1];
304 x2 = approx(end.x, digs);
305 y2 = approx(end.y, digs);
308 * The following conditions enforce five edge-crossing rules:
309 * 1. points coincident with edges are considered contained;
310 * 2. an upward edge includes its starting endpoint, and
311 * excludes its final endpoint;
312 * 3. a downward edge excludes its starting endpoint, and
313 * includes its final endpoint;
314 * 4. horizontal edges are excluded; and
315 * 5. the edge-ray intersection point must be strictly right
321 // point on horizontal line
322 if(x1 <= x2 && (px >= x1 && px <= x2) || // right or vert
323 x1 >= x2 && (px <= x1 && px >= x2)) { // left or vert
329 // ignore other horizontal edges
332 cx = approx(getX(py, x1, y1, x2, y2), digs);
335 if(y1 < y2 && (py >= y1 && py <= y2) || // upward
336 y1 > y2 && (py <= y1 && py >= y2)) { // downward
343 // no crossing to the right
346 if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
350 if(y1 < y2 && (py >= y1 && py < y2) || // upward
351 y1 > y2 && (py < y1 && py >= y2)) { // downward
355 var contained = (crosses == -1) ?
358 // even (out) or odd (in)
365 * APIMethod: intersects
366 * Determine if the input geometry intersects this one.
369 * geometry - {<OpenLayers.Geometry>} Any type of geometry.
372 * {Boolean} The input geometry intersects this one.
374 intersects: function(geometry) {
375 var intersect = false;
376 if(geometry.CLASS_NAME == "OpenLayers.Geometry.Point") {
377 intersect = this.containsPoint(geometry);
378 } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LineString") {
379 intersect = geometry.intersects(this);
380 } else if(geometry.CLASS_NAME == "OpenLayers.Geometry.LinearRing") {
381 intersect = OpenLayers.Geometry.LineString.prototype.intersects.apply(
385 // check for component intersections
386 for(var i=0, len=geometry.components.length; i<len; ++ i) {
387 intersect = geometry.components[i].intersects(this);
397 * APIMethod: getVertices
398 * Return a list of all points in this geometry.
401 * nodes - {Boolean} For lines, only return vertices that are
402 * endpoints. If false, for lines, only vertices that are not
403 * endpoints will be returned. If not provided, all vertices will
407 * {Array} A list of all vertices in the geometry.
409 getVertices: function(nodes) {
410 return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
413 CLASS_NAME: "OpenLayers.Geometry.LinearRing"