]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Geometry/Collection.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / Geometry / Collection.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.js
7  */
8
9 /**
10  * Class: OpenLayers.Geometry.Collection
11  * A Collection is exactly what it sounds like: A collection of different 
12  * Geometries. These are stored in the local parameter <components> (which
13  * can be passed as a parameter to the constructor). 
14  * 
15  * As new geometries are added to the collection, they are NOT cloned. 
16  * When removing geometries, they need to be specified by reference (ie you 
17  * have to pass in the *exact* geometry to be removed).
18  * 
19  * The <getArea> and <getLength> functions here merely iterate through
20  * the components, summing their respective areas and lengths.
21  *
22  * Create a new instance with the <OpenLayers.Geometry.Collection> constructor.
23  *
24  * Inerhits from:
25  *  - <OpenLayers.Geometry> 
26  */
27 OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {
28
29     /**
30      * APIProperty: components
31      * {Array(<OpenLayers.Geometry>)} The component parts of this geometry
32      */
33     components: null,
34     
35     /**
36      * Property: componentTypes
37      * {Array(String)} An array of class names representing the types of
38      * components that the collection can include.  A null value means the
39      * component types are not restricted.
40      */
41     componentTypes: null,
42
43     /**
44      * Constructor: OpenLayers.Geometry.Collection
45      * Creates a Geometry Collection -- a list of geoms.
46      *
47      * Parameters: 
48      * components - {Array(<OpenLayers.Geometry>)} Optional array of geometries
49      *
50      */
51     initialize: function (components) {
52         OpenLayers.Geometry.prototype.initialize.apply(this, arguments);
53         this.components = [];
54         if (components != null) {
55             this.addComponents(components);
56         }
57     },
58
59     /**
60      * APIMethod: destroy
61      * Destroy this geometry.
62      */
63     destroy: function () {
64         this.components.length = 0;
65         this.components = null;
66     },
67
68     /**
69      * APIMethod: clone
70      * Clone this geometry.
71      *
72      * Returns:
73      * {<OpenLayers.Geometry.Collection>} An exact clone of this collection
74      */
75     clone: function() {
76         var geometry = eval("new " + this.CLASS_NAME + "()");
77         for(var i=0, len=this.components.length; i<len; i++) {
78             geometry.addComponent(this.components[i].clone());
79         }
80         
81         // catch any randomly tagged-on properties
82         OpenLayers.Util.applyDefaults(geometry, this);
83         
84         return geometry;
85     },
86
87     /**
88      * Method: getComponentsString
89      * Get a string representing the components for this collection
90      * 
91      * Returns:
92      * {String} A string representation of the components of this geometry
93      */
94     getComponentsString: function(){
95         var strings = [];
96         for(var i=0, len=this.components.length; i<len; i++) {
97             strings.push(this.components[i].toShortString()); 
98         }
99         return strings.join(",");
100     },
101
102     /**
103      * APIMethod: calculateBounds
104      * Recalculate the bounds by iterating through the components and 
105      * calling calling extendBounds() on each item.
106      */
107     calculateBounds: function() {
108         this.bounds = null;
109         if ( this.components && this.components.length > 0) {
110             this.setBounds(this.components[0].getBounds());
111             for (var i=1, len=this.components.length; i<len; i++) {
112                 this.extendBounds(this.components[i].getBounds());
113             }
114         }
115     },
116
117     /**
118      * APIMethod: addComponents
119      * Add components to this geometry.
120      *
121      * Parameters:
122      * components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
123      */
124     addComponents: function(components){
125         if(!(components instanceof Array)) {
126             components = [components];
127         }
128         for(var i=0, len=components.length; i<len; i++) {
129             this.addComponent(components[i]);
130         }
131     },
132
133     /**
134      * Method: addComponent
135      * Add a new component (geometry) to the collection.  If this.componentTypes
136      * is set, then the component class name must be in the componentTypes array.
137      *
138      * The bounds cache is reset.
139      * 
140      * Parameters:
141      * component - {<OpenLayers.Geometry>} A geometry to add
142      * index - {int} Optional index into the array to insert the component
143      *
144      * Returns:
145      * {Boolean} The component geometry was successfully added
146      */    
147     addComponent: function(component, index) {
148         var added = false;
149         if(component) {
150             if(this.componentTypes == null ||
151                (OpenLayers.Util.indexOf(this.componentTypes,
152                                         component.CLASS_NAME) > -1)) {
153
154                 if(index != null && (index < this.components.length)) {
155                     var components1 = this.components.slice(0, index);
156                     var components2 = this.components.slice(index, 
157                                                            this.components.length);
158                     components1.push(component);
159                     this.components = components1.concat(components2);
160                 } else {
161                     this.components.push(component);
162                 }
163                 component.parent = this;
164                 this.clearBounds();
165                 added = true;
166             }
167         }
168         return added;
169     },
170     
171     /**
172      * APIMethod: removeComponents
173      * Remove components from this geometry.
174      *
175      * Parameters:
176      * components - {Array(<OpenLayers.Geometry>)} The components to be removed
177      */
178     removeComponents: function(components) {
179         if(!(components instanceof Array)) {
180             components = [components];
181         }
182         for(var i=components.length-1; i>=0; --i) {
183             this.removeComponent(components[i]);
184         }
185     },
186     
187     /**
188      * Method: removeComponent
189      * Remove a component from this geometry.
190      *
191      * Parameters:
192      * component - {<OpenLayers.Geometry>} 
193      */
194     removeComponent: function(component) {
195         
196         OpenLayers.Util.removeItem(this.components, component);
197         
198         // clearBounds() so that it gets recalculated on the next call
199         // to this.getBounds();
200         this.clearBounds();
201     },
202
203     /**
204      * APIMethod: getLength
205      * Calculate the length of this geometry
206      *
207      * Returns:
208      * {Float} The length of the geometry
209      */
210     getLength: function() {
211         var length = 0.0;
212         for (var i=0, len=this.components.length; i<len; i++) {
213             length += this.components[i].getLength();
214         }
215         return length;
216     },
217     
218     /**
219      * APIMethod: getArea
220      * Calculate the area of this geometry. Note how this function is overridden
221      * in <OpenLayers.Geometry.Polygon>.
222      *
223      * Returns:
224      * {Float} The area of the collection by summing its parts
225      */
226     getArea: function() {
227         var area = 0.0;
228         for (var i=0, len=this.components.length; i<len; i++) {
229             area += this.components[i].getArea();
230         }
231         return area;
232     },
233
234     /** 
235      * APIMethod: getGeodesicArea
236      * Calculate the approximate area of the polygon were it projected onto
237      *     the earth.
238      *
239      * Parameters:
240      * projection - {<OpenLayers.Projection>} The spatial reference system
241      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
242      *     assumed.
243      * 
244      * Reference:
245      * Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
246      *     Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
247      *     Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
248      *
249      * Returns:
250      * {float} The approximate geodesic area of the geometry in square meters.
251      */
252     getGeodesicArea: function(projection) {
253         var area = 0.0;
254         for(var i=0, len=this.components.length; i<len; i++) {
255             area += this.components[i].getGeodesicArea(projection);
256         }
257         return area;
258     },
259     
260     /**
261      * APIMethod: getCentroid
262      *
263      * Returns:
264      * {<OpenLayers.Geometry.Point>} The centroid of the collection
265      */
266     getCentroid: function() {
267         return this.components.length && this.components[0].getCentroid();
268         /*
269         var centroid;
270         for (var i=0, len=this.components.length; i<len; i++) {
271             if (!centroid) {
272                 centroid = this.components[i].getCentroid();
273             } else {
274                 centroid.resize(this.components[i].getCentroid(), 0.5);
275             }
276         }
277         return centroid;
278         */
279     },
280
281     /**
282      * APIMethod: getGeodesicLength
283      * Calculate the approximate length of the geometry were it projected onto
284      *     the earth.
285      *
286      * projection - {<OpenLayers.Projection>} The spatial reference system
287      *     for the geometry coordinates.  If not provided, Geographic/WGS84 is
288      *     assumed.
289      * 
290      * Returns:
291      * {Float} The appoximate geodesic length of the geometry in meters.
292      */
293     getGeodesicLength: function(projection) {
294         var length = 0.0;
295         for(var i=0, len=this.components.length; i<len; i++) {
296             length += this.components[i].getGeodesicLength(projection);
297         }
298         return length;
299     },
300
301     /**
302      * APIMethod: move
303      * Moves a geometry by the given displacement along positive x and y axes.
304      *     This modifies the position of the geometry and clears the cached
305      *     bounds.
306      *
307      * Parameters:
308      * x - {Float} Distance to move geometry in positive x direction. 
309      * y - {Float} Distance to move geometry in positive y direction.
310      */
311     move: function(x, y) {
312         for(var i=0, len=this.components.length; i<len; i++) {
313             this.components[i].move(x, y);
314         }
315     },
316
317     /**
318      * APIMethod: rotate
319      * Rotate a geometry around some origin
320      *
321      * Parameters:
322      * angle - {Float} Rotation angle in degrees (measured counterclockwise
323      *                 from the positive x-axis)
324      * origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
325      */
326     rotate: function(angle, origin) {
327         for(var i=0, len=this.components.length; i<len; ++i) {
328             this.components[i].rotate(angle, origin);
329         }
330     },
331
332     /**
333      * APIMethod: resize
334      * Resize a geometry relative to some origin.  Use this method to apply
335      *     a uniform scaling to a geometry.
336      *
337      * Parameters:
338      * scale - {Float} Factor by which to scale the geometry.  A scale of 2
339      *                 doubles the size of the geometry in each dimension
340      *                 (lines, for example, will be twice as long, and polygons
341      *                 will have four times the area).
342      * origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
343      * ratio - {Float} Optional x:y ratio for resizing.  Default ratio is 1.
344      * 
345      * Returns:
346      * {OpenLayers.Geometry} - The current geometry. 
347      */
348     resize: function(scale, origin, ratio) {
349         for(var i=0; i<this.components.length; ++i) {
350             this.components[i].resize(scale, origin, ratio);
351         }
352         return this;
353     },
354
355     /**
356      * APIMethod: distanceTo
357      * Calculate the closest distance between two geometries (on the x-y plane).
358      *
359      * Parameters:
360      * geometry - {<OpenLayers.Geometry>} The target geometry.
361      * options - {Object} Optional properties for configuring the distance
362      *     calculation.
363      *
364      * Valid options:
365      * details - {Boolean} Return details from the distance calculation.
366      *     Default is false.
367      * edge - {Boolean} Calculate the distance from this geometry to the
368      *     nearest edge of the target geometry.  Default is true.  If true,
369      *     calling distanceTo from a geometry that is wholly contained within
370      *     the target will result in a non-zero distance.  If false, whenever
371      *     geometries intersect, calling distanceTo will return 0.  If false,
372      *     details cannot be returned.
373      *
374      * Returns:
375      * {Number | Object} The distance between this geometry and the target.
376      *     If details is true, the return will be an object with distance,
377      *     x0, y0, x1, and y1 properties.  The x0 and y0 properties represent
378      *     the coordinates of the closest point on this geometry. The x1 and y1
379      *     properties represent the coordinates of the closest point on the
380      *     target geometry.
381      */
382     distanceTo: function(geometry, options) {
383         var edge = !(options && options.edge === false);
384         var details = edge && options && options.details;
385         var result, best;
386         var min = Number.POSITIVE_INFINITY;
387         for(var i=0, len=this.components.length; i<len; ++i) {
388             result = this.components[i].distanceTo(geometry, options);
389             distance = details ? result.distance : result;
390             if(distance < min) {
391                 min = distance;
392                 best = result;
393                 if(min == 0) {
394                     break;
395                 }
396             }
397         }
398         return best;
399     },
400
401     /** 
402      * APIMethod: equals
403      * Determine whether another geometry is equivalent to this one.  Geometries
404      *     are considered equivalent if all components have the same coordinates.
405      * 
406      * Parameters:
407      * geom - {<OpenLayers.Geometry>} The geometry to test. 
408      *
409      * Returns:
410      * {Boolean} The supplied geometry is equivalent to this geometry.
411      */
412     equals: function(geometry) {
413         var equivalent = true;
414         if(!geometry || !geometry.CLASS_NAME ||
415            (this.CLASS_NAME != geometry.CLASS_NAME)) {
416             equivalent = false;
417         } else if(!(geometry.components instanceof Array) ||
418                   (geometry.components.length != this.components.length)) {
419             equivalent = false;
420         } else {
421             for(var i=0, len=this.components.length; i<len; ++i) {
422                 if(!this.components[i].equals(geometry.components[i])) {
423                     equivalent = false;
424                     break;
425                 }
426             }
427         }
428         return equivalent;
429     },
430
431     /**
432      * APIMethod: transform
433      * Reproject the components geometry from source to dest.
434      * 
435      * Parameters:
436      * source - {<OpenLayers.Projection>} 
437      * dest - {<OpenLayers.Projection>}
438      * 
439      * Returns:
440      * {<OpenLayers.Geometry>} 
441      */
442     transform: function(source, dest) {
443         if (source && dest) {
444             for (var i=0, len=this.components.length; i<len; i++) {  
445                 var component = this.components[i];
446                 component.transform(source, dest);
447             }
448             this.bounds = null;
449         }
450         return this;
451     },
452
453     /**
454      * APIMethod: intersects
455      * Determine if the input geometry intersects this one.
456      *
457      * Parameters:
458      * geometry - {<OpenLayers.Geometry>} Any type of geometry.
459      *
460      * Returns:
461      * {Boolean} The input geometry intersects this one.
462      */
463     intersects: function(geometry) {
464         var intersect = false;
465         for(var i=0, len=this.components.length; i<len; ++ i) {
466             intersect = geometry.intersects(this.components[i]);
467             if(intersect) {
468                 break;
469             }
470         }
471         return intersect;
472     },
473
474     /**
475      * APIMethod: getVertices
476      * Return a list of all points in this geometry.
477      *
478      * Parameters:
479      * nodes - {Boolean} For lines, only return vertices that are
480      *     endpoints.  If false, for lines, only vertices that are not
481      *     endpoints will be returned.  If not provided, all vertices will
482      *     be returned.
483      *
484      * Returns:
485      * {Array} A list of all vertices in the geometry.
486      */
487     getVertices: function(nodes) {
488         var vertices = [];
489         for(var i=0, len=this.components.length; i<len; ++i) {
490             Array.prototype.push.apply(
491                 vertices, this.components[i].getVertices(nodes)
492             );
493         }
494         return vertices;
495     },
496
497
498     CLASS_NAME: "OpenLayers.Geometry.Collection"
499 });