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