1
|
/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
|
2
|
* full list of contributors). Published under the 2-clause BSD license.
|
3
|
* See license.txt in the OpenLayers distribution or repository 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
|
* Inherits 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
|
var bounds = new OpenLayers.Bounds();
|
112
|
var components = this.components;
|
113
|
if (components) {
|
114
|
for (var i=0, len=components.length; i<len; i++) {
|
115
|
bounds.extend(components[i].getBounds());
|
116
|
}
|
117
|
}
|
118
|
// to preserve old behavior, we only set bounds if non-null
|
119
|
// in the future, we could add bounds.isEmpty()
|
120
|
if (bounds.left != null && bounds.bottom != null &&
|
121
|
bounds.right != null && bounds.top != null) {
|
122
|
this.setBounds(bounds);
|
123
|
}
|
124
|
},
|
125
|
|
126
|
/**
|
127
|
* APIMethod: addComponents
|
128
|
* Add components to this geometry.
|
129
|
*
|
130
|
* Parameters:
|
131
|
* components - {Array(<OpenLayers.Geometry>)} An array of geometries to add
|
132
|
*/
|
133
|
addComponents: function(components){
|
134
|
if(!(OpenLayers.Util.isArray(components))) {
|
135
|
components = [components];
|
136
|
}
|
137
|
for(var i=0, len=components.length; i<len; i++) {
|
138
|
this.addComponent(components[i]);
|
139
|
}
|
140
|
},
|
141
|
|
142
|
/**
|
143
|
* Method: addComponent
|
144
|
* Add a new component (geometry) to the collection. If this.componentTypes
|
145
|
* is set, then the component class name must be in the componentTypes array.
|
146
|
*
|
147
|
* The bounds cache is reset.
|
148
|
*
|
149
|
* Parameters:
|
150
|
* component - {<OpenLayers.Geometry>} A geometry to add
|
151
|
* index - {int} Optional index into the array to insert the component
|
152
|
*
|
153
|
* Returns:
|
154
|
* {Boolean} The component geometry was successfully added
|
155
|
*/
|
156
|
addComponent: function(component, index) {
|
157
|
var added = false;
|
158
|
if(component) {
|
159
|
if(this.componentTypes == null ||
|
160
|
(OpenLayers.Util.indexOf(this.componentTypes,
|
161
|
component.CLASS_NAME) > -1)) {
|
162
|
|
163
|
if(index != null && (index < this.components.length)) {
|
164
|
var components1 = this.components.slice(0, index);
|
165
|
var components2 = this.components.slice(index,
|
166
|
this.components.length);
|
167
|
components1.push(component);
|
168
|
this.components = components1.concat(components2);
|
169
|
} else {
|
170
|
this.components.push(component);
|
171
|
}
|
172
|
component.parent = this;
|
173
|
this.clearBounds();
|
174
|
added = true;
|
175
|
}
|
176
|
}
|
177
|
return added;
|
178
|
},
|
179
|
|
180
|
/**
|
181
|
* APIMethod: removeComponents
|
182
|
* Remove components from this geometry.
|
183
|
*
|
184
|
* Parameters:
|
185
|
* components - {Array(<OpenLayers.Geometry>)} The components to be removed
|
186
|
*
|
187
|
* Returns:
|
188
|
* {Boolean} A component was removed.
|
189
|
*/
|
190
|
removeComponents: function(components) {
|
191
|
var removed = false;
|
192
|
|
193
|
if(!(OpenLayers.Util.isArray(components))) {
|
194
|
components = [components];
|
195
|
}
|
196
|
for(var i=components.length-1; i>=0; --i) {
|
197
|
removed = this.removeComponent(components[i]) || removed;
|
198
|
}
|
199
|
return removed;
|
200
|
},
|
201
|
|
202
|
/**
|
203
|
* Method: removeComponent
|
204
|
* Remove a component from this geometry.
|
205
|
*
|
206
|
* Parameters:
|
207
|
* component - {<OpenLayers.Geometry>}
|
208
|
*
|
209
|
* Returns:
|
210
|
* {Boolean} The component was removed.
|
211
|
*/
|
212
|
removeComponent: function(component) {
|
213
|
|
214
|
OpenLayers.Util.removeItem(this.components, component);
|
215
|
|
216
|
// clearBounds() so that it gets recalculated on the next call
|
217
|
// to this.getBounds();
|
218
|
this.clearBounds();
|
219
|
return true;
|
220
|
},
|
221
|
|
222
|
/**
|
223
|
* APIMethod: getLength
|
224
|
* Calculate the length of this geometry
|
225
|
*
|
226
|
* Returns:
|
227
|
* {Float} The length of the geometry
|
228
|
*/
|
229
|
getLength: function() {
|
230
|
var length = 0.0;
|
231
|
for (var i=0, len=this.components.length; i<len; i++) {
|
232
|
length += this.components[i].getLength();
|
233
|
}
|
234
|
return length;
|
235
|
},
|
236
|
|
237
|
/**
|
238
|
* APIMethod: getArea
|
239
|
* Calculate the area of this geometry. Note how this function is overridden
|
240
|
* in <OpenLayers.Geometry.Polygon>.
|
241
|
*
|
242
|
* Returns:
|
243
|
* {Float} The area of the collection by summing its parts
|
244
|
*/
|
245
|
getArea: function() {
|
246
|
var area = 0.0;
|
247
|
for (var i=0, len=this.components.length; i<len; i++) {
|
248
|
area += this.components[i].getArea();
|
249
|
}
|
250
|
return area;
|
251
|
},
|
252
|
|
253
|
/**
|
254
|
* APIMethod: getGeodesicArea
|
255
|
* Calculate the approximate area of the polygon were it projected onto
|
256
|
* the earth.
|
257
|
*
|
258
|
* Parameters:
|
259
|
* projection - {<OpenLayers.Projection>} The spatial reference system
|
260
|
* for the geometry coordinates. If not provided, Geographic/WGS84 is
|
261
|
* assumed.
|
262
|
*
|
263
|
* Reference:
|
264
|
* Robert. G. Chamberlain and William H. Duquette, "Some Algorithms for
|
265
|
* Polygons on a Sphere", JPL Publication 07-03, Jet Propulsion
|
266
|
* Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409
|
267
|
*
|
268
|
* Returns:
|
269
|
* {float} The approximate geodesic area of the geometry in square meters.
|
270
|
*/
|
271
|
getGeodesicArea: function(projection) {
|
272
|
var area = 0.0;
|
273
|
for(var i=0, len=this.components.length; i<len; i++) {
|
274
|
area += this.components[i].getGeodesicArea(projection);
|
275
|
}
|
276
|
return area;
|
277
|
},
|
278
|
|
279
|
/**
|
280
|
* APIMethod: getCentroid
|
281
|
*
|
282
|
* Compute the centroid for this geometry collection.
|
283
|
*
|
284
|
* Parameters:
|
285
|
* weighted - {Boolean} Perform the getCentroid computation recursively,
|
286
|
* returning an area weighted average of all geometries in this collection.
|
287
|
*
|
288
|
* Returns:
|
289
|
* {<OpenLayers.Geometry.Point>} The centroid of the collection
|
290
|
*/
|
291
|
getCentroid: function(weighted) {
|
292
|
if (!weighted) {
|
293
|
return this.components.length && this.components[0].getCentroid();
|
294
|
}
|
295
|
var len = this.components.length;
|
296
|
if (!len) {
|
297
|
return false;
|
298
|
}
|
299
|
|
300
|
var areas = [];
|
301
|
var centroids = [];
|
302
|
var areaSum = 0;
|
303
|
var minArea = Number.MAX_VALUE;
|
304
|
var component;
|
305
|
for (var i=0; i<len; ++i) {
|
306
|
component = this.components[i];
|
307
|
var area = component.getArea();
|
308
|
var centroid = component.getCentroid(true);
|
309
|
if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {
|
310
|
continue;
|
311
|
}
|
312
|
areas.push(area);
|
313
|
areaSum += area;
|
314
|
minArea = (area < minArea && area > 0) ? area : minArea;
|
315
|
centroids.push(centroid);
|
316
|
}
|
317
|
len = areas.length;
|
318
|
if (areaSum === 0) {
|
319
|
// all the components in this collection have 0 area
|
320
|
// probably a collection of points -- weight all the points the same
|
321
|
for (var i=0; i<len; ++i) {
|
322
|
areas[i] = 1;
|
323
|
}
|
324
|
areaSum = areas.length;
|
325
|
} else {
|
326
|
// normalize all the areas where the smallest area will get
|
327
|
// a value of 1
|
328
|
for (var i=0; i<len; ++i) {
|
329
|
areas[i] /= minArea;
|
330
|
}
|
331
|
areaSum /= minArea;
|
332
|
}
|
333
|
|
334
|
var xSum = 0, ySum = 0, centroid, area;
|
335
|
for (var i=0; i<len; ++i) {
|
336
|
centroid = centroids[i];
|
337
|
area = areas[i];
|
338
|
xSum += centroid.x * area;
|
339
|
ySum += centroid.y * area;
|
340
|
}
|
341
|
|
342
|
return new OpenLayers.Geometry.Point(xSum/areaSum, ySum/areaSum);
|
343
|
},
|
344
|
|
345
|
/**
|
346
|
* APIMethod: getGeodesicLength
|
347
|
* Calculate the approximate length of the geometry were it projected onto
|
348
|
* the earth.
|
349
|
*
|
350
|
* projection - {<OpenLayers.Projection>} The spatial reference system
|
351
|
* for the geometry coordinates. If not provided, Geographic/WGS84 is
|
352
|
* assumed.
|
353
|
*
|
354
|
* Returns:
|
355
|
* {Float} The appoximate geodesic length of the geometry in meters.
|
356
|
*/
|
357
|
getGeodesicLength: function(projection) {
|
358
|
var length = 0.0;
|
359
|
for(var i=0, len=this.components.length; i<len; i++) {
|
360
|
length += this.components[i].getGeodesicLength(projection);
|
361
|
}
|
362
|
return length;
|
363
|
},
|
364
|
|
365
|
/**
|
366
|
* APIMethod: move
|
367
|
* Moves a geometry by the given displacement along positive x and y axes.
|
368
|
* This modifies the position of the geometry and clears the cached
|
369
|
* bounds.
|
370
|
*
|
371
|
* Parameters:
|
372
|
* x - {Float} Distance to move geometry in positive x direction.
|
373
|
* y - {Float} Distance to move geometry in positive y direction.
|
374
|
*/
|
375
|
move: function(x, y) {
|
376
|
for(var i=0, len=this.components.length; i<len; i++) {
|
377
|
this.components[i].move(x, y);
|
378
|
}
|
379
|
},
|
380
|
|
381
|
/**
|
382
|
* APIMethod: rotate
|
383
|
* Rotate a geometry around some origin
|
384
|
*
|
385
|
* Parameters:
|
386
|
* angle - {Float} Rotation angle in degrees (measured counterclockwise
|
387
|
* from the positive x-axis)
|
388
|
* origin - {<OpenLayers.Geometry.Point>} Center point for the rotation
|
389
|
*/
|
390
|
rotate: function(angle, origin) {
|
391
|
for(var i=0, len=this.components.length; i<len; ++i) {
|
392
|
this.components[i].rotate(angle, origin);
|
393
|
}
|
394
|
},
|
395
|
|
396
|
/**
|
397
|
* APIMethod: resize
|
398
|
* Resize a geometry relative to some origin. Use this method to apply
|
399
|
* a uniform scaling to a geometry.
|
400
|
*
|
401
|
* Parameters:
|
402
|
* scale - {Float} Factor by which to scale the geometry. A scale of 2
|
403
|
* doubles the size of the geometry in each dimension
|
404
|
* (lines, for example, will be twice as long, and polygons
|
405
|
* will have four times the area).
|
406
|
* origin - {<OpenLayers.Geometry.Point>} Point of origin for resizing
|
407
|
* ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.
|
408
|
*
|
409
|
* Returns:
|
410
|
* {<OpenLayers.Geometry>} - The current geometry.
|
411
|
*/
|
412
|
resize: function(scale, origin, ratio) {
|
413
|
for(var i=0; i<this.components.length; ++i) {
|
414
|
this.components[i].resize(scale, origin, ratio);
|
415
|
}
|
416
|
return this;
|
417
|
},
|
418
|
|
419
|
/**
|
420
|
* APIMethod: distanceTo
|
421
|
* Calculate the closest distance between two geometries (on the x-y plane).
|
422
|
*
|
423
|
* Parameters:
|
424
|
* geometry - {<OpenLayers.Geometry>} The target geometry.
|
425
|
* options - {Object} Optional properties for configuring the distance
|
426
|
* calculation.
|
427
|
*
|
428
|
* Valid options:
|
429
|
* details - {Boolean} Return details from the distance calculation.
|
430
|
* Default is false.
|
431
|
* edge - {Boolean} Calculate the distance from this geometry to the
|
432
|
* nearest edge of the target geometry. Default is true. If true,
|
433
|
* calling distanceTo from a geometry that is wholly contained within
|
434
|
* the target will result in a non-zero distance. If false, whenever
|
435
|
* geometries intersect, calling distanceTo will return 0. If false,
|
436
|
* details cannot be returned.
|
437
|
*
|
438
|
* Returns:
|
439
|
* {Number | Object} The distance between this geometry and the target.
|
440
|
* If details is true, the return will be an object with distance,
|
441
|
* x0, y0, x1, and y1 properties. The x0 and y0 properties represent
|
442
|
* the coordinates of the closest point on this geometry. The x1 and y1
|
443
|
* properties represent the coordinates of the closest point on the
|
444
|
* target geometry.
|
445
|
*/
|
446
|
distanceTo: function(geometry, options) {
|
447
|
var edge = !(options && options.edge === false);
|
448
|
var details = edge && options && options.details;
|
449
|
var result, best, distance;
|
450
|
var min = Number.POSITIVE_INFINITY;
|
451
|
for(var i=0, len=this.components.length; i<len; ++i) {
|
452
|
result = this.components[i].distanceTo(geometry, options);
|
453
|
distance = details ? result.distance : result;
|
454
|
if(distance < min) {
|
455
|
min = distance;
|
456
|
best = result;
|
457
|
if(min == 0) {
|
458
|
break;
|
459
|
}
|
460
|
}
|
461
|
}
|
462
|
return best;
|
463
|
},
|
464
|
|
465
|
/**
|
466
|
* APIMethod: equals
|
467
|
* Determine whether another geometry is equivalent to this one. Geometries
|
468
|
* are considered equivalent if all components have the same coordinates.
|
469
|
*
|
470
|
* Parameters:
|
471
|
* geometry - {<OpenLayers.Geometry>} The geometry to test.
|
472
|
*
|
473
|
* Returns:
|
474
|
* {Boolean} The supplied geometry is equivalent to this geometry.
|
475
|
*/
|
476
|
equals: function(geometry) {
|
477
|
var equivalent = true;
|
478
|
if(!geometry || !geometry.CLASS_NAME ||
|
479
|
(this.CLASS_NAME != geometry.CLASS_NAME)) {
|
480
|
equivalent = false;
|
481
|
} else if(!(OpenLayers.Util.isArray(geometry.components)) ||
|
482
|
(geometry.components.length != this.components.length)) {
|
483
|
equivalent = false;
|
484
|
} else {
|
485
|
for(var i=0, len=this.components.length; i<len; ++i) {
|
486
|
if(!this.components[i].equals(geometry.components[i])) {
|
487
|
equivalent = false;
|
488
|
break;
|
489
|
}
|
490
|
}
|
491
|
}
|
492
|
return equivalent;
|
493
|
},
|
494
|
|
495
|
/**
|
496
|
* APIMethod: transform
|
497
|
* Reproject the components geometry from source to dest.
|
498
|
*
|
499
|
* Parameters:
|
500
|
* source - {<OpenLayers.Projection>}
|
501
|
* dest - {<OpenLayers.Projection>}
|
502
|
*
|
503
|
* Returns:
|
504
|
* {<OpenLayers.Geometry>}
|
505
|
*/
|
506
|
transform: function(source, dest) {
|
507
|
if (source && dest) {
|
508
|
for (var i=0, len=this.components.length; i<len; i++) {
|
509
|
var component = this.components[i];
|
510
|
component.transform(source, dest);
|
511
|
}
|
512
|
this.bounds = null;
|
513
|
}
|
514
|
return this;
|
515
|
},
|
516
|
|
517
|
/**
|
518
|
* APIMethod: intersects
|
519
|
* Determine if the input geometry intersects this one.
|
520
|
*
|
521
|
* Parameters:
|
522
|
* geometry - {<OpenLayers.Geometry>} Any type of geometry.
|
523
|
*
|
524
|
* Returns:
|
525
|
* {Boolean} The input geometry intersects this one.
|
526
|
*/
|
527
|
intersects: function(geometry) {
|
528
|
var intersect = false;
|
529
|
for(var i=0, len=this.components.length; i<len; ++ i) {
|
530
|
intersect = geometry.intersects(this.components[i]);
|
531
|
if(intersect) {
|
532
|
break;
|
533
|
}
|
534
|
}
|
535
|
return intersect;
|
536
|
},
|
537
|
|
538
|
/**
|
539
|
* APIMethod: getVertices
|
540
|
* Return a list of all points in this geometry.
|
541
|
*
|
542
|
* Parameters:
|
543
|
* nodes - {Boolean} For lines, only return vertices that are
|
544
|
* endpoints. If false, for lines, only vertices that are not
|
545
|
* endpoints will be returned. If not provided, all vertices will
|
546
|
* be returned.
|
547
|
*
|
548
|
* Returns:
|
549
|
* {Array} A list of all vertices in the geometry.
|
550
|
*/
|
551
|
getVertices: function(nodes) {
|
552
|
var vertices = [];
|
553
|
for(var i=0, len=this.components.length; i<len; ++i) {
|
554
|
Array.prototype.push.apply(
|
555
|
vertices, this.components[i].getVertices(nodes)
|
556
|
);
|
557
|
}
|
558
|
return vertices;
|
559
|
},
|
560
|
|
561
|
|
562
|
CLASS_NAME: "OpenLayers.Geometry.Collection"
|
563
|
});
|