/* Copyright (c) 2006-2010 by OpenLayers Contributors (see authors.txt for
* full list of contributors). Published under the Clear BSD license.
* See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Console.js
*/
/**
* Class: OpenLayers.Bounds
* Instances of this class represent bounding boxes. Data stored as left,
* bottom, right, top floats. All values are initialized to null, however,
* you should make sure you set them before using the bounds for anything.
*
* Possible use case:
* > bounds = new OpenLayers.Bounds();
* > bounds.extend(new OpenLayers.LonLat(4,5));
* > bounds.extend(new OpenLayers.LonLat(5,6));
* > bounds.toBBOX(); // returns 4,5,5,6
*/
OpenLayers.Bounds = OpenLayers.Class({
/**
* Property: left
* {Number} Minimum horizontal coordinate.
*/
left: null,
/**
* Property: bottom
* {Number} Minimum vertical coordinate.
*/
bottom: null,
/**
* Property: right
* {Number} Maximum horizontal coordinate.
*/
right: null,
/**
* Property: top
* {Number} Maximum vertical coordinate.
*/
top: null,
/**
* Property: centerLonLat
* {<OpenLayers.LonLat>} A cached center location. This should not be
* accessed directly. Use <getCenterLonLat> instead.
*/
centerLonLat: null,
/**
* Constructor: OpenLayers.Bounds
* Construct a new bounds object.
*
* Parameters:
* left - {Number} The left bounds of the box. Note that for width
* calculations, this is assumed to be less than the right value.
* bottom - {Number} The bottom bounds of the box. Note that for height
* calculations, this is assumed to be more than the top value.
* right - {Number} The right bounds.
* top - {Number} The top bounds.
*/
initialize: function(left, bottom, right, top) {
if (left != null) {
this.left = OpenLayers.Util.toFloat(left);
}
if (bottom != null) {
this.bottom = OpenLayers.Util.toFloat(bottom);
}
if (right != null) {
this.right = OpenLayers.Util.toFloat(right);
}
if (top != null) {
this.top = OpenLayers.Util.toFloat(top);
}
},
/**
* Method: clone
* Create a cloned instance of this bounds.
*
* Returns:
* {<OpenLayers.Bounds>} A fresh copy of the bounds
*/
clone:function() {
return new OpenLayers.Bounds(this.left, this.bottom,
this.right, this.top);
},
/**
* Method: equals
* Test a two bounds for equivalence.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*
* Returns:
* {Boolean} The passed-in bounds object has the same left,
* right, top, bottom components as this. Note that if bounds
* passed in is null, returns false.
*/
equals:function(bounds) {
var equals = false;
if (bounds != null) {
equals = ((this.left == bounds.left) &&
(this.right == bounds.right) &&
(this.top == bounds.top) &&
(this.bottom == bounds.bottom));
}
return equals;
},
/**
* APIMethod: toString
*
* Returns:
* {String} String representation of bounds object.
* (ex.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)
*/
toString:function() {
return ( "left-bottom=(" + this.left + "," + this.bottom + ")"
+ " right-top=(" + this.right + "," + this.top + ")" );
},
/**
* APIMethod: toArray
*
* Parameters:
* reverseAxisOrder - {Boolean} Should we reverse the axis order?
*
* Returns:
* {Array} array of left, bottom, right, top
*/
toArray: function(reverseAxisOrder) {
if (reverseAxisOrder === true) {
return [this.bottom, this.left, this.top, this.right];
} else {
return [this.left, this.bottom, this.right, this.top];
}
},
/**
* APIMethod: toBBOX
*
* Parameters:
* decimal - {Integer} How many significant digits in the bbox coords?
* Default is 6
* reverseAxisOrder - {Boolean} Should we reverse the axis order?
*
* Returns:
* {String} Simple String representation of bounds object.
* (ex. <i>"5,42,10,45"</i>)
*/
toBBOX:function(decimal, reverseAxisOrder) {
if (decimal== null) {
decimal = 6;
}
var mult = Math.pow(10, decimal);
var xmin = Math.round(this.left * mult) / mult;
var ymin = Math.round(this.bottom * mult) / mult;
var xmax = Math.round(this.right * mult) / mult;
var ymax = Math.round(this.top * mult) / mult;
if (reverseAxisOrder === true) {
return ymin + "," + xmin + "," + ymax + "," + xmax;
} else {
return xmin + "," + ymin + "," + xmax + "," + ymax;
}
},
/**
* APIMethod: toGeometry
* Create a new polygon geometry based on this bounds.
*
* Returns:
* {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
* of this bounds.
*/
toGeometry: function() {
return new OpenLayers.Geometry.Polygon([
new OpenLayers.Geometry.LinearRing([
new OpenLayers.Geometry.Point(this.left, this.bottom),
new OpenLayers.Geometry.Point(this.right, this.bottom),
new OpenLayers.Geometry.Point(this.right, this.top),
new OpenLayers.Geometry.Point(this.left, this.top)
])
]);
},
/**
* APIMethod: getWidth
*
* Returns:
* {Float} The width of the bounds
*/
getWidth:function() {
return (this.right - this.left);
},
/**
* APIMethod: getHeight
*
* Returns:
* {Float} The height of the bounds (top minus bottom).
*/
getHeight:function() {
return (this.top - this.bottom);
},
/**
* APIMethod: getSize
*
* Returns:
* {<OpenLayers.Size>} The size of the box.
*/
getSize:function() {
return new OpenLayers.Size(this.getWidth(), this.getHeight());
},
/**
* APIMethod: getCenterPixel
*
* Returns:
* {<OpenLayers.Pixel>} The center of the bounds in pixel space.
*/
getCenterPixel:function() {
return new OpenLayers.Pixel( (this.left + this.right) / 2,
(this.bottom + this.top) / 2);
},
/**
* APIMethod: getCenterLonLat
*
* Returns:
* {<OpenLayers.LonLat>} The center of the bounds in map space.
*/
getCenterLonLat:function() {
if(!this.centerLonLat) {
this.centerLonLat = new OpenLayers.LonLat(
(this.left + this.right) / 2, (this.bottom + this.top) / 2
);
}
return this.centerLonLat;
},
/**
* Method: scale
* Scales the bounds around a pixel or lonlat. Note that the new
* bounds may return non-integer properties, even if a pixel
* is passed.
*
* Parameters:
* ratio - {Float}
* origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
* Default is center.
*
* Returns:
* {<OpenLayers.Bound>} A new bounds that is scaled by ratio
* from origin.
*/
scale: function(ratio, origin){
if(origin == null){
origin = this.getCenterLonLat();
}
var origx,origy;
// get origin coordinates
if(origin.CLASS_NAME == "OpenLayers.LonLat"){
origx = origin.lon;
origy = origin.lat;
} else {
origx = origin.x;
origy = origin.y;
}
var left = (this.left - origx) * ratio + origx;
var bottom = (this.bottom - origy) * ratio + origy;
var right = (this.right - origx) * ratio + origx;
var top = (this.top - origy) * ratio + origy;
return new OpenLayers.Bounds(left, bottom, right, top);
},
/**
* APIMethod: add
*
* Parameters:
* x - {Float}
* y - {Float}
*
* Returns:
* {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
* this, but shifted by the passed-in x and y values.
*/
add:function(x, y) {
if ( (x == null) || (y == null) ) {
var msg = OpenLayers.i18n("boundsAddError");
OpenLayers.Console.error(msg);
return null;
}
return new OpenLayers.Bounds(this.left + x, this.bottom + y,
this.right + x, this.top + y);
},
/**
* APIMethod: extend
* Extend the bounds to include the point, lonlat, or bounds specified.
* Note, this function assumes that left < right and bottom < top.
*
* Parameters:
* object - {Object} Can be LonLat, Point, or Bounds
*/
extend:function(object) {
var bounds = null;
if (object) {
// clear cached center location
switch(object.CLASS_NAME) {
case "OpenLayers.LonLat":
bounds = new OpenLayers.Bounds(object.lon, object.lat,
object.lon, object.lat);
break;
case "OpenLayers.Geometry.Point":
bounds = new OpenLayers.Bounds(object.x, object.y,
object.x, object.y);
break;
case "OpenLayers.Bounds":
bounds = object;
break;
}
if (bounds) {
this.centerLonLat = null;
if ( (this.left == null) || (bounds.left < this.left)) {
this.left = bounds.left;
}
if ( (this.bottom == null) || (bounds.bottom < this.bottom) ) {
this.bottom = bounds.bottom;
}
if ( (this.right == null) || (bounds.right > this.right) ) {
this.right = bounds.right;
}
if ( (this.top == null) || (bounds.top > this.top) ) {
this.top = bounds.top;
}
}
}
},
/**
* APIMethod: containsLonLat
*
* Parameters:
* ll - {<OpenLayers.LonLat>}
* inclusive - {Boolean} Whether or not to include the border.
* Default is true.
*
* Returns:
* {Boolean} The passed-in lonlat is within this bounds.
*/
containsLonLat:function(ll, inclusive) {
return this.contains(ll.lon, ll.lat, inclusive);
},
/**
* APIMethod: containsPixel
*
* Parameters:
* px - {<OpenLayers.Pixel>}
* inclusive - {Boolean} Whether or not to include the border. Default is
* true.
*
* Returns:
* {Boolean} The passed-in pixel is within this bounds.
*/
containsPixel:function(px, inclusive) {
return this.contains(px.x, px.y, inclusive);
},
/**
* APIMethod: contains
*
* Parameters:
* x - {Float}
* y - {Float}
* inclusive - {Boolean} Whether or not to include the border. Default is
* true.
*
* Returns:
* {Boolean} Whether or not the passed-in coordinates are within this
* bounds.
*/
contains:function(x, y, inclusive) {
//set default
if (inclusive == null) {
inclusive = true;
}
if (x == null || y == null) {
return false;
}
x = OpenLayers.Util.toFloat(x);
y = OpenLayers.Util.toFloat(y);
var contains = false;
if (inclusive) {
contains = ((x >= this.left) && (x <= this.right) &&
(y >= this.bottom) && (y <= this.top));
} else {
contains = ((x > this.left) && (x < this.right) &&
(y > this.bottom) && (y < this.top));
}
return contains;
},
/**
* APIMethod: intersectsBounds
* Determine whether the target bounds intersects this bounds. Bounds are
* considered intersecting if any of their edges intersect or if one
* bounds contains the other.
*
* Parameters:
* bounds - {<OpenLayers.Bounds>} The target bounds.
* inclusive - {Boolean} Treat coincident borders as intersecting. Default
* is true. If false, bounds that do not overlap but only touch at the
* border will not be considered as intersecting.
*
* Returns:
* {Boolean} The passed-in bounds object intersects this bounds.
*/
intersectsBounds:function(bounds, inclusive) {
if (inclusive == null) {
inclusive = true;
}
var intersects = false;
var mightTouch = (
this.left == bounds.right ||
this.right == bounds.left ||
this.top == bounds.bottom ||
this.bottom == bounds.top
);
// if the two bounds only touch at an edge, and inclusive is false,
// then the bounds don't *really* intersect.
if (inclusive || !mightTouch) {
// otherwise, if one of the boundaries even partially contains another,
// inclusive of the edges, then they do intersect.
var inBottom = (
((bounds.bottom >= this.bottom) && (bounds.bottom <= this.top)) ||
((this.bottom >= bounds.bottom) && (this.bottom <= bounds.top))
);
var inTop = (
((bounds.top >= this.bottom) && (bounds.top <= this.top)) ||
((this.top > bounds.bottom) && (this.top < bounds.top))
);
var inLeft = (
((bounds.left >= this.left) && (bounds.left <= this.right)) ||
((this.left >= bounds.left) && (this.left <= bounds.right))
);
var inRight = (
((bounds.right >= this.left) && (bounds.right <= this.right)) ||
((this.right >= bounds.left) && (this.right <= bounds.right))
);
intersects = ((inBottom || inTop) && (inLeft || inRight));
}
return intersects;
},
/**
* APIMethod: containsBounds
* Determine whether the target bounds is contained within this bounds.
*
* bounds - {<OpenLayers.Bounds>} The target bounds.
* partial - {Boolean} If any of the target corners is within this bounds
* consider the bounds contained. Default is false. If false, the
* entire target bounds must be contained within this bounds.
* inclusive - {Boolean} Treat shared edges as contained. Default is
* true.
*
* Returns:
* {Boolean} The passed-in bounds object is contained within this bounds.
*/
containsBounds:function(bounds, partial, inclusive) {
if (partial == null) {
partial = false;
}
if (inclusive == null) {
inclusive = true;
}
var bottomLeft = this.contains(bounds.left, bounds.bottom, inclusive);
var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
var topLeft = this.contains(bounds.left, bounds.top, inclusive);
var topRight = this.contains(bounds.right, bounds.top, inclusive);
return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
: (bottomLeft && bottomRight && topLeft && topRight);
},
/**
* APIMethod: determineQuadrant
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
*
* Returns:
* {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
* coordinate lies.
*/
determineQuadrant: function(lonlat) {
var quadrant = "";
var center = this.getCenterLonLat();
quadrant += (lonlat.lat < center.lat) ? "b" : "t";
quadrant += (lonlat.lon < center.lon) ? "l" : "r";
return quadrant;
},
/**
* APIMethod: transform
* Transform the Bounds object from source to dest.
*
* Parameters:
* source - {<OpenLayers.Projection>} Source projection.
* dest - {<OpenLayers.Projection>} Destination projection.
*
* Returns:
* {<OpenLayers.Bounds>} Itself, for use in chaining operations.
*/
transform: function(source, dest) {
// clear cached center location
this.centerLonLat = null;
var ll = OpenLayers.Projection.transform(
{'x': this.left, 'y': this.bottom}, source, dest);
var lr = OpenLayers.Projection.transform(
{'x': this.right, 'y': this.bottom}, source, dest);
var ul = OpenLayers.Projection.transform(
{'x': this.left, 'y': this.top}, source, dest);
var ur = OpenLayers.Projection.transform(
{'x': this.right, 'y': this.top}, source, dest);
this.left = Math.min(ll.x, ul.x);
this.bottom = Math.min(ll.y, lr.y);
this.right = Math.max(lr.x, ur.x);
this.top = Math.max(ul.y, ur.y);
return this;
},
/**
* APIMethod: wrapDateLine
*
* Parameters:
* maxExtent - {<OpenLayers.Bounds>}
* options - {Object} Some possible options are:
* leftTolerance - {float} Allow for a margin of error
* with the 'left' value of this
* bound.
* Default is 0.
* rightTolerance - {float} Allow for a margin of error
* with the 'right' value of
* this bound.
* Default is 0.
*
* Returns:
* {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the
* "dateline" (as specified by the borders of
* maxExtent). Note that this function only returns
* a different bounds value if this bounds is
* *entirely* outside of the maxExtent. If this
* bounds straddles the dateline (is part in/part
* out of maxExtent), the returned bounds will be
* merely a copy of this one.
*/
wrapDateLine: function(maxExtent, options) {
options = options || {};
var leftTolerance = options.leftTolerance || 0;
var rightTolerance = options.rightTolerance || 0;
var newBounds = this.clone();
if (maxExtent) {
//shift right?
while ( newBounds.left < maxExtent.left &&
(newBounds.right - rightTolerance) <= maxExtent.left ) {
newBounds = newBounds.add(maxExtent.getWidth(), 0);
}
//shift left?
while ( (newBounds.left + leftTolerance) >= maxExtent.right &&
newBounds.right > maxExtent.right ) {
newBounds = newBounds.add(-maxExtent.getWidth(), 0);
}
}
return newBounds;
},
CLASS_NAME: "OpenLayers.Bounds"
});
/**
* APIFunction: fromString
* Alternative constructor that builds a new OpenLayers.Bounds from a
* parameter string
*
* Parameters:
* str - {String}Comma-separated bounds string. (ex. <i>"5,42,10,45"</i>)
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the
* passed-in String.
*/
OpenLayers.Bounds.fromString = function(str) {
var bounds = str.split(",");
return OpenLayers.Bounds.fromArray(bounds);
};
/**
* APIFunction: fromArray
* Alternative constructor that builds a new OpenLayers.Bounds
* from an array
*
* Parameters:
* bbox - {Array(Float)} Array of bounds values (ex. <i>[5,42,10,45]</i>)
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
*/
OpenLayers.Bounds.fromArray = function(bbox) {
return new OpenLayers.Bounds(parseFloat(bbox[0]),
parseFloat(bbox[1]),
parseFloat(bbox[2]),
parseFloat(bbox[3]));
};
/**
* APIFunction: fromSize
* Alternative constructor that builds a new OpenLayers.Bounds
* from a size
*
* Parameters:
* size - {<OpenLayers.Size>}
*
* Returns:
* {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
*/
OpenLayers.Bounds.fromSize = function(size) {
return new OpenLayers.Bounds(0,
size.h,
size.w,
0);
};
/**
* Function: oppositeQuadrant
* Get the opposite quadrant for a given quadrant string.
*
* Parameters:
* quadrant - {String} two character quadrant shortstring
*
* Returns:
* {String} The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if
* you pass in "bl" it returns "tr", if you pass in "br" it
* returns "tl", etc.
*/
OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
var opp = "";
opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
return opp;
};