source: trunk/openlayers/lib/OpenLayers/Util.js

Last change on this file was 12434, checked in by openlayersgit, 5 years ago

Merge branch 'master' of github.com:openlayers/openlayers

From: ahocevar <andreas.hocevar@…>

  • Property svn:eol-style set to native
File size: 57.6 KB
Line 
1/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for
2 * full list of contributors). Published under the Clear BSD license. 
3 * See http://svn.openlayers.org/trunk/openlayers/license.txt for the
4 * full text of the license. */
5
6/**
7 * @requires OpenLayers/BaseTypes.js
8 * @requires OpenLayers/BaseTypes/Bounds.js
9 * @requires OpenLayers/BaseTypes/Element.js
10 * @requires OpenLayers/BaseTypes/LonLat.js
11 * @requires OpenLayers/BaseTypes/Pixel.js
12 * @requires OpenLayers/BaseTypes/Size.js
13 * @requires OpenLayers/Console.js
14 * @requires OpenLayers/Lang.js
15 */
16
17/**
18 * Namespace: Util
19 */
20OpenLayers.Util = OpenLayers.Util || {};
21
22/**
23 * Function: getElement
24 * This is the old $() from prototype
25 *
26 * Parameters:
27 * e - {String or DOMElement or Window}
28 * Return:
29 * {Array(DOMElement)}
30 */
31OpenLayers.Util.getElement = function() {
32    var elements = [];
33
34    for (var i=0, len=arguments.length; i<len; i++) {
35        var element = arguments[i];
36        if (typeof element == 'string') {
37            element = document.getElementById(element);
38        }
39        if (arguments.length == 1) {
40            return element;
41        }
42        elements.push(element);
43    }
44    return elements;
45};
46
47/**
48 * Function: isElement
49 * A cross-browser implementation of "e instanceof Element".
50 *
51 * Parameters:
52 * o - {Object} The object to test.
53 *
54 * Returns:
55 * {Boolean}
56 */
57OpenLayers.Util.isElement = function(o) {
58    return !!(o && o.nodeType === 1);
59};
60
61/**
62 * Function: isArray
63 * Tests that the provided object is an array.
64 * This test handles the cross-IFRAME case not caught
65 * by "a instanceof Array" and should be used instead.
66 *
67 * Parameters:
68 * a - {Object} the object test.
69 *
70 * Returns
71 * {Boolean} true if the object is an array.
72 */
73OpenLayers.Util.isArray = function(a) {
74    return (Object.prototype.toString.call(a) === '[object Array]');
75};
76
77/**
78 * Maintain existing definition of $.
79 */
80if(typeof window.$  === "undefined") {
81    window.$ = OpenLayers.Util.getElement;
82}
83
84/**
85 * Function: removeItem
86 * Remove an object from an array. Iterates through the array
87 *     to find the item, then removes it.
88 *
89 * Parameters:
90 * array - {Array}
91 * item - {Object}
92 *
93 * Return
94 * {Array} A reference to the array
95 */
96OpenLayers.Util.removeItem = function(array, item) {
97    for(var i = array.length - 1; i >= 0; i--) {
98        if(array[i] == item) {
99            array.splice(i,1);
100            //break;more than once??
101        }
102    }
103    return array;
104};
105
106/**
107 * Function: clearArray
108 * *Deprecated*. This function will disappear in 3.0.
109 * Please use "array.length = 0" instead.
110 *
111 * Parameters:
112 * array - {Array}
113 */
114OpenLayers.Util.clearArray = function(array) {
115    OpenLayers.Console.warn(
116        OpenLayers.i18n(
117            "methodDeprecated", {'newMethod': 'array = []'}
118        )
119    );
120    array.length = 0;
121};
122
123/**
124 * Function: indexOf
125 * Seems to exist already in FF, but not in MOZ.
126 *
127 * Parameters:
128 * array - {Array}
129 * obj - {*}
130 *
131 * Returns:
132 * {Integer} The index at, which the first object was found in the array.
133 *           If not found, returns -1.
134 */
135OpenLayers.Util.indexOf = function(array, obj) {
136    // use the build-in function if available.
137    if (typeof array.indexOf == "function") {
138        return array.indexOf(obj);
139    } else {
140        for (var i = 0, len = array.length; i < len; i++) {
141            if (array[i] == obj) {
142                return i;
143            }
144        }
145        return -1;   
146    }
147};
148
149
150
151/**
152 * Function: modifyDOMElement
153 *
154 * Modifies many properties of a DOM element all at once.  Passing in
155 * null to an individual parameter will avoid setting the attribute.
156 *
157 * Parameters:
158 * element - {DOMElement} DOM element to modify.
159 * id - {String} The element id attribute to set.
160 * px - {<OpenLayers.Pixel>} The left and top style position.
161 * sz - {<OpenLayers.Size>}  The width and height style attributes.
162 * position - {String}       The position attribute.  eg: absolute,
163 *                           relative, etc.
164 * border - {String}         The style.border attribute.  eg:
165 *                           solid black 2px
166 * overflow - {String}       The style.overview attribute. 
167 * opacity - {Float}         Fractional value (0.0 - 1.0)
168 */
169OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, 
170                                            border, overflow, opacity) {
171
172    if (id) {
173        element.id = id;
174    }
175    if (px) {
176        element.style.left = px.x + "px";
177        element.style.top = px.y + "px";
178    }
179    if (sz) {
180        element.style.width = sz.w + "px";
181        element.style.height = sz.h + "px";
182    }
183    if (position) {
184        element.style.position = position;
185    }
186    if (border) {
187        element.style.border = border;
188    }
189    if (overflow) {
190        element.style.overflow = overflow;
191    }
192    if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
193        element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
194        element.style.opacity = opacity;
195    } else if (parseFloat(opacity) == 1.0) {
196        element.style.filter = '';
197        element.style.opacity = '';
198    }
199};
200
201/**
202 * Function: createDiv
203 * Creates a new div and optionally set some standard attributes.
204 * Null may be passed to each parameter if you do not wish to
205 * set a particular attribute.
206 * Note - zIndex is NOT set on the resulting div.
207 *
208 * Parameters:
209 * id - {String} An identifier for this element.  If no id is
210 *               passed an identifier will be created
211 *               automatically.
212 * px - {<OpenLayers.Pixel>} The element left and top position.
213 * sz - {<OpenLayers.Size>} The element width and height.
214 * imgURL - {String} A url pointing to an image to use as a
215 *                   background image.
216 * position - {String} The style.position value. eg: absolute,
217 *                     relative etc.
218 * border - {String} The the style.border value.
219 *                   eg: 2px solid black
220 * overflow - {String} The style.overflow value. Eg. hidden
221 * opacity - {Float} Fractional value (0.0 - 1.0)
222 *
223 * Returns:
224 * {DOMElement} A DOM Div created with the specified attributes.
225 */
226OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, 
227                                     border, overflow, opacity) {
228
229    var dom = document.createElement('div');
230
231    if (imgURL) {
232        dom.style.backgroundImage = 'url(' + imgURL + ')';
233    }
234
235    //set generic properties
236    if (!id) {
237        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
238    }
239    if (!position) {
240        position = "absolute";
241    }
242    OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, 
243                                     border, overflow, opacity);
244
245    return dom;
246};
247
248/**
249 * Function: createImage
250 * Creates an img element with specific attribute values.
251 * 
252 * Parameters:
253 * id - {String} The id field for the img.  If none assigned one will be
254 *               automatically generated.
255 * px - {<OpenLayers.Pixel>} The left and top positions.
256 * sz - {<OpenLayers.Size>} The style.width and style.height values.
257 * imgURL - {String} The url to use as the image source.
258 * position - {String} The style.position value.
259 * border - {String} The border to place around the image.
260 * opacity - {Float} Fractional value (0.0 - 1.0)
261 * delayDisplay - {Boolean} If true waits until the image has been
262 *                          loaded.
263 *
264 * Returns:
265 * {DOMElement} A DOM Image created with the specified attributes.
266 */
267OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
268                                       opacity, delayDisplay) {
269
270    var image = document.createElement("img");
271
272    //set generic properties
273    if (!id) {
274        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
275    }
276    if (!position) {
277        position = "relative";
278    }
279    OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, 
280                                     border, null, opacity);
281
282    if (delayDisplay) {
283        image.style.display = "none";
284        function display() {
285            image.style.display = "";
286            OpenLayers.Event.stopObservingElement(image);
287        }
288        OpenLayers.Event.observe(image, "load", display);
289        OpenLayers.Event.observe(image, "error", display);
290    }
291   
292    //set special properties
293    image.style.alt = id;
294    image.galleryImg = "no";
295    if (imgURL) {
296        image.src = imgURL;
297    }
298       
299    return image;
300};
301
302/**
303 * Function: setOpacity
304 * *Deprecated*.  This function has been deprecated. Instead, please use
305 *     <OpenLayers.Util.modifyDOMElement>
306 *     or
307 *     <OpenLayers.Util.modifyAlphaImageDiv>
308 *
309 * Set the opacity of a DOM Element
310 *     Note that for this function to work in IE, elements must "have layout"
311 *     according to:
312 *     http://msdn.microsoft.com/workshop/author/dhtml/reference/properties/haslayout.asp
313 *
314 * Parameters:
315 * element - {DOMElement} Set the opacity on this DOM element
316 * opacity - {Float} Opacity value (0.0 - 1.0)
317 */
318OpenLayers.Util.setOpacity = function(element, opacity) {
319    OpenLayers.Util.modifyDOMElement(element, null, null, null,
320                                     null, null, null, opacity);
321};
322
323/**
324 * Property: IMAGE_RELOAD_ATTEMPTS
325 * {Integer} How many times should we try to reload an image before giving up?
326 *           Default is 0
327 */
328OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
329
330/**
331 * Property: alphaHackNeeded
332 * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
333 */
334OpenLayers.Util.alphaHackNeeded = null;
335
336/**
337 * Function: alphaHack
338 * Checks whether it's necessary (and possible) to use the png alpha
339 * hack which allows alpha transparency for png images under Internet
340 * Explorer.
341 *
342 * Returns:
343 * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
344 */
345OpenLayers.Util.alphaHack = function() {
346    if (OpenLayers.Util.alphaHackNeeded == null) {
347        var arVersion = navigator.appVersion.split("MSIE");
348        var version = parseFloat(arVersion[1]);
349        var filter = false;
350   
351        // IEs4Lin dies when trying to access document.body.filters, because
352        // the property is there, but requires a DLL that can't be provided. This
353        // means that we need to wrap this in a try/catch so that this can
354        // continue.
355   
356        try { 
357            filter = !!(document.body.filters);
358        } catch (e) {}   
359   
360        OpenLayers.Util.alphaHackNeeded = (filter && 
361                                           (version >= 5.5) && (version < 7));
362    }
363    return OpenLayers.Util.alphaHackNeeded;
364};
365
366/**
367 * Function: modifyAlphaImageDiv
368 *
369 * Parameters:
370 * div - {DOMElement} Div containing Alpha-adjusted Image
371 * id - {String}
372 * px - {<OpenLayers.Pixel>}
373 * sz - {<OpenLayers.Size>}
374 * imgURL - {String}
375 * position - {String}
376 * border - {String}
377 * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
378 * opacity - {Float} Fractional value (0.0 - 1.0)
379 */ 
380OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, 
381                                               position, border, sizing, 
382                                               opacity) {
383
384    OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
385                                     null, null, opacity);
386
387    var img = div.childNodes[0];
388
389    if (imgURL) {
390        img.src = imgURL;
391    }
392    OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, 
393                                     "relative", border);
394   
395    if (OpenLayers.Util.alphaHack()) {
396        if(div.style.display != "none") {
397            div.style.display = "inline-block";
398        }
399        if (sizing == null) {
400            sizing = "scale";
401        }
402       
403        div.style.filter = "progid:DXImageTransform.Microsoft" +
404                           ".AlphaImageLoader(src='" + img.src + "', " +
405                           "sizingMethod='" + sizing + "')";
406        if (parseFloat(div.style.opacity) >= 0.0 && 
407            parseFloat(div.style.opacity) < 1.0) {
408            div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
409        }
410
411        img.style.filter = "alpha(opacity=0)";
412    }
413};
414
415/**
416 * Function: createAlphaImageDiv
417 *
418 * Parameters:
419 * id - {String}
420 * px - {<OpenLayers.Pixel>}
421 * sz - {<OpenLayers.Size>}
422 * imgURL - {String}
423 * position - {String}
424 * border - {String}
425 * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
426 * opacity - {Float} Fractional value (0.0 - 1.0)
427 * delayDisplay - {Boolean} If true waits until the image has been
428 *                          loaded.
429 *
430 * Returns:
431 * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is
432 *              needed for transparency in IE, it is added.
433 */ 
434OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, 
435                                               position, border, sizing, 
436                                               opacity, delayDisplay) {
437   
438    var div = OpenLayers.Util.createDiv();
439    var img = OpenLayers.Util.createImage(null, null, null, null, null, null, 
440                                          null, delayDisplay);
441    div.appendChild(img);
442
443    OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, 
444                                        border, sizing, opacity);
445   
446    return div;
447};
448
449
450/**
451 * Function: upperCaseObject
452 * Creates a new hashtable and copies over all the keys from the
453 *     passed-in object, but storing them under an uppercased
454 *     version of the key at which they were stored.
455 *
456 * Parameters:
457 * object - {Object}
458 *
459 * Returns:
460 * {Object} A new Object with all the same keys but uppercased
461 */
462OpenLayers.Util.upperCaseObject = function (object) {
463    var uObject = {};
464    for (var key in object) {
465        uObject[key.toUpperCase()] = object[key];
466    }
467    return uObject;
468};
469
470/**
471 * Function: applyDefaults
472 * Takes an object and copies any properties that don't exist from
473 *     another properties, by analogy with OpenLayers.Util.extend() from
474 *     Prototype.js.
475 *
476 * Parameters:
477 * to - {Object} The destination object.
478 * from - {Object} The source object.  Any properties of this object that
479 *     are undefined in the to object will be set on the to object.
480 *
481 * Returns:
482 * {Object} A reference to the to object.  Note that the to argument is modified
483 *     in place and returned by this function.
484 */
485OpenLayers.Util.applyDefaults = function (to, from) {
486    to = to || {};
487    /*
488     * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
489     * prototype object" when calling hawOwnProperty if the source object is an
490     * instance of window.Event.
491     */
492    var fromIsEvt = typeof window.Event == "function"
493                    && from instanceof window.Event;
494
495    for (var key in from) {
496        if (to[key] === undefined ||
497            (!fromIsEvt && from.hasOwnProperty
498             && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
499            to[key] = from[key];
500        }
501    }
502    /**
503     * IE doesn't include the toString property when iterating over an object's
504     * properties with the for(property in object) syntax.  Explicitly check if
505     * the source has its own toString property.
506     */
507    if(!fromIsEvt && from && from.hasOwnProperty
508       && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
509        to.toString = from.toString;
510    }
511   
512    return to;
513};
514
515/**
516 * Function: getParameterString
517 *
518 * Parameters:
519 * params - {Object}
520 *
521 * Returns:
522 * {String} A concatenation of the properties of an object in
523 *          http parameter notation.
524 *          (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
525 *          If a parameter is actually a list, that parameter will then
526 *          be set to a comma-seperated list of values (foo,bar) instead
527 *          of being URL escaped (foo%3Abar).
528 */
529OpenLayers.Util.getParameterString = function(params) {
530    var paramsArray = [];
531   
532    for (var key in params) {
533      var value = params[key];
534      if ((value != null) && (typeof value != 'function')) {
535        var encodedValue;
536        if (typeof value == 'object' && value.constructor == Array) {
537          /* value is an array; encode items and separate with "," */
538          var encodedItemArray = [];
539          var item;
540          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
541            item = value[itemIndex];
542            encodedItemArray.push(encodeURIComponent(
543                (item === null || item === undefined) ? "" : item)
544            );
545          }
546          encodedValue = encodedItemArray.join(",");
547        }
548        else {
549          /* value is a string; simply encode */
550          encodedValue = encodeURIComponent(value);
551        }
552        paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
553      }
554    }
555   
556    return paramsArray.join("&");
557};
558
559/**
560 * Function: urlAppend
561 * Appends a parameter string to a url. This function includes the logic for
562 * using the appropriate character (none, & or ?) to append to the url before
563 * appending the param string.
564 *
565 * Parameters:
566 * url - {String} The url to append to
567 * paramStr - {String} The param string to append
568 *
569 * Returns:
570 * {String} The new url
571 */
572OpenLayers.Util.urlAppend = function(url, paramStr) {
573    var newUrl = url;
574    if(paramStr) {
575        var parts = (url + " ").split(/[?&]/);
576        newUrl += (parts.pop() === " " ?
577            paramStr :
578            parts.length ? "&" + paramStr : "?" + paramStr);
579    }
580    return newUrl;
581};
582
583/**
584 * Property: ImgPath
585 * {String} Default is ''.
586 */
587OpenLayers.ImgPath = '';
588
589/**
590 * Function: getImagesLocation
591 *
592 * Returns:
593 * {String} The fully formatted image location string
594 */
595OpenLayers.Util.getImagesLocation = function() {
596    return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
597};
598
599
600/**
601 * Function: Try
602 * Execute functions until one of them doesn't throw an error.
603 *     Capitalized because "try" is a reserved word in JavaScript.
604 *     Taken directly from OpenLayers.Util.Try()
605 *
606 * Parameters:
607 * [*] - {Function} Any number of parameters may be passed to Try()
608 *    It will attempt to execute each of them until one of them
609 *    successfully executes.
610 *    If none executes successfully, returns null.
611 *
612 * Returns:
613 * {*} The value returned by the first successfully executed function.
614 */
615OpenLayers.Util.Try = function() {
616    var returnValue = null;
617
618    for (var i=0, len=arguments.length; i<len; i++) {
619      var lambda = arguments[i];
620      try {
621        returnValue = lambda();
622        break;
623      } catch (e) {}
624    }
625
626    return returnValue;
627};
628
629/**
630 * Function: getXmlNodeValue
631 *
632 * Parameters:
633 * node - {XMLNode}
634 *
635 * Returns:
636 * {String} The text value of the given node, without breaking in firefox or IE
637 */
638OpenLayers.Util.getXmlNodeValue = function(node) {
639    var val = null;
640    OpenLayers.Util.Try( 
641        function() {
642            val = node.text;
643            if (!val) {
644                val = node.textContent;
645            }
646            if (!val) {
647                val = node.firstChild.nodeValue;
648            }
649        }, 
650        function() {
651            val = node.textContent;
652        }); 
653    return val;
654};
655
656/**
657 * Function: mouseLeft
658 *
659 * Parameters:
660 * evt - {Event}
661 * div - {HTMLDivElement}
662 *
663 * Returns:
664 * {Boolean}
665 */
666OpenLayers.Util.mouseLeft = function (evt, div) {
667    // start with the element to which the mouse has moved
668    var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
669    // walk up the DOM tree.
670    while (target != div && target != null) {
671        target = target.parentNode;
672    }
673    // if the target we stop at isn't the div, then we've left the div.
674    return (target != div);
675};
676
677/**
678 * Property: precision
679 * {Number} The number of significant digits to retain to avoid
680 * floating point precision errors.
681 *
682 * We use 14 as a "safe" default because, although IEEE 754 double floats
683 * (standard on most modern operating systems) support up to about 16
684 * significant digits, 14 significant digits are sufficient to represent
685 * sub-millimeter accuracy in any coordinate system that anyone is likely to
686 * use with OpenLayers.
687 *
688 * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
689 * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
690 * with certain projections, e.g. spherical Mercator.
691 *
692 */
693OpenLayers.Util.DEFAULT_PRECISION = 14;
694
695/**
696 * Function: toFloat
697 * Convenience method to cast an object to a Number, rounded to the
698 * desired floating point precision.
699 *
700 * Parameters:
701 * number    - {Number} The number to cast and round.
702 * precision - {Number} An integer suitable for use with
703 *      Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
704 *      If set to 0, no rounding is performed.
705 *
706 * Returns:
707 * {Number} The cast, rounded number.
708 */
709OpenLayers.Util.toFloat = function (number, precision) {
710    if (precision == null) {
711        precision = OpenLayers.Util.DEFAULT_PRECISION;
712    }
713    if (typeof number !== "number") {
714        number = parseFloat(number);
715    }
716    return precision === 0 ? number :
717                             parseFloat(number.toPrecision(precision));
718};
719
720/**
721 * Function: rad
722 *
723 * Parameters:
724 * x - {Float}
725 *
726 * Returns:
727 * {Float}
728 */
729OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
730
731/**
732 * Function: deg
733 *
734 * Parameters:
735 * x - {Float}
736 *
737 * Returns:
738 * {Float}
739 */
740OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
741
742/**
743 * Property: VincentyConstants
744 * {Object} Constants for Vincenty functions.
745 */
746OpenLayers.Util.VincentyConstants = {
747    a: 6378137,
748    b: 6356752.3142,
749    f: 1/298.257223563
750};
751
752/**
753 * APIFunction: distVincenty
754 * Given two objects representing points with geographic coordinates, this
755 *     calculates the distance between those points on the surface of an
756 *     ellipsoid.
757 *
758 * Parameters:
759 * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
760 * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
761 *
762 * Returns:
763 * {Float} The distance (in km) between the two input points as measured on an
764 *     ellipsoid.  Note that the input point objects must be in geographic
765 *     coordinates (decimal degrees) and the return distance is in kilometers.
766 */
767OpenLayers.Util.distVincenty = function(p1, p2) {
768    var ct = OpenLayers.Util.VincentyConstants;
769    var a = ct.a, b = ct.b, f = ct.f;
770
771    var L = OpenLayers.Util.rad(p2.lon - p1.lon);
772    var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
773    var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
774    var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
775    var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
776    var lambda = L, lambdaP = 2*Math.PI;
777    var iterLimit = 20;
778    while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
779        var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
780        var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
781        (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
782        if (sinSigma==0) {
783            return 0;  // co-incident points
784        }
785        var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
786        var sigma = Math.atan2(sinSigma, cosSigma);
787        var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
788        var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
789        var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
790        var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
791        lambdaP = lambda;
792        lambda = L + (1-C) * f * Math.sin(alpha) *
793        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
794    }
795    if (iterLimit==0) {
796        return NaN;  // formula failed to converge
797    }
798    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
799    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
800    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
801    var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
802        B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
803    var s = b*A*(sigma-deltaSigma);
804    var d = s.toFixed(3)/1000; // round to 1mm precision
805    return d;
806};
807
808/**
809 * APIFunction: destinationVincenty
810 * Calculate destination point given start point lat/long (numeric degrees),
811 * bearing (numeric degrees) & distance (in m).
812 * Adapted from Chris Veness work, see
813 * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
814 *
815 * Parameters:
816 * lonlat  - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
817 *     properties) The start point.
818 * brng     - {Float} The bearing (degrees).
819 * dist     - {Float} The ground distance (meters).
820 *
821 * Returns:
822 * {<OpenLayers.LonLat>} The destination point.
823 */
824OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
825    var u = OpenLayers.Util;
826    var ct = u.VincentyConstants;
827    var a = ct.a, b = ct.b, f = ct.f;
828
829    var lon1 = lonlat.lon;
830    var lat1 = lonlat.lat;
831
832    var s = dist;
833    var alpha1 = u.rad(brng);
834    var sinAlpha1 = Math.sin(alpha1);
835    var cosAlpha1 = Math.cos(alpha1);
836
837    var tanU1 = (1-f) * Math.tan(u.rad(lat1));
838    var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
839    var sigma1 = Math.atan2(tanU1, cosAlpha1);
840    var sinAlpha = cosU1 * sinAlpha1;
841    var cosSqAlpha = 1 - sinAlpha*sinAlpha;
842    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
843    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
844    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
845
846    var sigma = s / (b*A), sigmaP = 2*Math.PI;
847    while (Math.abs(sigma-sigmaP) > 1e-12) {
848        var cos2SigmaM = Math.cos(2*sigma1 + sigma);
849        var sinSigma = Math.sin(sigma);
850        var cosSigma = Math.cos(sigma);
851        var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
852            B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
853        sigmaP = sigma;
854        sigma = s / (b*A) + deltaSigma;
855    }
856
857    var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
858    var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
859        (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
860    var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
861    var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
862    var L = lambda - (1-C) * f * sinAlpha *
863        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
864
865    var revAz = Math.atan2(sinAlpha, -tmp);  // final bearing
866
867    return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
868};
869
870/**
871 * Function: getParameters
872 * Parse the parameters from a URL or from the current page itself into a
873 *     JavaScript Object. Note that parameter values with commas are separated
874 *     out into an Array.
875 *
876 * Parameters:
877 * url - {String} Optional url used to extract the query string.
878 *                If url is null or is not supplied, query string is taken
879 *                from the page location.
880 *
881 * Returns:
882 * {Object} An object of key/value pairs from the query string.
883 */
884OpenLayers.Util.getParameters = function(url) {
885    // if no url specified, take it from the location bar
886    url = (url === null || url === undefined) ? window.location.href : url;
887
888    //parse out parameters portion of url string
889    var paramsString = "";
890    if (OpenLayers.String.contains(url, '?')) {
891        var start = url.indexOf('?') + 1;
892        var end = OpenLayers.String.contains(url, "#") ?
893                    url.indexOf('#') : url.length;
894        paramsString = url.substring(start, end);
895    }
896
897    var parameters = {};
898    var pairs = paramsString.split(/[&;]/);
899    for(var i=0, len=pairs.length; i<len; ++i) {
900        var keyValue = pairs[i].split('=');
901        if (keyValue[0]) {
902
903            var key = keyValue[0];
904            try {
905                key = decodeURIComponent(key);
906            } catch (err) {
907                key = unescape(key);
908            }
909           
910            // being liberal by replacing "+" with " "
911            var value = (keyValue[1] || '').replace(/\+/g, " ");
912
913            try {
914                value = decodeURIComponent(value);
915            } catch (err) {
916                value = unescape(value);
917            }
918           
919            // follow OGC convention of comma delimited values
920            value = value.split(",");
921
922            //if there's only one value, do not return as array                   
923            if (value.length == 1) {
924                value = value[0];
925            }               
926           
927            parameters[key] = value;
928         }
929     }
930    return parameters;
931};
932
933/**
934 * Function: getArgs
935 * *Deprecated*.  Will be removed in 3.0.  Please use instead
936 *     <OpenLayers.Util.getParameters>
937 *
938 * Parameters:
939 * url - {String} Optional url used to extract the query string.
940 *                If null, query string is taken from page location.
941 *
942 * Returns:
943 * {Object} An object of key/value pairs from the query string.
944 */
945OpenLayers.Util.getArgs = function(url) {
946    OpenLayers.Console.warn(
947        OpenLayers.i18n(
948            "methodDeprecated", {'newMethod': 'OpenLayers.Util.getParameters'}
949        )
950    );
951    return OpenLayers.Util.getParameters(url);
952};
953
954/**
955 * Property: lastSeqID
956 * {Integer} The ever-incrementing count variable.
957 *           Used for generating unique ids.
958 */
959OpenLayers.Util.lastSeqID = 0;
960
961/**
962 * Function: createUniqueID
963 * Create a unique identifier for this session.  Each time this function
964 *     is called, a counter is incremented.  The return will be the optional
965 *     prefix (defaults to "id_") appended with the counter value.
966 *
967 * Parameters:
968 * prefix {String} Optionsal string to prefix unique id. Default is "id_".
969 *
970 * Returns:
971 * {String} A unique id string, built on the passed in prefix.
972 */
973OpenLayers.Util.createUniqueID = function(prefix) {
974    if (prefix == null) {
975        prefix = "id_";
976    }
977    OpenLayers.Util.lastSeqID += 1; 
978    return prefix + OpenLayers.Util.lastSeqID;       
979};
980
981/**
982 * Constant: INCHES_PER_UNIT
983 * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
984 * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
985 * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
986 * and PROJ.4 (http://trac.osgeo.org/proj/)
987 * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
988 * The hardcoded table of PROJ.4 units are in pj_units.c.
989 */
990OpenLayers.INCHES_PER_UNIT = { 
991    'inches': 1.0,
992    'ft': 12.0,
993    'mi': 63360.0,
994    'm': 39.3701,
995    'km': 39370.1,
996    'dd': 4374754,
997    'yd': 36
998};
999OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
1000OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
1001OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
1002
1003// Units from CS-Map
1004OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
1005OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
1006    "Inch": OpenLayers.INCHES_PER_UNIT.inches,
1007    "Meter": 1.0 / OpenLayers.METERS_PER_INCH,   //EPSG:9001
1008    "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH,   //EPSG:9003
1009    "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9002
1010    "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH,   //EPSG:9005
1011    "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH,   //EPSG:9041
1012    "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH,   //EPSG:9094
1013    "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
1014    "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
1015    "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
1016    "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
1017    "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9036
1018    "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
1019    "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH,   //EPSG:9040
1020    "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH,   //EPSG:9084
1021    "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH,   //EPSG:9085
1022    "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH,   //EPSG:9086
1023    "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH,   //EPSG:9087
1024    "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH,   //EPSG:9080
1025    "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH,   //EPSG:9081
1026    "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH,   //EPSG:9082
1027    "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH,   //EPSG:9083
1028    "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
1029    "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9096
1030    "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9093
1031    "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9030
1032    "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
1033    "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
1034    "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
1035    "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
1036    "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
1037    "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
1038    "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
1039    "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH,   //EPSG:9031
1040    "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
1041    "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9038
1042    "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9033
1043    "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9062
1044    "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9042
1045    "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9039
1046    "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9034
1047    "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9063
1048    "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9043
1049    "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
1050    "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH,   //EPSG:9097
1051    "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH,   //EPSG:9098
1052    "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
1053    "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
1054    "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
1055    "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
1056    "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
1057    "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
1058    "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
1059    "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
1060    "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
1061    "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
1062    "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
1063});
1064
1065//unit abbreviations supported by PROJ.4
1066OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
1067    "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
1068    "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
1069    "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
1070    "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
1071    "kmi": OpenLayers.INCHES_PER_UNIT["nmi"],    //International Nautical Mile
1072    "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
1073    "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"],  //International Chain
1074    "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
1075    "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
1076    "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"],    //U.S. Surveyor's Foot
1077    "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"],    //U.S. Surveyor's Yard
1078    "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
1079    "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"],   //U.S. Surveyor's Statute Mile
1080    "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"],  //Indian Yard
1081    "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"],  //Indian Foot
1082    "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH  //Indian Chain
1083});
1084
1085/**
1086 * Constant: DOTS_PER_INCH
1087 * {Integer} 72 (A sensible default)
1088 */
1089OpenLayers.DOTS_PER_INCH = 72;
1090
1091/**
1092 * Function: normalizeScale
1093 *
1094 * Parameters:
1095 * scale - {float}
1096 *
1097 * Returns:
1098 * {Float} A normalized scale value, in 1 / X format.
1099 *         This means that if a value less than one ( already 1/x) is passed
1100 *         in, it just returns scale directly. Otherwise, it returns
1101 *         1 / scale
1102 */
1103OpenLayers.Util.normalizeScale = function (scale) {
1104    var normScale = (scale > 1.0) ? (1.0 / scale) 
1105                                  : scale;
1106    return normScale;
1107};
1108
1109/**
1110 * Function: getResolutionFromScale
1111 *
1112 * Parameters:
1113 * scale - {Float}
1114 * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
1115 *                  Default is degrees
1116 *
1117 * Returns:
1118 * {Float} The corresponding resolution given passed-in scale and unit
1119 *     parameters.  If the given scale is falsey, the returned resolution will
1120 *     be undefined.
1121 */
1122OpenLayers.Util.getResolutionFromScale = function (scale, units) {
1123    var resolution;
1124    if (scale) {
1125        if (units == null) {
1126            units = "degrees";
1127        }
1128        var normScale = OpenLayers.Util.normalizeScale(scale);
1129        resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
1130                                        * OpenLayers.DOTS_PER_INCH);       
1131    }
1132    return resolution;
1133};
1134
1135/**
1136 * Function: getScaleFromResolution
1137 *
1138 * Parameters:
1139 * resolution - {Float}
1140 * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
1141 *                  Default is degrees
1142 *
1143 * Returns:
1144 * {Float} The corresponding scale given passed-in resolution and unit
1145 *         parameters.
1146 */
1147OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
1148
1149    if (units == null) {
1150        units = "degrees";
1151    }
1152
1153    var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
1154                    OpenLayers.DOTS_PER_INCH;
1155    return scale;
1156};
1157
1158/**
1159 * Function: safeStopPropagation
1160 * *Deprecated*. This function has been deprecated. Please use directly
1161 *     <OpenLayers.Event.stop> passing 'true' as the 2nd
1162 *     argument (preventDefault)
1163 *
1164 * Safely stop the propagation of an event *without* preventing
1165 *   the default browser action from occurring.
1166 *
1167 * Parameter:
1168 * evt - {Event}
1169 */
1170OpenLayers.Util.safeStopPropagation = function(evt) {
1171    OpenLayers.Event.stop(evt, true);
1172};
1173
1174/**
1175 * Function: pagePosition
1176 * Calculates the position of an element on the page (see
1177 * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
1178 *
1179 * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
1180 * Copyright (c) 2006, Yahoo! Inc.
1181 * All rights reserved.
1182 *
1183 * Redistribution and use of this software in source and binary forms, with or
1184 * without modification, are permitted provided that the following conditions
1185 * are met:
1186 *
1187 * * Redistributions of source code must retain the above copyright notice,
1188 *   this list of conditions and the following disclaimer.
1189 *
1190 * * Redistributions in binary form must reproduce the above copyright notice,
1191 *   this list of conditions and the following disclaimer in the documentation
1192 *   and/or other materials provided with the distribution.
1193 *
1194 * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
1195 *   used to endorse or promote products derived from this software without
1196 *   specific prior written permission of Yahoo! Inc.
1197 *
1198 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
1199 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
1200 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
1201 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
1202 * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
1203 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
1204 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
1205 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
1206 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
1207 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
1208 * POSSIBILITY OF SUCH DAMAGE.
1209 *
1210 * Parameters:
1211 * forElement - {DOMElement}
1212 *
1213 * Returns:
1214 * {Array} two item array, Left value then Top value.
1215 */
1216OpenLayers.Util.pagePosition =  function(forElement) {
1217    // NOTE: If element is hidden (display none or disconnected or any the
1218    // ancestors are hidden) we get (0,0) by default but we still do the
1219    // accumulation of scroll position.
1220
1221    var pos = [0, 0];
1222    var viewportElement = OpenLayers.Util.getViewportElement();
1223    if (!forElement || forElement == window || forElement == viewportElement) {
1224        // viewport is always at 0,0 as that defined the coordinate system for
1225        // this function - this avoids special case checks in the code below
1226        return pos;
1227    }
1228
1229    // Gecko browsers normally use getBoxObjectFor to calculate the position.
1230    // When invoked for an element with an implicit absolute position though it
1231    // can be off by one. Therefore the recursive implementation is used in
1232    // those (relatively rare) cases.
1233    var BUGGY_GECKO_BOX_OBJECT =
1234        OpenLayers.IS_GECKO && document.getBoxObjectFor &&
1235        OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
1236        (forElement.style.top == '' || forElement.style.left == '');
1237
1238    var parent = null;
1239    var box;
1240
1241    if (forElement.getBoundingClientRect) { // IE
1242        box = forElement.getBoundingClientRect();
1243        var scrollTop = viewportElement.scrollTop;
1244        var scrollLeft = viewportElement.scrollLeft;
1245
1246        pos[0] = box.left + scrollLeft;
1247        pos[1] = box.top + scrollTop;
1248
1249    } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
1250        // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
1251        // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
1252        // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
1253
1254        box = document.getBoxObjectFor(forElement);
1255        var vpBox = document.getBoxObjectFor(viewportElement);
1256        pos[0] = box.screenX - vpBox.screenX;
1257        pos[1] = box.screenY - vpBox.screenY;
1258
1259    } else { // safari/opera
1260        pos[0] = forElement.offsetLeft;
1261        pos[1] = forElement.offsetTop;
1262        parent = forElement.offsetParent;
1263        if (parent != forElement) {
1264            while (parent) {
1265                pos[0] += parent.offsetLeft;
1266                pos[1] += parent.offsetTop;
1267                parent = parent.offsetParent;
1268            }
1269        }
1270
1271        var browser = OpenLayers.BROWSER_NAME;
1272
1273        // opera & (safari absolute) incorrectly account for body offsetTop
1274        if (browser == "opera" || (browser == "safari" &&
1275              OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
1276            pos[1] -= document.body.offsetTop;
1277        }
1278
1279        // accumulate the scroll positions for everything but the body element
1280        parent = forElement.offsetParent;
1281        while (parent && parent != document.body) {
1282            pos[0] -= parent.scrollLeft;
1283            // see https://bugs.opera.com/show_bug.cgi?id=249965
1284            if (browser != "opera" || parent.tagName != 'TR') {
1285                pos[1] -= parent.scrollTop;
1286            }
1287            parent = parent.offsetParent;
1288        }
1289    }
1290   
1291    return pos;
1292};
1293
1294/**
1295 * Function: getViewportElement
1296 * Returns die viewport element of the document. The viewport element is
1297 * usually document.documentElement, except in IE,where it is either
1298 * document.body or document.documentElement, depending on the document's
1299 * compatibility mode (see
1300 * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
1301 */
1302OpenLayers.Util.getViewportElement = function() {
1303    var viewportElement = arguments.callee.viewportElement;
1304    if (viewportElement == undefined) {
1305        viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
1306            document.compatMode != 'CSS1Compat') ? document.body :
1307            document.documentElement;
1308        arguments.callee.viewportElement = viewportElement;
1309    }
1310    return viewportElement;
1311};
1312
1313/**
1314 * Function: isEquivalentUrl
1315 * Test two URLs for equivalence.
1316 *
1317 * Setting 'ignoreCase' allows for case-independent comparison.
1318 *
1319 * Comparison is based on:
1320 *  - Protocol
1321 *  - Host (evaluated without the port)
1322 *  - Port (set 'ignorePort80' to ignore "80" values)
1323 *  - Hash ( set 'ignoreHash' to disable)
1324 *  - Pathname (for relative <-> absolute comparison)
1325 *  - Arguments (so they can be out of order)
1326 * 
1327 * Parameters:
1328 * url1 - {String}
1329 * url2 - {String}
1330 * options - {Object} Allows for customization of comparison:
1331 *                    'ignoreCase' - Default is True
1332 *                    'ignorePort80' - Default is True
1333 *                    'ignoreHash' - Default is True
1334 *
1335 * Returns:
1336 * {Boolean} Whether or not the two URLs are equivalent
1337 */
1338OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
1339    options = options || {};
1340
1341    OpenLayers.Util.applyDefaults(options, {
1342        ignoreCase: true,
1343        ignorePort80: true,
1344        ignoreHash: true
1345    });
1346
1347    var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
1348    var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
1349
1350    //compare all keys except for "args" (treated below)
1351    for(var key in urlObj1) {
1352        if(key !== "args") {
1353            if(urlObj1[key] != urlObj2[key]) {
1354                return false;
1355            }
1356        }
1357    }
1358
1359    // compare search args - irrespective of order
1360    for(var key in urlObj1.args) {
1361        if(urlObj1.args[key] != urlObj2.args[key]) {
1362            return false;
1363        }
1364        delete urlObj2.args[key];
1365    }
1366    // urlObj2 shouldn't have any args left
1367    for(var key in urlObj2.args) {
1368        return false;
1369    }
1370   
1371    return true;
1372};
1373
1374/**
1375 * Function: createUrlObject
1376 *
1377 * Parameters:
1378 * url - {String}
1379 * options - {Object} A hash of options.  Can be one of:
1380 *            ignoreCase: lowercase url,
1381 *            ignorePort80: don't include explicit port if port is 80,
1382 *            ignoreHash: Don't include part of url after the hash (#).
1383 *
1384 * Returns:
1385 * {Object} An object with separate url, a, port, host, and args parsed out
1386 *          and ready for comparison
1387 */
1388OpenLayers.Util.createUrlObject = function(url, options) {
1389    options = options || {};
1390
1391    // deal with relative urls first
1392    if(!(/^\w+:\/\//).test(url)) {
1393        var loc = window.location;
1394        var port = loc.port ? ":" + loc.port : "";
1395        var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
1396        if(url.indexOf("/") === 0) {
1397            // full pathname
1398            url = fullUrl + url;
1399        } else {
1400            // relative to current path
1401            var parts = loc.pathname.split("/");
1402            parts.pop();
1403            url = fullUrl + parts.join("/") + "/" + url;
1404        }
1405    }
1406 
1407    if (options.ignoreCase) {
1408        url = url.toLowerCase(); 
1409    }
1410
1411    var a = document.createElement('a');
1412    a.href = url;
1413   
1414    var urlObject = {};
1415   
1416    //host (without port)
1417    urlObject.host = a.host.split(":").shift();
1418
1419    //protocol
1420    urlObject.protocol = a.protocol; 
1421
1422    //port (get uniform browser behavior with port 80 here)
1423    if(options.ignorePort80) {
1424        urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
1425    } else {
1426        urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
1427    }
1428
1429    //hash
1430    urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash; 
1431   
1432    //args
1433    var queryString = a.search;
1434    if (!queryString) {
1435        var qMark = url.indexOf("?");
1436        queryString = (qMark != -1) ? url.substr(qMark) : "";
1437    }
1438    urlObject.args = OpenLayers.Util.getParameters(queryString);
1439
1440    // pathname
1441    //
1442    // This is a workaround for Internet Explorer where
1443    // window.location.pathname has a leading "/", but
1444    // a.pathname has no leading "/".
1445    urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
1446   
1447    return urlObject; 
1448};
1449 
1450/**
1451 * Function: removeTail
1452 * Takes a url and removes everything after the ? and #
1453 *
1454 * Parameters:
1455 * url - {String} The url to process
1456 *
1457 * Returns:
1458 * {String} The string with all queryString and Hash removed
1459 */
1460OpenLayers.Util.removeTail = function(url) {
1461    var head = null;
1462   
1463    var qMark = url.indexOf("?");
1464    var hashMark = url.indexOf("#");
1465
1466    if (qMark == -1) {
1467        head = (hashMark != -1) ? url.substr(0,hashMark) : url;
1468    } else {
1469        head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) 
1470                                  : url.substr(0, qMark);
1471    }
1472    return head;
1473};
1474
1475/**
1476 * Constant: IS_GECKO
1477 * {Boolean} True if the userAgent reports the browser to use the Gecko engine
1478 */
1479OpenLayers.IS_GECKO = (function() {
1480    var ua = navigator.userAgent.toLowerCase();
1481    return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
1482})();
1483
1484/**
1485 * Constant: BROWSER_NAME
1486 * {String}
1487 * A substring of the navigator.userAgent property.  Depending on the userAgent
1488 *     property, this will be the empty string or one of the following:
1489 *     * "opera" -- Opera
1490 *     * "msie"  -- Internet Explorer
1491 *     * "safari" -- Safari
1492 *     * "firefox" -- Firefox
1493 *     * "mozilla" -- Mozilla
1494 */
1495OpenLayers.BROWSER_NAME = (function() {
1496    var name = "";
1497    var ua = navigator.userAgent.toLowerCase();
1498    if (ua.indexOf("opera") != -1) {
1499        name = "opera";
1500    } else if (ua.indexOf("msie") != -1) {
1501        name = "msie";
1502    } else if (ua.indexOf("safari") != -1) {
1503        name = "safari";
1504    } else if (ua.indexOf("mozilla") != -1) {
1505        if (ua.indexOf("firefox") != -1) {
1506            name = "firefox";
1507        } else {
1508            name = "mozilla";
1509        }
1510    }
1511    return name;
1512})();
1513
1514/**
1515 * Function: getBrowserName
1516 *
1517 * Returns:
1518 * {String} A string which specifies which is the current
1519 *          browser in which we are running.
1520 *
1521 *          Currently-supported browser detection and codes:
1522 *           * 'opera' -- Opera
1523 *           * 'msie'  -- Internet Explorer
1524 *           * 'safari' -- Safari
1525 *           * 'firefox' -- Firefox
1526 *           * 'mozilla' -- Mozilla
1527 *
1528 *          If we are unable to property identify the browser, we
1529 *           return an empty string.
1530 */
1531OpenLayers.Util.getBrowserName = function() {
1532    return OpenLayers.BROWSER_NAME;
1533};
1534
1535/**
1536 * Method: getRenderedDimensions
1537 * Renders the contentHTML offscreen to determine actual dimensions for
1538 *     popup sizing. As we need layout to determine dimensions the content
1539 *     is rendered -9999px to the left and absolute to ensure the
1540 *     scrollbars do not flicker
1541 *     
1542 * Parameters:
1543 * contentHTML
1544 * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is
1545 *     specified, we fix that dimension of the div to be measured. This is
1546 *     useful in the case where we have a limit in one dimension and must
1547 *     therefore meaure the flow in the other dimension.
1548 * options - {Object}
1549 *
1550 * Allowed Options:
1551 *     displayClass - {String} Optional parameter.  A CSS class name(s) string
1552 *         to provide the CSS context of the rendered content.
1553 *     containerElement - {DOMElement} Optional parameter. Insert the HTML to
1554 *         this node instead of the body root when calculating dimensions.
1555 *
1556 * Returns:
1557 * {OpenLayers.Size}
1558 */
1559OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
1560   
1561    var w, h;
1562   
1563    // create temp container div with restricted size
1564    var container = document.createElement("div");
1565    container.style.visibility = "hidden";
1566       
1567    var containerElement = (options && options.containerElement) 
1568        ? options.containerElement : document.body;
1569
1570    //fix a dimension, if specified.
1571    if (size) {
1572        if (size.w) {
1573            w = size.w;
1574            container.style.width = w + "px";
1575        } else if (size.h) {
1576            h = size.h;
1577            container.style.height = h + "px";
1578        }
1579    }
1580
1581    //add css classes, if specified
1582    if (options && options.displayClass) {
1583        container.className = options.displayClass;
1584    }
1585   
1586    // create temp content div and assign content
1587    var content = document.createElement("div");
1588    content.innerHTML = contentHTML;
1589   
1590    // we need overflow visible when calculating the size
1591    content.style.overflow = "visible";
1592    if (content.childNodes) {
1593        for (var i=0, l=content.childNodes.length; i<l; i++) {
1594            if (!content.childNodes[i].style) continue;
1595            content.childNodes[i].style.overflow = "visible";
1596        }
1597    }
1598   
1599    // add content to restricted container
1600    container.appendChild(content);
1601   
1602    // append container to body for rendering
1603    containerElement.appendChild(container);
1604   
1605    // Opera and IE7 can't handle a node with position:aboslute if it inherits
1606    // position:absolute from a parent.
1607    var parentHasPositionAbsolute = false;
1608    var parent = container.parentNode;
1609    while (parent && parent.tagName.toLowerCase()!="body") {
1610        var parentPosition = OpenLayers.Element.getStyle(parent, "position");
1611        if(parentPosition == "absolute") {
1612            parentHasPositionAbsolute = true;
1613            break;
1614        } else if (parentPosition && parentPosition != "static") {
1615            break;
1616        }
1617        parent = parent.parentNode;
1618    }
1619
1620    if(!parentHasPositionAbsolute) {
1621        container.style.position = "absolute";
1622    }
1623   
1624    // calculate scroll width of content and add corners and shadow width
1625    if (!w) {
1626        w = parseInt(content.scrollWidth);
1627   
1628        // update container width to allow height to adjust
1629        container.style.width = w + "px";
1630    }       
1631    // capture height and add shadow and corner image widths
1632    if (!h) {
1633        h = parseInt(content.scrollHeight);
1634    }
1635
1636    // remove elements
1637    container.removeChild(content);
1638    containerElement.removeChild(container);
1639   
1640    return new OpenLayers.Size(w, h);
1641};
1642
1643/**
1644 * APIFunction: getScrollbarWidth
1645 * This function has been modified by the OpenLayers from the original version,
1646 *     written by Matthew Eernisse and released under the Apache 2
1647 *     license here:
1648 *
1649 *     http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
1650 *
1651 *     It has been modified simply to cache its value, since it is physically
1652 *     impossible that this code could ever run in more than one browser at
1653 *     once.
1654 *
1655 * Returns:
1656 * {Integer}
1657 */
1658OpenLayers.Util.getScrollbarWidth = function() {
1659   
1660    var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
1661   
1662    if (scrollbarWidth == null) {
1663        var scr = null;
1664        var inn = null;
1665        var wNoScroll = 0;
1666        var wScroll = 0;
1667   
1668        // Outer scrolling div
1669        scr = document.createElement('div');
1670        scr.style.position = 'absolute';
1671        scr.style.top = '-1000px';
1672        scr.style.left = '-1000px';
1673        scr.style.width = '100px';
1674        scr.style.height = '50px';
1675        // Start with no scrollbar
1676        scr.style.overflow = 'hidden';
1677   
1678        // Inner content div
1679        inn = document.createElement('div');
1680        inn.style.width = '100%';
1681        inn.style.height = '200px';
1682   
1683        // Put the inner div in the scrolling div
1684        scr.appendChild(inn);
1685        // Append the scrolling div to the doc
1686        document.body.appendChild(scr);
1687   
1688        // Width of the inner div sans scrollbar
1689        wNoScroll = inn.offsetWidth;
1690   
1691        // Add the scrollbar
1692        scr.style.overflow = 'scroll';
1693        // Width of the inner div width scrollbar
1694        wScroll = inn.offsetWidth;
1695   
1696        // Remove the scrolling div from the doc
1697        document.body.removeChild(document.body.lastChild);
1698   
1699        // Pixel width of the scroller
1700        OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
1701        scrollbarWidth = OpenLayers.Util._scrollbarWidth;
1702    }
1703
1704    return scrollbarWidth;
1705};
1706
1707/**
1708 * APIFunction: getFormattedLonLat
1709 * This function will return latitude or longitude value formatted as
1710 *
1711 * Parameters:
1712 * coordinate - {Float} the coordinate value to be formatted
1713 * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
1714 *          to be formatted (default = lat)
1715 * dmsOption - {String} specify the precision of the output can be one of:
1716 *           'dms' show degrees minutes and seconds
1717 *           'dm' show only degrees and minutes
1718 *           'd' show only degrees
1719 *
1720 * Returns:
1721 * {String} the coordinate value formatted as a string
1722 */
1723OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
1724    if (!dmsOption) {
1725        dmsOption = 'dms';    //default to show degree, minutes, seconds
1726    }
1727   
1728    coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
1729   
1730    var abscoordinate = Math.abs(coordinate);
1731    var coordinatedegrees = Math.floor(abscoordinate);
1732
1733    var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
1734    var tempcoordinateminutes = coordinateminutes;
1735    coordinateminutes = Math.floor(coordinateminutes);
1736    var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
1737    coordinateseconds =  Math.round(coordinateseconds*10);
1738    coordinateseconds /= 10;
1739
1740    if( coordinateseconds >= 60) { 
1741        coordinateseconds -= 60; 
1742        coordinateminutes += 1; 
1743        if( coordinateminutes >= 60) { 
1744            coordinateminutes -= 60; 
1745            coordinatedegrees += 1; 
1746        } 
1747    }
1748   
1749    if( coordinatedegrees < 10 ) {
1750        coordinatedegrees = "0" + coordinatedegrees;
1751    }
1752    var str = coordinatedegrees + "\u00B0";
1753
1754    if (dmsOption.indexOf('dm') >= 0) {
1755        if( coordinateminutes < 10 ) {
1756            coordinateminutes = "0" + coordinateminutes;
1757        }
1758        str += coordinateminutes + "'";
1759 
1760        if (dmsOption.indexOf('dms') >= 0) {
1761            if( coordinateseconds < 10 ) {
1762                coordinateseconds = "0" + coordinateseconds;
1763            }
1764            str += coordinateseconds + '"';
1765        }
1766    }
1767   
1768    if (axis == "lon") {
1769        str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
1770    } else {
1771        str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
1772    }
1773    return str;
1774};
1775
Note: See TracBrowser for help on using the repository browser.