]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/BaseTypes/Bounds.js
initial commit
[syp.git] / openlayers / lib / OpenLayers / BaseTypes / Bounds.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/Console.js
7  */
8
9 /**
10  * Class: OpenLayers.Bounds
11  * Instances of this class represent bounding boxes.  Data stored as left,
12  * bottom, right, top floats. All values are initialized to null, however,
13  * you should make sure you set them before using the bounds for anything.
14  * 
15  * Possible use case:
16  * > bounds = new OpenLayers.Bounds();
17  * > bounds.extend(new OpenLayers.LonLat(4,5));
18  * > bounds.extend(new OpenLayers.LonLat(5,6));
19  * > bounds.toBBOX(); // returns 4,5,5,6
20  */
21 OpenLayers.Bounds = OpenLayers.Class({
22
23     /**
24      * Property: left
25      * {Number} Minimum horizontal coordinate.
26      */
27     left: null,
28
29     /**
30      * Property: bottom
31      * {Number} Minimum vertical coordinate.
32      */
33     bottom: null,
34
35     /**
36      * Property: right
37      * {Number} Maximum horizontal coordinate.
38      */
39     right: null,
40
41     /**
42      * Property: top
43      * {Number} Maximum vertical coordinate.
44      */
45     top: null,
46     
47     /**
48      * Property: centerLonLat
49      * {<OpenLayers.LonLat>} A cached center location.  This should not be
50      *     accessed directly.  Use <getCenterLonLat> instead.
51      */
52     centerLonLat: null,
53
54     /**
55      * Constructor: OpenLayers.Bounds
56      * Construct a new bounds object.
57      *
58      * Parameters:
59      * left - {Number} The left bounds of the box.  Note that for width
60      *        calculations, this is assumed to be less than the right value.
61      * bottom - {Number} The bottom bounds of the box.  Note that for height
62      *          calculations, this is assumed to be more than the top value.
63      * right - {Number} The right bounds.
64      * top - {Number} The top bounds.
65      */
66     initialize: function(left, bottom, right, top) {
67         if (left != null) {
68             this.left = OpenLayers.Util.toFloat(left);
69         }
70         if (bottom != null) {
71             this.bottom = OpenLayers.Util.toFloat(bottom);
72         }
73         if (right != null) {
74             this.right = OpenLayers.Util.toFloat(right);
75         }
76         if (top != null) {
77             this.top = OpenLayers.Util.toFloat(top);
78         }
79     },
80
81     /**
82      * Method: clone
83      * Create a cloned instance of this bounds.
84      *
85      * Returns:
86      * {<OpenLayers.Bounds>} A fresh copy of the bounds
87      */
88     clone:function() {
89         return new OpenLayers.Bounds(this.left, this.bottom, 
90                                      this.right, this.top);
91     },
92
93     /**
94      * Method: equals
95      * Test a two bounds for equivalence.
96      *
97      * Parameters:
98      * bounds - {<OpenLayers.Bounds>}
99      *
100      * Returns:
101      * {Boolean} The passed-in bounds object has the same left,
102      *           right, top, bottom components as this.  Note that if bounds 
103      *           passed in is null, returns false.
104      */
105     equals:function(bounds) {
106         var equals = false;
107         if (bounds != null) {
108             equals = ((this.left == bounds.left) && 
109                       (this.right == bounds.right) &&
110                       (this.top == bounds.top) && 
111                       (this.bottom == bounds.bottom));
112         }
113         return equals;
114     },
115
116     /** 
117      * APIMethod: toString
118      * 
119      * Returns:
120      * {String} String representation of bounds object. 
121      *          (ex.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)
122      */
123     toString:function() {
124         return ( "left-bottom=(" + this.left + "," + this.bottom + ")"
125                  + " right-top=(" + this.right + "," + this.top + ")" );
126     },
127
128     /**
129      * APIMethod: toArray
130      *
131      * Returns:
132      * {Array} array of left, bottom, right, top
133      */
134     toArray: function() {
135         return [this.left, this.bottom, this.right, this.top];
136     },    
137
138     /** 
139      * APIMethod: toBBOX
140      * 
141      * Parameters:
142      * decimal - {Integer} How many significant digits in the bbox coords?
143      *                     Default is 6
144      * 
145      * Returns:
146      * {String} Simple String representation of bounds object.
147      *          (ex. <i>"5,42,10,45"</i>)
148      */
149     toBBOX:function(decimal) {
150         if (decimal== null) {
151             decimal = 6; 
152         }
153         var mult = Math.pow(10, decimal);
154         var bbox = Math.round(this.left * mult) / mult + "," + 
155                    Math.round(this.bottom * mult) / mult + "," + 
156                    Math.round(this.right * mult) / mult + "," + 
157                    Math.round(this.top * mult) / mult;
158
159         return bbox;
160     },
161  
162     /**
163      * APIMethod: toGeometry
164      * Create a new polygon geometry based on this bounds.
165      *
166      * Returns:
167      * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
168      *     of this bounds.
169      */
170     toGeometry: function() {
171         return new OpenLayers.Geometry.Polygon([
172             new OpenLayers.Geometry.LinearRing([
173                 new OpenLayers.Geometry.Point(this.left, this.bottom),
174                 new OpenLayers.Geometry.Point(this.right, this.bottom),
175                 new OpenLayers.Geometry.Point(this.right, this.top),
176                 new OpenLayers.Geometry.Point(this.left, this.top)
177             ])
178         ]);
179     },
180     
181     /**
182      * APIMethod: getWidth
183      * 
184      * Returns:
185      * {Float} The width of the bounds
186      */
187     getWidth:function() {
188         return (this.right - this.left);
189     },
190
191     /**
192      * APIMethod: getHeight
193      * 
194      * Returns:
195      * {Float} The height of the bounds (top minus bottom).
196      */
197     getHeight:function() {
198         return (this.top - this.bottom);
199     },
200
201     /**
202      * APIMethod: getSize
203      * 
204      * Returns:
205      * {<OpenLayers.Size>} The size of the box.
206      */
207     getSize:function() {
208         return new OpenLayers.Size(this.getWidth(), this.getHeight());
209     },
210
211     /**
212      * APIMethod: getCenterPixel
213      * 
214      * Returns:
215      * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
216      */
217     getCenterPixel:function() {
218         return new OpenLayers.Pixel( (this.left + this.right) / 2,
219                                      (this.bottom + this.top) / 2);
220     },
221
222     /**
223      * APIMethod: getCenterLonLat
224      * 
225      * Returns:
226      * {<OpenLayers.LonLat>} The center of the bounds in map space.
227      */
228     getCenterLonLat:function() {
229         if(!this.centerLonLat) {
230             this.centerLonLat = new OpenLayers.LonLat(
231                 (this.left + this.right) / 2, (this.bottom + this.top) / 2
232             );
233         }
234         return this.centerLonLat;
235     },
236
237     /**
238      * Method: scale
239      * Scales the bounds around a pixel or lonlat. Note that the new 
240      *     bounds may return non-integer properties, even if a pixel
241      *     is passed. 
242      * 
243      * Parameters:
244      * ratio - {Float} 
245      * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
246      *          Default is center.
247      *
248      * Returns:
249      * {<OpenLayers.Bound>} A new bounds that is scaled by ratio
250      *                      from origin.
251      */
252
253     scale: function(ratio, origin){
254         if(origin == null){
255             origin = this.getCenterLonLat();
256         }
257
258         var bounds = [];
259         
260         var origx,origy;
261
262         // get origin coordinates
263         if(origin.CLASS_NAME == "OpenLayers.LonLat"){
264             origx = origin.lon;
265             origy = origin.lat;
266         } else {
267             origx = origin.x;
268             origy = origin.y;
269         }
270
271         var left = (this.left - origx) * ratio + origx;
272         var bottom = (this.bottom - origy) * ratio + origy;
273         var right = (this.right - origx) * ratio + origx;
274         var top = (this.top - origy) * ratio + origy;
275         
276         return new OpenLayers.Bounds(left, bottom, right, top);
277     },
278
279     /**
280      * APIMethod: add
281      * 
282      * Parameters:
283      * x - {Float}
284      * y - {Float}
285      * 
286      * Returns:
287      * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
288      *     this, but shifted by the passed-in x and y values.
289      */
290     add:function(x, y) {
291         if ( (x == null) || (y == null) ) {
292             var msg = OpenLayers.i18n("boundsAddError");
293             OpenLayers.Console.error(msg);
294             return null;
295         }
296         return new OpenLayers.Bounds(this.left + x, this.bottom + y,
297                                      this.right + x, this.top + y);
298     },
299     
300     /**
301      * APIMethod: extend
302      * Extend the bounds to include the point, lonlat, or bounds specified.
303      *     Note, this function assumes that left < right and bottom < top.
304      * 
305      * Parameters: 
306      * object - {Object} Can be LonLat, Point, or Bounds
307      */
308     extend:function(object) {
309         var bounds = null;
310         if (object) {
311             // clear cached center location
312             switch(object.CLASS_NAME) {
313                 case "OpenLayers.LonLat":    
314                     bounds = new OpenLayers.Bounds(object.lon, object.lat,
315                                                     object.lon, object.lat);
316                     break;
317                 case "OpenLayers.Geometry.Point":
318                     bounds = new OpenLayers.Bounds(object.x, object.y,
319                                                     object.x, object.y);
320                     break;
321                     
322                 case "OpenLayers.Bounds":    
323                     bounds = object;
324                     break;
325             }
326     
327             if (bounds) {
328                 this.centerLonLat = null;
329                 if ( (this.left == null) || (bounds.left < this.left)) {
330                     this.left = bounds.left;
331                 }
332                 if ( (this.bottom == null) || (bounds.bottom < this.bottom) ) {
333                     this.bottom = bounds.bottom;
334                 } 
335                 if ( (this.right == null) || (bounds.right > this.right) ) {
336                     this.right = bounds.right;
337                 }
338                 if ( (this.top == null) || (bounds.top > this.top) ) { 
339                     this.top = bounds.top;
340                 }
341             }
342         }
343     },
344
345     /**
346      * APIMethod: containsLonLat
347      * 
348      * Parameters:
349      * ll - {<OpenLayers.LonLat>}
350      * inclusive - {Boolean} Whether or not to include the border.
351      *     Default is true.
352      *
353      * Returns:
354      * {Boolean} The passed-in lonlat is within this bounds.
355      */
356     containsLonLat:function(ll, inclusive) {
357         return this.contains(ll.lon, ll.lat, inclusive);
358     },
359
360     /**
361      * APIMethod: containsPixel
362      * 
363      * Parameters:
364      * px - {<OpenLayers.Pixel>}
365      * inclusive - {Boolean} Whether or not to include the border. Default is
366      *     true.
367      *
368      * Returns:
369      * {Boolean} The passed-in pixel is within this bounds.
370      */
371     containsPixel:function(px, inclusive) {
372         return this.contains(px.x, px.y, inclusive);
373     },
374     
375     /**
376      * APIMethod: contains
377      * 
378      * Parameters:
379      * x - {Float}
380      * y - {Float}
381      * inclusive - {Boolean} Whether or not to include the border. Default is
382      *     true.
383      *
384      * Returns:
385      * {Boolean} Whether or not the passed-in coordinates are within this
386      *     bounds.
387      */
388     contains:function(x, y, inclusive) {
389         //set default
390         if (inclusive == null) {
391             inclusive = true;
392         }
393
394         if (x == null || y == null) {
395             return false;
396         }
397
398         x = OpenLayers.Util.toFloat(x);
399         y = OpenLayers.Util.toFloat(y);
400
401         var contains = false;
402         if (inclusive) {
403             contains = ((x >= this.left) && (x <= this.right) && 
404                         (y >= this.bottom) && (y <= this.top));
405         } else {
406             contains = ((x > this.left) && (x < this.right) && 
407                         (y > this.bottom) && (y < this.top));
408         }              
409         return contains;
410     },
411
412     /**
413      * APIMethod: intersectsBounds
414      * Determine whether the target bounds intersects this bounds.  Bounds are
415      *     considered intersecting if any of their edges intersect or if one
416      *     bounds contains the other.
417      * 
418      * Parameters:
419      * bounds - {<OpenLayers.Bounds>} The target bounds.
420      * inclusive - {Boolean} Treat coincident borders as intersecting.  Default
421      *     is true.  If false, bounds that do not overlap but only touch at the
422      *     border will not be considered as intersecting.
423      *
424      * Returns:
425      * {Boolean} The passed-in bounds object intersects this bounds.
426      */
427     intersectsBounds:function(bounds, inclusive) {
428         if (inclusive == null) {
429             inclusive = true;
430         }
431         var intersects = false;
432         var mightTouch = (
433             this.left == bounds.right ||
434             this.right == bounds.left ||
435             this.top == bounds.bottom ||
436             this.bottom == bounds.top
437         );
438         
439         // if the two bounds only touch at an edge, and inclusive is false,
440         // then the bounds don't *really* intersect.
441         if (inclusive || !mightTouch) {
442             // otherwise, if one of the boundaries even partially contains another,
443             // inclusive of the edges, then they do intersect.
444             var inBottom = (
445                 ((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) ||
446                 ((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top))
447             );
448             var inTop = (
449                 ((bounds.top >= this.bottom) && (bounds.top <= this.top)) ||
450                 ((this.top > bounds.bottom) && (this.top < bounds.top))
451             );
452             var inLeft = (
453                 ((bounds.left >= this.left) && (bounds.left <= this.right)) ||
454                 ((this.left >= bounds.left) && (this.left <= bounds.right))
455             );
456             var inRight = (
457                 ((bounds.right >= this.left) && (bounds.right <= this.right)) ||
458                 ((this.right >= bounds.left) && (this.right <= bounds.right))
459             );
460             intersects = ((inBottom || inTop) && (inLeft || inRight));
461         }
462         return intersects;
463     },
464     
465     /**
466      * APIMethod: containsBounds
467      * Determine whether the target bounds is contained within this bounds.
468      * 
469      * bounds - {<OpenLayers.Bounds>} The target bounds.
470      * partial - {Boolean} If any of the target corners is within this bounds
471      *     consider the bounds contained.  Default is false.  If true, the
472      *     entire target bounds must be contained within this bounds.
473      * inclusive - {Boolean} Treat shared edges as contained.  Default is
474      *     true.
475      *
476      * Returns:
477      * {Boolean} The passed-in bounds object is contained within this bounds. 
478      */
479     containsBounds:function(bounds, partial, inclusive) {
480         if (partial == null) {
481             partial = false;
482         }
483         if (inclusive == null) {
484             inclusive = true;
485         }
486         var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
487         var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
488         var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
489         var topRight = this.contains(bounds.right, bounds.top, inclusive);
490         
491         return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
492                          : (bottomLeft && bottomRight && topLeft && topRight);
493     },
494
495     /** 
496      * APIMethod: determineQuadrant
497      * 
498      * Parameters:
499      * lonlat - {<OpenLayers.LonLat>}
500      * 
501      * Returns:
502      * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
503      *     coordinate lies.
504      */
505     determineQuadrant: function(lonlat) {
506     
507         var quadrant = "";
508         var center = this.getCenterLonLat();
509         
510         quadrant += (lonlat.lat < center.lat) ? "b" : "t";
511         quadrant += (lonlat.lon < center.lon) ? "l" : "r";
512     
513         return quadrant; 
514     },
515     
516     /**
517      * APIMethod: transform
518      * Transform the Bounds object from source to dest. 
519      *
520      * Parameters: 
521      * source - {<OpenLayers.Projection>} Source projection. 
522      * dest   - {<OpenLayers.Projection>} Destination projection. 
523      *
524      * Returns:
525      * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
526      */
527     transform: function(source, dest) {
528         // clear cached center location
529         this.centerLonLat = null;
530         var ll = OpenLayers.Projection.transform(
531             {'x': this.left, 'y': this.bottom}, source, dest);
532         var lr = OpenLayers.Projection.transform(
533             {'x': this.right, 'y': this.bottom}, source, dest);
534         var ul = OpenLayers.Projection.transform(
535             {'x': this.left, 'y': this.top}, source, dest);
536         var ur = OpenLayers.Projection.transform(
537             {'x': this.right, 'y': this.top}, source, dest);
538         this.left   = Math.min(ll.x, ul.x);
539         this.bottom = Math.min(ll.y, lr.y);
540         this.right  = Math.max(lr.x, ur.x);
541         this.top    = Math.max(ul.y, ur.y);
542         return this;
543     },
544
545     /**
546      * APIMethod: wrapDateLine
547      *  
548      * Parameters:
549      * maxExtent - {<OpenLayers.Bounds>}
550      * options - {Object} Some possible options are:
551      *                    leftTolerance - {float} Allow for a margin of error 
552      *                                            with the 'left' value of this 
553      *                                            bound.
554      *                                            Default is 0.
555      *                    rightTolerance - {float} Allow for a margin of error 
556      *                                             with the 'right' value of 
557      *                                             this bound.
558      *                                             Default is 0.
559      * 
560      * Returns:
561      * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the 
562      *                       "dateline" (as specified by the borders of 
563      *                       maxExtent). Note that this function only returns 
564      *                       a different bounds value if this bounds is 
565      *                       *entirely* outside of the maxExtent. If this 
566      *                       bounds straddles the dateline (is part in/part 
567      *                       out of maxExtent), the returned bounds will be 
568      *                       merely a copy of this one.
569      */
570     wrapDateLine: function(maxExtent, options) {    
571         options = options || {};
572         
573         var leftTolerance = options.leftTolerance || 0;
574         var rightTolerance = options.rightTolerance || 0;
575
576         var newBounds = this.clone();
577     
578         if (maxExtent) {
579
580            //shift right?
581            while ( newBounds.left < maxExtent.left && 
582                    (newBounds.right - rightTolerance) <= maxExtent.left ) { 
583                 newBounds = newBounds.add(maxExtent.getWidth(), 0);
584            }
585
586            //shift left?
587            while ( (newBounds.left + leftTolerance) >= maxExtent.right && 
588                    newBounds.right > maxExtent.right ) { 
589                 newBounds = newBounds.add(-maxExtent.getWidth(), 0);
590            }
591         }
592                 
593         return newBounds;
594     },
595
596     CLASS_NAME: "OpenLayers.Bounds"
597 });
598
599 /** 
600  * APIFunction: fromString
601  * Alternative constructor that builds a new OpenLayers.Bounds from a 
602  *     parameter string
603  * 
604  * Parameters: 
605  * str - {String}Comma-separated bounds string. (ex. <i>"5,42,10,45"</i>)
606  * 
607  * Returns:
608  * {<OpenLayers.Bounds>} New bounds object built from the 
609  *                       passed-in String.
610  */
611 OpenLayers.Bounds.fromString = function(str) {
612     var bounds = str.split(",");
613     return OpenLayers.Bounds.fromArray(bounds);
614 };
615
616 /** 
617  * APIFunction: fromArray
618  * Alternative constructor that builds a new OpenLayers.Bounds
619  *     from an array
620  * 
621  * Parameters:
622  * bbox - {Array(Float)} Array of bounds values (ex. <i>[5,42,10,45]</i>)
623  *
624  * Returns:
625  * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
626  */
627 OpenLayers.Bounds.fromArray = function(bbox) {
628     return new OpenLayers.Bounds(parseFloat(bbox[0]),
629                                  parseFloat(bbox[1]),
630                                  parseFloat(bbox[2]),
631                                  parseFloat(bbox[3]));
632 };
633
634 /** 
635  * APIFunction: fromSize
636  * Alternative constructor that builds a new OpenLayers.Bounds
637  *     from a size
638  * 
639  * Parameters:
640  * size - {<OpenLayers.Size>} 
641  *
642  * Returns:
643  * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
644  */
645 OpenLayers.Bounds.fromSize = function(size) {
646     return new OpenLayers.Bounds(0,
647                                  size.h,
648                                  size.w,
649                                  0);
650 };
651
652 /**
653  * Function: oppositeQuadrant
654  * Get the opposite quadrant for a given quadrant string.
655  *
656  * Parameters:
657  * quadrant - {String} two character quadrant shortstring
658  *
659  * Returns:
660  * {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if 
661  *          you pass in "bl" it returns "tr", if you pass in "br" it 
662  *          returns "tl", etc.
663  */
664 OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
665     var opp = "";
666     
667     opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
668     opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
669     
670     return opp;
671 };