]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Geometry/LinearRing.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Geometry / LinearRing.js
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. */
4
5 /**
6  * @requires OpenLayers/Geometry/LineString.js
7  */
8
9 /**
10  * Class: OpenLayers.Geometry.LinearRing
11  * 
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. 
15  * 
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
18  * 
19  * Inherits:
20  *  - <OpenLayers.Geometry.LineString>
21  */
22 OpenLayers.Geometry.LinearRing = OpenLayers.Class(
23   OpenLayers.Geometry.LineString, {
24
25     /**
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.
30      */
31     componentTypes: ["OpenLayers.Geometry.Point"],
32
33     /**
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.
40      * 
41      * Parameters:
42      * points - {Array(<OpenLayers.Geometry.Point>)} points
43      */
44     initialize: function(points) {
45         OpenLayers.Geometry.LineString.prototype.initialize.apply(this, 
46                                                                   arguments);
47     },
48
49     /**
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 
57      *     second argument.
58      *
59      * Parameter:
60      * point - {<OpenLayers.Geometry.Point>}
61      * index - {Integer} Index into the array to insert the component
62      * 
63      * Returns:
64      * {Boolean} Was the Point successfully added?
65      */
66     addComponent: function(point, index) {
67         var added = false;
68
69         //remove last point
70         var lastPoint = this.components.pop();
71
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, 
76                                                                     arguments);
77         }
78
79         //append copy of first point
80         var firstPoint = this.components[0];
81         OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
82                                                                 [firstPoint]);
83         
84         return added;
85     },
86     
87     /**
88      * APIMethod: removeComponent
89      * Removes a point from geometry components.
90      *
91      * Parameters:
92      * point - {<OpenLayers.Geometry.Point>}
93      */
94     removeComponent: function(point) {
95         if (this.components.length > 4) {
96
97             //remove last point
98             this.components.pop();
99             
100             //remove our point
101             OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this, 
102                                                                     arguments);
103             //append copy of first point
104             var firstPoint = this.components[0];
105             OpenLayers.Geometry.Collection.prototype.addComponent.apply(this, 
106                                                                 [firstPoint]);
107         }
108     },
109     
110     /**
111      * APIMethod: move
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
114      *     bounds.
115      *
116      * Parameters:
117      * x - {Float} Distance to move geometry in positive x direction. 
118      * y - {Float} Distance to move geometry in positive y direction.
119      */
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);
123         }
124     },
125
126     /**
127      * APIMethod: rotate
128      * Rotate a geometry around some origin
129      *
130      * Parameters:
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
134      */
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);
138         }
139     },
140
141     /**
142      * APIMethod: resize
143      * Resize a geometry relative to some origin.  Use this method to apply
144      *     a uniform scaling to a geometry.
145      *
146      * Parameters:
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.
153      * 
154      * Returns:
155      * {OpenLayers.Geometry} - The current geometry. 
156      */
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);
160         }
161         return this;
162     },
163     
164     /**
165      * APIMethod: transform
166      * Reproject the components geometry from source to dest.
167      *
168      * Parameters:
169      * source - {<OpenLayers.Projection>}
170      * dest - {<OpenLayers.Projection>}
171      * 
172      * Returns:
173      * {<OpenLayers.Geometry>} 
174      */
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);
180             }
181             this.bounds = null;
182         }
183         return this;
184     },
185     
186     /**
187      * APIMethod: getCentroid
188      *
189      * Returns:
190      * {<OpenLayers.Geometry.Point>} The centroid of the collection
191      */
192     getCentroid: function() {
193         if ( this.components && (this.components.length > 2)) {
194             var sumX = 0.0;
195             var sumY = 0.0;
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);
201             }
202             var area = -1 * this.getArea();
203             var x = sumX / (6 * area);
204             var y = sumY / (6 * area);
205         }
206         return new OpenLayers.Geometry.Point(x, y);
207     },
208
209     /**
210      * APIMethod: getArea
211      * Note - The area is positive if the ring is oriented CW, otherwise
212      *         it will be negative.
213      * 
214      * Returns:
215      * {Float} The signed area for a ring.
216      */
217     getArea: function() {
218         var area = 0.0;
219         if ( this.components && (this.components.length > 2)) {
220             var sum = 0.0;
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);
225             }
226             area = - sum / 2.0;
227         }
228         return area;
229     },
230     
231     /**
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.
236      *
237      * Parameters:
238      * projection - {<OpenLayers.Projection>} The spatial reference system
239      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
240      *     assumed.
241      * 
242      * Reference:
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
246      *
247      * Returns:
248      * {float} The approximate signed geodesic area of the polygon in square
249      *     meters.
250      */
251     getGeodesicArea: function(projection) {
252         var ring = this;  // so we can work with a clone if needed
253         if(projection) {
254             var gg = new OpenLayers.Projection("EPSG:4326");
255             if(!gg.equals(projection)) {
256                 ring = this.clone().transform(projection, gg);
257             }
258         }
259         var area = 0.0;
260         var len = ring.components && ring.components.length;
261         if(len > 2) {
262             var p1, p2;
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)));
269             }
270             area = area * 6378137.0 * 6378137.0 / 2.0;
271         }
272         return area;
273     },
274     
275     /**
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,
279      *     returns boolean.
280      *
281      * Parameters:
282      * point - {<OpenLayers.Geometry.Point>}
283      *
284      * Returns:
285      * {Boolean | Number} The point is inside the linear ring.  Returns 1 if
286      *     the point is coincident with an edge.  Returns boolean otherwise.
287      */
288     containsPoint: function(point) {
289         var approx = OpenLayers.Number.limitSigDigs;
290         var digs = 14;
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);
295         }
296         var numSeg = this.components.length - 1;
297         var start, end, x1, y1, x2, y2, cx, cy;
298         var crosses = 0;
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);
306             
307             /**
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
316              *    of the point P.
317              */
318             if(y1 == y2) {
319                 // horizontal edge
320                 if(py == y1) {
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
324                         // point on edge
325                         crosses = -1;
326                         break;
327                     }
328                 }
329                 // ignore other horizontal edges
330                 continue;
331             }
332             cx = approx(getX(py, x1, y1, x2, y2), digs);
333             if(cx == px) {
334                 // point on line
335                 if(y1 < y2 && (py >= y1 && py <= y2) || // upward
336                    y1 > y2 && (py <= y1 && py >= y2)) { // downward
337                     // point on edge
338                     crosses = -1;
339                     break;
340                 }
341             }
342             if(cx <= px) {
343                 // no crossing to the right
344                 continue;
345             }
346             if(x1 != x2 && (cx < Math.min(x1, x2) || cx > Math.max(x1, x2))) {
347                 // no crossing
348                 continue;
349             }
350             if(y1 < y2 && (py >= y1 && py < y2) || // upward
351                y1 > y2 && (py < y1 && py >= y2)) { // downward
352                 ++crosses;
353             }
354         }
355         var contained = (crosses == -1) ?
356             // on edge
357             1 :
358             // even (out) or odd (in)
359             !!(crosses & 1);
360
361         return contained;
362     },
363
364     /**
365      * APIMethod: intersects
366      * Determine if the input geometry intersects this one.
367      *
368      * Parameters:
369      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
370      *
371      * Returns:
372      * {Boolean} The input geometry intersects this one.
373      */
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(
382                 this, [geometry]
383             );
384         } else {
385             // check for component intersections
386             for(var i=0, len=geometry.components.length; i<len; ++ i) {
387                 intersect = geometry.components[i].intersects(this);
388                 if(intersect) {
389                     break;
390                 }
391             }
392         }
393         return intersect;
394     },
395
396     /**
397      * APIMethod: getVertices
398      * Return a list of all points in this geometry.
399      *
400      * Parameters:
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
404      *     be returned.
405      *
406      * Returns:
407      * {Array} A list of all vertices in the geometry.
408      */
409     getVertices: function(nodes) {
410         return (nodes === true) ? [] : this.components.slice(0, this.components.length-1);
411     },
412
413     CLASS_NAME: "OpenLayers.Geometry.LinearRing"
414 });