/* 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 * {} A cached center location. This should not be * accessed directly. Use 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: * {} 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 - {} * * 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."left-bottom=(5,42) right-top=(10,45)") */ 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. "5,42,10,45") */ 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: * {} 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: * {} The size of the box. */ getSize:function() { return new OpenLayers.Size(this.getWidth(), this.getHeight()); }, /** * APIMethod: getCenterPixel * * Returns: * {} 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: * {} 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 - { or } * Default is center. * * Returns: * {} 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: * {} 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 - {} * 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 - {} * 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 - {} 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 - {} 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 - {} * * 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 - {} Source projection. * dest - {} Destination projection. * * Returns: * {} 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 - {} * 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: * {} 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. "5,42,10,45") * * Returns: * {} 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. [5,42,10,45]) * * Returns: * {} 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 - {} * * Returns: * {} 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; };