Project

General

Profile

Download (18.2 KB) Statistics
| Branch: | Tag: | Revision:
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
});
(1-1/9)