1 /* Copyright (c) 2006 MetaCarta, Inc., published under the BSD license.
 
   2  * See http://svn.openlayers.org/trunk/openlayers/license.txt for the full
 
   3  * text of the license. */
 
   7 OpenLayers.Util = new Object();
 
  13 * @class This class represents a screen coordinate, in x and y coordinates
 
  15 OpenLayers.Pixel = Class.create();
 
  16 OpenLayers.Pixel.prototype = {
 
  30     initialize: function(x, y) {
 
  36     * @return string representation of Pixel. ex: "x=200.4,y=242.2"
 
  40         return ("x=" + this.x + ",y=" + this.y);
 
  44     * @type OpenLayers.Pixel
 
  47         return new OpenLayers.Pixel(this.x, this.y); 
 
  51     * @param {OpenLayers.Pixel} px
 
  53     * @return whether or not the point passed in as parameter is equal to this
 
  54     *          note that if px passed in is null, returns false
 
  60             equals = ((this.x == px.x) && (this.y == px.y));
 
  69     * @return a new Pixel with this pixel's x&y augmented by the 
 
  71     * @type OpenLayers.Pixel
 
  74         return new OpenLayers.Pixel(this.x + x, this.y + y);
 
  78     * @param {OpenLayers.Pixel} px
 
  80     * @return a new Pixel with this pixel's x&y augmented by the 
 
  81     *         x&y values of the pixel passed in.
 
  82     * @type OpenLayers.Pixel
 
  85         return this.add(px.x, px.y);                
 
  88     /** @final @type str */
 
  89     CLASS_NAME: "OpenLayers.Pixel"
 
  94 * @class This class represents a width and height pair
 
  96 OpenLayers.Size = Class.create();
 
  97 OpenLayers.Size.prototype = {
 
 112     initialize: function(w, h) {
 
 118     * @return String representation of OpenLayers.Size object. 
 
 119     *         (ex. <i>"w=55,h=66"</i>)
 
 122     toString:function() {
 
 123         return ("w=" + this.w + ",h=" + this.h);
 
 127     * @return New OpenLayers.Size object with the same w and h values
 
 128     * @type OpenLayers.Size
 
 131         return new OpenLayers.Size(this.w, this.h);
 
 135     * @param {OpenLayers.Size} sz
 
 136     * @returns Boolean value indicating whether the passed-in OpenLayers.Size 
 
 137     *          object has the same w and h components as this
 
 138     *          note that if sz passed in is null, returns false
 
 142     equals:function(sz) {
 
 145             equals = ((this.w == sz.w) && (this.h == sz.h));
 
 150     /** @final @type String */
 
 151     CLASS_NAME: "OpenLayers.Size"
 
 155 * @class This class represents a longitude and latitude pair
 
 157 OpenLayers.LonLat = Class.create();
 
 158 OpenLayers.LonLat.prototype = {
 
 172     initialize: function(lon, lat) {
 
 178     * @return String representation of OpenLayers.LonLat object. 
 
 179     *         (ex. <i>"lon=5,lat=42"</i>)
 
 182     toString:function() {
 
 183         return ("lon=" + this.lon + ",lat=" + this.lat);
 
 187     * @return Shortened String representation of OpenLayers.LonLat object. 
 
 188     *         (ex. <i>"5, 42"</i>)
 
 191     toShortString:function() {
 
 192         return (this.lon + ", " + this.lat);
 
 196     * @return New OpenLayers.LonLat object with the same lon and lat values
 
 197     * @type OpenLayers.LonLat
 
 200         return new OpenLayers.LonLat(this.lon, this.lat);
 
 207     * @return A new OpenLayers.LonLat object with the lon and lat passed-in
 
 209     * @type OpenLayers.LonLat
 
 211     add:function(lon, lat) {
 
 212         return new OpenLayers.LonLat(this.lon + lon, this.lat + lat);
 
 216     * @param {OpenLayers.LonLat} ll
 
 217     * @returns Boolean value indicating whether the passed-in OpenLayers.LonLat
 
 218     *          object has the same lon and lat components as this
 
 219     *          note that if ll passed in is null, returns false
 
 223     equals:function(ll) {
 
 226             equals = ((this.lon == ll.lon) && (this.lat == ll.lat));
 
 231     /** @final @type String */
 
 232     CLASS_NAME: "OpenLayers.LonLat"
 
 235 /** Alternative constructor that builds a new OpenLayers.LonLat from a 
 
 240 * @param {String} str Comma-separated Lon,Lat coordinate string. 
 
 241 *                     (ex. <i>"5,40"</i>)
 
 243 * @returns New OpenLayers.LonLat object built from the passed-in String.
 
 244 * @type OpenLayers.LonLat
 
 246 OpenLayers.LonLat.fromString = function(str) {
 
 247     var pair = str.split(",");
 
 248     return new OpenLayers.LonLat(parseFloat(pair[0]), 
 
 249                                  parseFloat(pair[1]));
 
 256 * @class This class represents a bounding box. 
 
 257 *        Data stored as left, bottom, right, top floats
 
 259 OpenLayers.Bounds = Class.create();
 
 260 OpenLayers.Bounds.prototype = {
 
 277     * @param {float} left
 
 278     * @param {float} bottom
 
 279     * @param {float} right
 
 283     initialize: function(left, bottom, right, top) {
 
 285         this.bottom = bottom;
 
 291     * @returns A fresh copy of the bounds
 
 292     * @type OpenLayers.Bounds
 
 295         return new OpenLayers.Bounds(this.left, this.bottom, 
 
 296                                      this.right, this.top);
 
 300     * @param {OpenLayers.Bounds} bounds
 
 301     * @returns Boolean value indicating whether the passed-in OpenLayers.Bounds
 
 302     *          object has the same left, right, top, bottom components as this
 
 303     *           note that if bounds passed in is null, returns false
 
 307     equals:function(bounds) {
 
 309         if (bounds != null) {
 
 310             equals = ((this.left == bounds.left) && 
 
 311                       (this.right == bounds.right) &&
 
 312                       (this.top == bounds.top) && 
 
 313                       (this.bottom == bounds.bottom));
 
 319     * @return String representation of OpenLayers.Bounds object. 
 
 320     *         (ex.<i>"left-bottom=(5,42) right-top=(10,45)"</i>)
 
 324         return ( "left-bottom=(" + this.left + "," + this.bottom + ")"
 
 325                  + " right-top=(" + this.right + "," + this.top + ")" );
 
 329     * @return Simple String representation of OpenLayers.Bounds object.
 
 330     *         (ex. <i>"5,42,10,45"</i>)
 
 334         return (this.left + "," + this.bottom + ","
 
 335                 + this.right + "," + this.top);
 
 339     * @returns The width of the bounds
 
 342     getWidth:function() {
 
 343         return (this.right - this.left);
 
 347     * @returns The height of the bounds
 
 350     getHeight:function() {
 
 351         return (this.top - this.bottom);
 
 355     * @returns An OpenLayers.Size which represents the size of the box
 
 356     * @type OpenLayers.Size
 
 359         return new OpenLayers.Size(this.getWidth(), this.getHeight());
 
 363     * @returns An OpenLayers.Pixel which represents the center of the bounds
 
 364     * @type OpenLayers.Pixel
 
 366     getCenterPixel:function() {
 
 367         return new OpenLayers.Pixel( (this.left + this.right) / 2,
 
 368                                      (this.bottom + this.top) / 2);
 
 372     * @returns An OpenLayers.LonLat which represents the center of the bounds
 
 373     * @type OpenLayers.LonLat
 
 375     getCenterLonLat:function() {
 
 376         return new OpenLayers.LonLat( (this.left + this.right) / 2,
 
 377                                       (this.bottom + this.top) / 2);
 
 384     * @returns A new OpenLayers.Bounds whose coordinates are the same as this, 
 
 385     *          but shifted by the passed-in x and y values
 
 386     * @type OpenLayers.Bounds
 
 389         return new OpenLayers.Box(this.left + x, this.bottom + y,
 
 390                                   this.right + x, this.top + y);
 
 396     * @param {Boolean} inclusive Whether or not to include the border. 
 
 399     * @return Whether or not the passed-in coordinates are within this bounds
 
 402     contains:function(x, y, inclusive) {
 
 405         if (inclusive == null) {
 
 409         var contains = false;
 
 411             contains = ((x >= this.left) && (x <= this.right) && 
 
 412                         (y >= this.bottom) && (y <= this.top));
 
 414             contains = ((x > this.left) && (x < this.right) && 
 
 415                         (y > this.bottom) && (y < this.top));
 
 421     * @param {OpenLayers.Bounds} bounds
 
 422     * @param {Boolean} partial If true, only part of passed-in 
 
 423     *                          OpenLayers.Bounds needs be within this bounds. 
 
 424     *                          If false, the entire passed-in bounds must be
 
 425     *                          within. Default is false
 
 426     * @param {Boolean} inclusive Whether or not to include the border. 
 
 429     * @return Whether or not the passed-in OpenLayers.Bounds object is 
 
 430     *         contained within this bounds. 
 
 433     containsBounds:function(bounds, partial, inclusive) {
 
 436         if (partial == null) {
 
 439         if (inclusive == null) {
 
 449             inLeft = (bounds.left >= this.left) && (bounds.left <= this.right);
 
 450             inTop = (bounds.top >= this.bottom) && (bounds.top <= this.top);
 
 451             inRight= (bounds.right >= this.left) && (bounds.right <= this.right);
 
 452             inBottom = (bounds.bottom >= this.bottom) && (bounds.bottom <= this.top);
 
 454             inLeft = (bounds.left > this.left) && (bounds.left < this.right);
 
 455             inTop = (bounds.top > this.bottom) && (bounds.top < this.top);
 
 456             inRight= (bounds.right > this.left) && (bounds.right < this.right);
 
 457             inBottom = (bounds.bottom > this.bottom) && (bounds.bottom < this.top);
 
 460         return (partial) ? (inTop || inBottom) && (inLeft || inRight )
 
 461                          : (inTop && inLeft && inBottom && inRight);
 
 465      * @param {OpenLayers.LonLat} lonlat
 
 467      * @returns The quadrant ("br" "tr" "tl" "bl") of the bounds in which 
 
 468      *           the coordinate lies.
 
 471     determineQuadrant: function(lonlat) {
 
 474         var center = this.getCenterLonLat();
 
 476         quadrant += (lonlat.lat < center.lat) ? "b" : "t";
 
 477         quadrant += (lonlat.lon < center.lon) ? "l" : "r";
 
 482     /** @final @type String */
 
 483     CLASS_NAME: "OpenLayers.Bounds"
 
 486 /** Alternative constructor that builds a new OpenLayers.Bounds from a 
 
 491 * @param {String} str Comma-separated bounds string. (ex. <i>"5,42,10,45"</i>)
 
 493 * @returns New OpenLayers.Bounds object built from the passed-in String.
 
 494 * @type OpenLayers.Bounds
 
 496 OpenLayers.Bounds.fromString = function(str) {
 
 497     var bounds = str.split(",");
 
 498     return OpenLayers.Bounds.fromArray(bounds);
 
 501 /** Alternative constructor that builds a new OpenLayers.Bounds
 
 506 * @param {Array} bbox Array of bounds values (ex. <i>[5,42,10,45]</i>)
 
 508 * @returns New OpenLayers.Bounds object built from the passed-in Array.
 
 509 * @type OpenLayers.Bounds
 
 511 OpenLayers.Bounds.fromArray = function(bbox) {
 
 512     return new OpenLayers.Bounds(parseFloat(bbox[0]),
 
 515                                  parseFloat(bbox[3]));
 
 518 /** Alternative constructor that builds a new OpenLayers.Bounds
 
 519 *    from an OpenLayers.Size
 
 523 * @param {OpenLayers.Size} size
 
 525 * @returns New OpenLayers.Bounds object built with top and left set to 0 and
 
 526 *           bottom right taken from the passed-in OpenLayers.Size.
 
 527 * @type OpenLayers.Bounds
 
 529 OpenLayers.Bounds.fromSize = function(size) {
 
 530     return new OpenLayers.Bounds(0,
 
 536  * @param {String} quadrant 
 
 538  * @returns The opposing quadrant ("br" "tr" "tl" "bl"). For Example, if 
 
 539  *           you pass in "bl" it returns "tr", if you pass in "br" it 
 
 543 OpenLayers.Bounds.oppositeQuadrant = function(quadrant) {
 
 546     opp += (quadrant.charAt(0) == 't') ? 'b' : 't';
 
 547     opp += (quadrant.charAt(1) == 'l') ? 'r' : 'l';
 
 552 // Some other helpful things
 
 555 * @param {String} sStart
 
 557 * @returns Whether or not this string starts with the string passed in.
 
 560 String.prototype.startsWith = function(sStart){
 
 561     return (this.substr(0,sStart.length) == sStart);
 
 565 * @returns A trimmed version of the string - all leading and 
 
 566 *          trailing spaces removed
 
 569 String.prototype.trim = function() {
 
 572     while(this.substr(b,1) == " ") {
 
 576     var e = this.length - 1;
 
 577     while(this.substr(e,1) == " ") {
 
 581     return this.substring(b, e+1);
 
 584 /** Remove an object from an array. Iterates through the array
 
 585 *    to find the item, then removes it.
 
 587 * @param {Object} item
 
 589 * @returns A reference to the array
 
 592 Array.prototype.remove = function(item) {
 
 593     for(var i=0; i < this.length; i++) {
 
 594         if(this[i] == item) {
 
 596             //break;more than once??
 
 603 * @returns A fresh copy of the array
 
 606 Array.prototype.copyOf = function() {
 
 607   var copy = new Array();
 
 608   for (var i = 0; i < this.length; i++) {
 
 615 * @param  {Object} item
 
 617 Array.prototype.prepend = function(item) {
 
 618     this.splice(0, 0, item);
 
 622 * @param  {Object} item
 
 624 Array.prototype.append = function(item){
 
 625     this[this.length] = item;
 
 630 Array.prototype.clear = function() {
 
 635 * @param {Object} element
 
 637 * @returns The first index of the element in the array if found. Else returns -1
 
 640 Array.prototype.indexOf = function(element) {
 
 642     for(var i=0; i < this.length; i++) {
 
 643         if (this[i] == element) {
 
 653  * @param {OpenLayers.Pixel} px
 
 654  * @param {OpenLayers.Size} sz
 
 655  * @param {String} position
 
 656  * @param {String} border
 
 657  * @param {String} overflow
 
 659 OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, 
 
 666         element.style.left = px.x + "px";
 
 667         element.style.top = px.y + "px";
 
 670         element.style.width = sz.w + "px";
 
 671         element.style.height = sz.h + "px";
 
 674         element.style.position = position;
 
 677         element.style.border = border;
 
 680         element.style.overflow = overflow;
 
 688 * @param {OpenLayers.Pixel} px
 
 689 * @param {OpenLayers.Size} sz
 
 690 * @param {String} imgURL
 
 691 * @param {String} position
 
 692 * @param {String} border
 
 693 * @param {String} overflow
 
 695 * @returns A DOM Div created with the specified attributes.
 
 698 OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, 
 
 701     var dom = document.createElement('div');
 
 703     //set specific properties
 
 704     dom.style.padding = "0";
 
 705     dom.style.margin = "0";
 
 707         dom.style.backgroundImage = 'url(' + imgURL + ')';
 
 710     //set generic properties
 
 712         id = "OpenLayersDiv" + (Math.random() * 10000 % 10000);
 
 715         position = "absolute";
 
 717     OpenLayers.Util.modifyDOMElement(dom, id, px, sz, 
 
 718                                      position, border, overflow);
 
 725 * @param {OpenLayers.Pixel} px
 
 726 * @param {OpenLayers.Size} sz
 
 727 * @param {String} imgURL
 
 728 * @param {String} position
 
 729 * @param {String} border
 
 731 * @returns A DOM Image created with the specified attributes.
 
 734 OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border) {
 
 736     image = document.createElement("img");
 
 738     //set special properties
 
 739     image.style.alt = id;
 
 740     image.galleryImg = "no";
 
 745     //set generic properties
 
 747         id = "OpenLayersDiv" + (Math.random() * 10000 % 10000);
 
 750         position = "relative";
 
 752     OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, border);
 
 757 OpenLayers.Util.alphaHack = function() {
 
 758     var arVersion = navigator.appVersion.split("MSIE");
 
 759     var version = parseFloat(arVersion[1]);
 
 761     return ( (document.body.filters) &&
 
 762                       (version >= 5.5) && (version < 7) );
 
 766 * @param {DOMElement} div Div containing Alpha-adjusted Image
 
 768 * @param {OpenLayers.Pixel} px
 
 769 * @param {OpenLayers.Size} sz
 
 770 * @param {String} imgURL
 
 771 * @param {String} position
 
 772 * @param {String} border
 
 773 * @param {String} sizing 'crop', 'scale', or 'image'. Default is "scale"
 
 775 OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, 
 
 776                                                position, border, sizing) {
 
 778     OpenLayers.Util.modifyDOMElement(div, id, px, sz);
 
 780     var img = div.childNodes[0];
 
 785     OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, 
 
 788     if (OpenLayers.Util.alphaHack()) {
 
 789         div.style.display = "inline-block";
 
 790         if (sizing == null) {
 
 793         div.style.filter = "progid:DXImageTransform.Microsoft" +
 
 794                            ".AlphaImageLoader(src='" + img.src + "', " +
 
 795                            "sizingMethod='" + sizing + "')";
 
 796         img.style.filter = "progid:DXImageTransform.Microsoft" +
 
 803 * @param {OpenLayers.Pixel} px
 
 804 * @param {OpenLayers.Size} sz
 
 805 * @param {String} imgURL
 
 806 * @param {String} position
 
 807 * @param {String} border
 
 808 * @param {String} sizing 'crop', 'scale', or 'image'. Default is "scale"
 
 810 * @returns A DOM Div created with a DOM Image inside it. If the hack is 
 
 811 *           needed for transparency in IE, it is added.
 
 814 OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, 
 
 815                                                position, border, sizing) {
 
 817     var div = OpenLayers.Util.createDiv();
 
 818     var img = OpenLayers.Util.createImage();
 
 819     div.appendChild(img);
 
 821     OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, 
 
 822                                         position, border, sizing);
 
 828 /** Creates a new hash and copies over all the keys from the 
 
 829 *    passed-in object, but storing them under an uppercased
 
 830 *    version of the key at which they were stored.
 
 832 * @param {Object} object
 
 834 * @returns A new Object with all the same keys but uppercased
 
 837 OpenLayers.Util.upperCaseObject = function (object) {
 
 838     var uObject = new Object();
 
 839     for (var key in object) {
 
 840         uObject[key.toUpperCase()] = object[key];
 
 845 /** Takes a hash and copies any keys that don't exist from
 
 846 *   another hash, by analogy with Object.extend() from
 
 850 * @param {Object} from
 
 854 OpenLayers.Util.applyDefaults = function (to, from) {
 
 855     for (var key in from) {
 
 856         if (to[key] == null) {
 
 864 * @param {Object} params
 
 866 * @returns a concatenation of the properties of an object in 
 
 867 *    http parameter notation. 
 
 868 *    (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
 
 871 OpenLayers.Util.getParameterString = function(params) {
 
 872     paramsArray = new Array();
 
 874     for (var key in params) {
 
 875         var value = params[key];
 
 877         if (typeof value == 'function') continue;
 
 879         paramsArray.push(key + "=" + value);
 
 882     return paramsArray.join("&");
 
 886 * @returns The fully formatted image location string
 
 889 OpenLayers.Util.getImagesLocation = function() {
 
 890     return OpenLayers._getScriptLocation() + "img/";
 
 895 /** These could/should be made namespace aware?
 
 898 * @param {str} tagName
 
 902 OpenLayers.Util.getNodes=function(p, tagName) {
 
 903     var nodes = Try.these(
 
 905             return OpenLayers.Util._getNodes(p.documentElement.childNodes,
 
 909             return OpenLayers.Util._getNodes(p.childNodes, tagName);
 
 916 * @param {Array} nodes
 
 917 * @param {str} tagName
 
 921 OpenLayers.Util._getNodes=function(nodes, tagName) {
 
 922     var retArray = new Array();
 
 923     for (var i=0;i<nodes.length;i++) {
 
 924         if (nodes[i].nodeName==tagName) {
 
 925             retArray.push(nodes[i]);
 
 941 OpenLayers.Util.getTagText = function (parent, item, index) {
 
 942     var result = OpenLayers.Util.getNodes(parent, item);
 
 943     if (result && (result.length > 0))
 
 948         if (result[index].childNodes.length > 1) {
 
 949             return result.childNodes[1].nodeValue; 
 
 951         else if (result[index].childNodes.length == 1) {
 
 952             return result[index].firstChild.nodeValue; 
 
 961 * @param {HTMLDivElement} div
 
 965 OpenLayers.Util.mouseLeft = function (evt, div) {
 
 966     // start with the element to which the mouse has moved
 
 967     var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
 
 968     // walk up the DOM tree.
 
 969     while (target != div && target != null) {
 
 970         target = target.parentNode;
 
 972     // if the target we stop at isn't the div, then we've left the div.
 
 973     return (target != div);
 
 976 OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
 
 977 OpenLayers.Util.distVincenty=function(p1, p2) {
 
 978     var a = 6378137, b = 6356752.3142,  f = 1/298.257223563;
 
 979     var L = OpenLayers.Util.rad(p2.lon - p1.lon);
 
 980     var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
 
 981     var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
 
 982     var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
 
 983     var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
 
 984     var lambda = L, lambdaP = 2*Math.PI;
 
 986     while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
 
 987         var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
 
 988         var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
 
 989         (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
 
 990         if (sinSigma==0) return 0;  // co-incident points
 
 991         var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
 
 992         var sigma = Math.atan2(sinSigma, cosSigma);
 
 993         var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
 
 994         var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
 
 995         var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
 
 996         var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
 
 998         lambda = L + (1-C) * f * Math.sin(alpha) *
 
 999         (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
 
1001     if (iterLimit==0) return NaN  // formula failed to converge
 
1002     var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
 
1003     var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
 
1004     var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
 
1005     var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
 
1006         B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
 
1007     var s = b*A*(sigma-deltaSigma);
 
1008     var d = s.toFixed(3)/1000; // round to 1mm precision