Added minified version of OpenLayers, compiled with wrmap.cfg.
authorphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Fri, 13 Sep 2013 20:31:28 +0000 (20:31 +0000)
committerphilipp <philipp@7aebc617-e5e2-0310-91dc-80fb5f6d2477>
Fri, 13 Sep 2013 20:31:28 +0000 (20:31 +0000)
git-svn-id: http://www.winterrodeln.org/svn/servermediawiki/mediawiki_extensions/wrmap/trunk@1578 7aebc617-e5e2-0310-91dc-80fb5f6d2477

openlayers/OpenLayers.js [new file with mode: 0644]
openlayers/OpenLayers.min.js [new file with mode: 0644]
openlayers/wrmap.cfg [new file with mode: 0644]

diff --git a/openlayers/OpenLayers.js b/openlayers/OpenLayers.js
new file mode 100644 (file)
index 0000000..13d8d3f
--- /dev/null
@@ -0,0 +1,41158 @@
+/*
+
+  OpenLayers.js -- OpenLayers Map Viewer Library
+
+  Copyright (c) 2006-2013 by OpenLayers Contributors
+  Published under the 2-clause BSD license.
+  See http://openlayers.org/dev/license.txt for the full text of the license, and http://openlayers.org/dev/authors.txt for full list of contributors.
+
+  Includes compressed code under the following licenses:
+
+  (For uncompressed versions of the code used, please see the
+  OpenLayers Github repository: <https://github.com/openlayers/openlayers>)
+
+*/
+
+/**
+ * Contains XMLHttpRequest.js <http://code.google.com/p/xmlhttprequest/>
+ * Copyright 2007 Sergey Ilinsky (http://www.ilinsky.com)
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ * http://www.apache.org/licenses/LICENSE-2.0
+ */
+
+/**
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission of Yahoo! Inc.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+/* ======================================================================
+    OpenLayers/SingleFile.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+var OpenLayers = {
+    /**
+     * Constant: VERSION_NUMBER
+     */
+    VERSION_NUMBER: "Release 2.13.1",
+
+    /**
+     * Constant: singleFile
+     * TODO: remove this in 3.0 when we stop supporting build profiles that
+     * include OpenLayers.js
+     */
+    singleFile: true,
+
+    /**
+     * Method: _getScriptLocation
+     * Return the path to this script. This is also implemented in
+     * OpenLayers.js
+     *
+     * Returns:
+     * {String} Path to this script
+     */
+    _getScriptLocation: (function() {
+        var r = new RegExp("(^|(.*?\\/))(OpenLayers[^\\/]*?\\.js)(\\?|$)"),
+            s = document.getElementsByTagName('script'),
+            src, m, l = "";
+        for(var i=0, len=s.length; i<len; i++) {
+            src = s[i].getAttribute('src');
+            if(src) {
+                m = src.match(r);
+                if(m) {
+                    l = m[1];
+                    break;
+                }
+            }
+        }
+        return (function() { return l; });
+    })(),
+    
+    /**
+     * Property: ImgPath
+     * {String} Set this to the path where control images are stored, a path  
+     * given here must end with a slash. If set to '' (which is the default) 
+     * OpenLayers will use its script location + "img/".
+     * 
+     * You will need to set this property when you have a singlefile build of 
+     * OpenLayers that either is not named "OpenLayers.js" or if you move
+     * the file in a way such that the image directory cannot be derived from 
+     * the script location.
+     * 
+     * If your custom OpenLayers build is named "my-custom-ol.js" and the images
+     * of OpenLayers are in a folder "/resources/external/images/ol" a correct
+     * way of including OpenLayers in your HTML would be:
+     * 
+     * (code)
+     *   <script src="/path/to/my-custom-ol.js" type="text/javascript"></script>
+     *   <script type="text/javascript">
+     *      // tell OpenLayers where the control images are
+     *      // remember the trailing slash
+     *      OpenLayers.ImgPath = "/resources/external/images/ol/";
+     *   </script>
+     * (end code)
+     * 
+     * Please remember that when your OpenLayers script is not named 
+     * "OpenLayers.js" you will have to make sure that the default theme is 
+     * loaded into the page by including an appropriate <link>-tag, 
+     * e.g.:
+     * 
+     * (code)
+     *   <link rel="stylesheet" href="/path/to/default/style.css"  type="text/css">
+     * (end code)
+     */
+    ImgPath : ''
+};
+/* ======================================================================
+    OpenLayers/BaseTypes.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/** 
+ * Header: OpenLayers Base Types
+ * OpenLayers custom string, number and function functions are described here.
+ */
+
+/**
+ * Namespace: OpenLayers.String
+ * Contains convenience functions for string manipulation.
+ */
+OpenLayers.String = {
+
+    /**
+     * APIFunction: startsWith
+     * Test whether a string starts with another string. 
+     * 
+     * Parameters:
+     * str - {String} The string to test.
+     * sub - {String} The substring to look for.
+     *  
+     * Returns:
+     * {Boolean} The first string starts with the second.
+     */
+    startsWith: function(str, sub) {
+        return (str.indexOf(sub) == 0);
+    },
+
+    /**
+     * APIFunction: contains
+     * Test whether a string contains another string.
+     * 
+     * Parameters:
+     * str - {String} The string to test.
+     * sub - {String} The substring to look for.
+     * 
+     * Returns:
+     * {Boolean} The first string contains the second.
+     */
+    contains: function(str, sub) {
+        return (str.indexOf(sub) != -1);
+    },
+    
+    /**
+     * APIFunction: trim
+     * Removes leading and trailing whitespace characters from a string.
+     * 
+     * Parameters:
+     * str - {String} The (potentially) space padded string.  This string is not
+     *     modified.
+     * 
+     * Returns:
+     * {String} A trimmed version of the string with all leading and 
+     *     trailing spaces removed.
+     */
+    trim: function(str) {
+        return str.replace(/^\s\s*/, '').replace(/\s\s*$/, '');
+    },
+    
+    /**
+     * APIFunction: camelize
+     * Camel-case a hyphenated string. 
+     *     Ex. "chicken-head" becomes "chickenHead", and
+     *     "-chicken-head" becomes "ChickenHead".
+     *
+     * Parameters:
+     * str - {String} The string to be camelized.  The original is not modified.
+     * 
+     * Returns:
+     * {String} The string, camelized
+     */
+    camelize: function(str) {
+        var oStringList = str.split('-');
+        var camelizedString = oStringList[0];
+        for (var i=1, len=oStringList.length; i<len; i++) {
+            var s = oStringList[i];
+            camelizedString += s.charAt(0).toUpperCase() + s.substring(1);
+        }
+        return camelizedString;
+    },
+    
+    /**
+     * APIFunction: format
+     * Given a string with tokens in the form ${token}, return a string
+     *     with tokens replaced with properties from the given context
+     *     object.  Represent a literal "${" by doubling it, e.g. "${${".
+     *
+     * Parameters:
+     * template - {String} A string with tokens to be replaced.  A template
+     *     has the form "literal ${token}" where the token will be replaced
+     *     by the value of context["token"].
+     * context - {Object} An optional object with properties corresponding
+     *     to the tokens in the format string.  If no context is sent, the
+     *     window object will be used.
+     * args - {Array} Optional arguments to pass to any functions found in
+     *     the context.  If a context property is a function, the token
+     *     will be replaced by the return from the function called with
+     *     these arguments.
+     *
+     * Returns:
+     * {String} A string with tokens replaced from the context object.
+     */
+    format: function(template, context, args) {
+        if(!context) {
+            context = window;
+        }
+
+        // Example matching: 
+        // str   = ${foo.bar}
+        // match = foo.bar
+        var replacer = function(str, match) {
+            var replacement;
+
+            // Loop through all subs. Example: ${a.b.c}
+            // 0 -> replacement = context[a];
+            // 1 -> replacement = context[a][b];
+            // 2 -> replacement = context[a][b][c];
+            var subs = match.split(/\.+/);
+            for (var i=0; i< subs.length; i++) {
+                if (i == 0) {
+                    replacement = context;
+                }
+                if (replacement === undefined) {
+                    break;
+                }
+                replacement = replacement[subs[i]];
+            }
+
+            if(typeof replacement == "function") {
+                replacement = args ?
+                    replacement.apply(null, args) :
+                    replacement();
+            }
+
+            // If replacement is undefined, return the string 'undefined'.
+            // This is a workaround for a bugs in browsers not properly 
+            // dealing with non-participating groups in regular expressions:
+            // http://blog.stevenlevithan.com/archives/npcg-javascript
+            if (typeof replacement == 'undefined') {
+                return 'undefined';
+            } else {
+                return replacement; 
+            }
+        };
+
+        return template.replace(OpenLayers.String.tokenRegEx, replacer);
+    },
+
+    /**
+     * Property: tokenRegEx
+     * Used to find tokens in a string.
+     * Examples: ${a}, ${a.b.c}, ${a-b}, ${5}
+     */
+    tokenRegEx:  /\$\{([\w.]+?)\}/g,
+    
+    /**
+     * Property: numberRegEx
+     * Used to test strings as numbers.
+     */
+    numberRegEx: /^([+-]?)(?=\d|\.\d)\d*(\.\d*)?([Ee]([+-]?\d+))?$/,
+    
+    /**
+     * APIFunction: isNumeric
+     * Determine whether a string contains only a numeric value.
+     *
+     * Examples:
+     * (code)
+     * OpenLayers.String.isNumeric("6.02e23") // true
+     * OpenLayers.String.isNumeric("12 dozen") // false
+     * OpenLayers.String.isNumeric("4") // true
+     * OpenLayers.String.isNumeric(" 4 ") // false
+     * (end)
+     *
+     * Returns:
+     * {Boolean} String contains only a number.
+     */
+    isNumeric: function(value) {
+        return OpenLayers.String.numberRegEx.test(value);
+    },
+    
+    /**
+     * APIFunction: numericIf
+     * Converts a string that appears to be a numeric value into a number.
+     * 
+     * Parameters:
+     * value - {String}
+     * trimWhitespace - {Boolean}
+     *
+     * Returns:
+     * {Number|String} a Number if the passed value is a number, a String
+     *     otherwise. 
+     */
+    numericIf: function(value, trimWhitespace) {
+        var originalValue = value;
+        if (trimWhitespace === true && value != null && value.replace) {
+            value = value.replace(/^\s*|\s*$/g, "");
+        }
+        return OpenLayers.String.isNumeric(value) ? parseFloat(value) : originalValue;
+    }
+
+};
+
+/**
+ * Namespace: OpenLayers.Number
+ * Contains convenience functions for manipulating numbers.
+ */
+OpenLayers.Number = {
+
+    /**
+     * Property: decimalSeparator
+     * Decimal separator to use when formatting numbers.
+     */
+    decimalSeparator: ".",
+    
+    /**
+     * Property: thousandsSeparator
+     * Thousands separator to use when formatting numbers.
+     */
+    thousandsSeparator: ",",
+    
+    /**
+     * APIFunction: limitSigDigs
+     * Limit the number of significant digits on a float.
+     * 
+     * Parameters:
+     * num - {Float}
+     * sig - {Integer}
+     * 
+     * Returns:
+     * {Float} The number, rounded to the specified number of significant
+     *     digits.
+     */
+    limitSigDigs: function(num, sig) {
+        var fig = 0;
+        if (sig > 0) {
+            fig = parseFloat(num.toPrecision(sig));
+        }
+        return fig;
+    },
+    
+    /**
+     * APIFunction: format
+     * Formats a number for output.
+     * 
+     * Parameters:
+     * num  - {Float}
+     * dec  - {Integer} Number of decimal places to round to.
+     *        Defaults to 0. Set to null to leave decimal places unchanged.
+     * tsep - {String} Thousands separator.
+     *        Default is ",".
+     * dsep - {String} Decimal separator.
+     *        Default is ".".
+     *
+     * Returns:
+     * {String} A string representing the formatted number.
+     */
+    format: function(num, dec, tsep, dsep) {
+        dec = (typeof dec != "undefined") ? dec : 0; 
+        tsep = (typeof tsep != "undefined") ? tsep :
+            OpenLayers.Number.thousandsSeparator; 
+        dsep = (typeof dsep != "undefined") ? dsep :
+            OpenLayers.Number.decimalSeparator;
+
+        if (dec != null) {
+            num = parseFloat(num.toFixed(dec));
+        }
+
+        var parts = num.toString().split(".");
+        if (parts.length == 1 && dec == null) {
+            // integer where we do not want to touch the decimals
+            dec = 0;
+        }
+        
+        var integer = parts[0];
+        if (tsep) {
+            var thousands = /(-?[0-9]+)([0-9]{3})/; 
+            while(thousands.test(integer)) { 
+                integer = integer.replace(thousands, "$1" + tsep + "$2"); 
+            }
+        }
+        
+        var str;
+        if (dec == 0) {
+            str = integer;
+        } else {
+            var rem = parts.length > 1 ? parts[1] : "0";
+            if (dec != null) {
+                rem = rem + new Array(dec - rem.length + 1).join("0");
+            }
+            str = integer + dsep + rem;
+        }
+        return str;
+    },
+
+    /**
+     * Method: zeroPad
+     * Create a zero padded string optionally with a radix for casting numbers.
+     *
+     * Parameters:
+     * num - {Number} The number to be zero padded.
+     * len - {Number} The length of the string to be returned.
+     * radix - {Number} An integer between 2 and 36 specifying the base to use
+     *     for representing numeric values.
+     */
+    zeroPad: function(num, len, radix) {
+        var str = num.toString(radix || 10);
+        while (str.length < len) {
+            str = "0" + str;
+        }
+        return str;
+    }    
+};
+
+/**
+ * Namespace: OpenLayers.Function
+ * Contains convenience functions for function manipulation.
+ */
+OpenLayers.Function = {
+    /**
+     * APIFunction: bind
+     * Bind a function to an object.  Method to easily create closures with
+     *     'this' altered.
+     * 
+     * Parameters:
+     * func - {Function} Input function.
+     * object - {Object} The object to bind to the input function (as this).
+     * 
+     * Returns:
+     * {Function} A closure with 'this' set to the passed in object.
+     */
+    bind: function(func, object) {
+        // create a reference to all arguments past the second one
+        var args = Array.prototype.slice.apply(arguments, [2]);
+        return function() {
+            // Push on any additional arguments from the actual function call.
+            // These will come after those sent to the bind call.
+            var newArgs = args.concat(
+                Array.prototype.slice.apply(arguments, [0])
+            );
+            return func.apply(object, newArgs);
+        };
+    },
+    
+    /**
+     * APIFunction: bindAsEventListener
+     * Bind a function to an object, and configure it to receive the event
+     *     object as first parameter when called. 
+     * 
+     * Parameters:
+     * func - {Function} Input function to serve as an event listener.
+     * object - {Object} A reference to this.
+     * 
+     * Returns:
+     * {Function}
+     */
+    bindAsEventListener: function(func, object) {
+        return function(event) {
+            return func.call(object, event || window.event);
+        };
+    },
+    
+    /**
+     * APIFunction: False
+     * A simple function to that just does "return false". We use this to 
+     * avoid attaching anonymous functions to DOM event handlers, which 
+     * causes "issues" on IE<8.
+     * 
+     * Usage:
+     * document.onclick = OpenLayers.Function.False;
+     * 
+     * Returns:
+     * {Boolean}
+     */
+    False : function() {
+        return false;
+    },
+
+    /**
+     * APIFunction: True
+     * A simple function to that just does "return true". We use this to 
+     * avoid attaching anonymous functions to DOM event handlers, which 
+     * causes "issues" on IE<8.
+     * 
+     * Usage:
+     * document.onclick = OpenLayers.Function.True;
+     * 
+     * Returns:
+     * {Boolean}
+     */
+    True : function() {
+        return true;
+    },
+    
+    /**
+     * APIFunction: Void
+     * A reusable function that returns ``undefined``.
+     *
+     * Returns:
+     * {undefined}
+     */
+    Void: function() {}
+
+};
+
+/**
+ * Namespace: OpenLayers.Array
+ * Contains convenience functions for array manipulation.
+ */
+OpenLayers.Array = {
+
+    /**
+     * APIMethod: filter
+     * Filter an array.  Provides the functionality of the
+     *     Array.prototype.filter extension to the ECMA-262 standard.  Where
+     *     available, Array.prototype.filter will be used.
+     *
+     * Based on well known example from http://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Array/filter
+     *
+     * Parameters:
+     * array - {Array} The array to be filtered.  This array is not mutated.
+     *     Elements added to this array by the callback will not be visited.
+     * callback - {Function} A function that is called for each element in
+     *     the array.  If this function returns true, the element will be
+     *     included in the return.  The function will be called with three
+     *     arguments: the element in the array, the index of that element, and
+     *     the array itself.  If the optional caller parameter is specified
+     *     the callback will be called with this set to caller.
+     * caller - {Object} Optional object to be set as this when the callback
+     *     is called.
+     *
+     * Returns:
+     * {Array} An array of elements from the passed in array for which the
+     *     callback returns true.
+     */
+    filter: function(array, callback, caller) {
+        var selected = [];
+        if (Array.prototype.filter) {
+            selected = array.filter(callback, caller);
+        } else {
+            var len = array.length;
+            if (typeof callback != "function") {
+                throw new TypeError();
+            }
+            for(var i=0; i<len; i++) {
+                if (i in array) {
+                    var val = array[i];
+                    if (callback.call(caller, val, i, array)) {
+                        selected.push(val);
+                    }
+                }
+            }        
+        }
+        return selected;
+    }
+    
+};
+/* ======================================================================
+    OpenLayers/BaseTypes/Class.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+/**
+ * Constructor: OpenLayers.Class
+ * Base class used to construct all other classes. Includes support for 
+ *     multiple inheritance. 
+ *     
+ * This constructor is new in OpenLayers 2.5.  At OpenLayers 3.0, the old 
+ *     syntax for creating classes and dealing with inheritance 
+ *     will be removed.
+ * 
+ * To create a new OpenLayers-style class, use the following syntax:
+ * (code)
+ *     var MyClass = OpenLayers.Class(prototype);
+ * (end)
+ *
+ * To create a new OpenLayers-style class with multiple inheritance, use the
+ *     following syntax:
+ * (code)
+ *     var MyClass = OpenLayers.Class(Class1, Class2, prototype);
+ * (end)
+ * 
+ * Note that instanceof reflection will only reveal Class1 as superclass.
+ *
+ */
+OpenLayers.Class = function() {
+    var len = arguments.length;
+    var P = arguments[0];
+    var F = arguments[len-1];
+
+    var C = typeof F.initialize == "function" ?
+        F.initialize :
+        function(){ P.prototype.initialize.apply(this, arguments); };
+
+    if (len > 1) {
+        var newArgs = [C, P].concat(
+                Array.prototype.slice.call(arguments).slice(1, len-1), F);
+        OpenLayers.inherit.apply(null, newArgs);
+    } else {
+        C.prototype = F;
+    }
+    return C;
+};
+
+/**
+ * Function: OpenLayers.inherit
+ *
+ * Parameters:
+ * C - {Object} the class that inherits
+ * P - {Object} the superclass to inherit from
+ *
+ * In addition to the mandatory C and P parameters, an arbitrary number of
+ * objects can be passed, which will extend C.
+ */
+OpenLayers.inherit = function(C, P) {
+   var F = function() {};
+   F.prototype = P.prototype;
+   C.prototype = new F;
+   var i, l, o;
+   for(i=2, l=arguments.length; i<l; i++) {
+       o = arguments[i];
+       if(typeof o === "function") {
+           o = o.prototype;
+       }
+       OpenLayers.Util.extend(C.prototype, o);
+   }
+};
+
+/**
+ * APIFunction: extend
+ * Copy all properties of a source object to a destination object.  Modifies
+ *     the passed in destination object.  Any properties on the source object
+ *     that are set to undefined will not be (re)set on the destination object.
+ *
+ * Parameters:
+ * destination - {Object} The object that will be modified
+ * source - {Object} The object with properties to be set on the destination
+ *
+ * Returns:
+ * {Object} The destination object.
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+OpenLayers.Util.extend = function(destination, source) {
+    destination = destination || {};
+    if (source) {
+        for (var property in source) {
+            var value = source[property];
+            if (value !== undefined) {
+                destination[property] = value;
+            }
+        }
+
+        /**
+         * IE doesn't include the toString property when iterating over an object's
+         * properties with the for(property in object) syntax.  Explicitly check if
+         * the source has its own toString property.
+         */
+
+        /*
+         * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+         * prototype object" when calling hawOwnProperty if the source object
+         * is an instance of window.Event.
+         */
+
+        var sourceIsEvt = typeof window.Event == "function"
+                          && source instanceof window.Event;
+
+        if (!sourceIsEvt
+           && source.hasOwnProperty && source.hasOwnProperty("toString")) {
+            destination.toString = source.toString;
+        }
+    }
+    return destination;
+};
+/* ======================================================================
+    OpenLayers/BaseTypes/Bounds.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.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:
+ * (code)
+ *     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
+ * (end)
+ */
+OpenLayers.Bounds = OpenLayers.Class({
+
+    /**
+     * Property: left
+     * {Number} Minimum horizontal coordinate.
+     */
+    left: null,
+
+    /**
+     * Property: bottom
+     * {Number} Minimum vertical coordinate.
+     */
+    bottom: null,
+
+    /**
+     * Property: right
+     * {Number} Maximum horizontal coordinate.
+     */
+    right: null,
+
+    /**
+     * Property: top
+     * {Number} Maximum vertical coordinate.
+     */
+    top: null,
+    
+    /**
+     * Property: centerLonLat
+     * {<OpenLayers.LonLat>} A cached center location.  This should not be
+     *     accessed directly.  Use <getCenterLonLat> instead.
+     */
+    centerLonLat: null,
+
+    /**
+     * Constructor: OpenLayers.Bounds
+     * Construct a new bounds object. Coordinates can either be passed as four
+     * arguments, or as a single argument.
+     *
+     * Parameters (four arguments):
+     * 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 less than the top value.
+     * right - {Number} The right bounds.
+     * top - {Number} The top bounds.
+     *
+     * Parameters (single argument):
+     * bounds - {Array(Number)} [left, bottom, right, top]
+     */
+    initialize: function(left, bottom, right, top) {
+        if (OpenLayers.Util.isArray(left)) {
+            top = left[3];
+            right = left[2];
+            bottom = left[1];
+            left = left[0];
+        }
+        if (left != null) {
+            this.left = OpenLayers.Util.toFloat(left);
+        }
+        if (bottom != null) {
+            this.bottom = OpenLayers.Util.toFloat(bottom);
+        }
+        if (right != null) {
+            this.right = OpenLayers.Util.toFloat(right);
+        }
+        if (top != null) {
+            this.top = OpenLayers.Util.toFloat(top);
+        }
+    },
+
+    /**
+     * Method: clone
+     * Create a cloned instance of this bounds.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} A fresh copy of the bounds
+     */
+    clone:function() {
+        return new OpenLayers.Bounds(this.left, this.bottom, 
+                                     this.right, this.top);
+    },
+
+    /**
+     * Method: equals
+     * Test a two bounds for equivalence.
+     *
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>}
+     *
+     * Returns:
+     * {Boolean} The passed-in bounds object has the same left,
+     *           right, top, bottom components as this.  Note that if bounds 
+     *           passed in is null, returns false.
+     */
+    equals:function(bounds) {
+        var equals = false;
+        if (bounds != null) {
+            equals = ((this.left == bounds.left) && 
+                      (this.right == bounds.right) &&
+                      (this.top == bounds.top) && 
+                      (this.bottom == bounds.bottom));
+        }
+        return equals;
+    },
+
+    /** 
+     * APIMethod: toString
+     * Returns a string representation of the bounds object.
+     * 
+     * Returns:
+     * {String} String representation of bounds object. 
+     */
+    toString:function() {
+        return [this.left, this.bottom, this.right, this.top].join(",");
+    },
+
+    /**
+     * APIMethod: toArray
+     * Returns an array representation of the bounds object.
+     *
+     * Returns an array of left, bottom, right, top properties, or -- when the
+     *     optional parameter is true -- an array of the  bottom, left, top,
+     *     right properties.
+     *
+     * 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
+     * Returns a boundingbox-string representation of the bounds object.
+     * 
+     * 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.
+     *          (e.g. "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:
+     * {<OpenLayers.Geometry.Polygon>} A new polygon with the coordinates
+     *     of this bounds.
+     */
+    toGeometry: function() {
+        return new OpenLayers.Geometry.Polygon([
+            new OpenLayers.Geometry.LinearRing([
+                new OpenLayers.Geometry.Point(this.left, this.bottom),
+                new OpenLayers.Geometry.Point(this.right, this.bottom),
+                new OpenLayers.Geometry.Point(this.right, this.top),
+                new OpenLayers.Geometry.Point(this.left, this.top)
+            ])
+        ]);
+    },
+    
+    /**
+     * APIMethod: getWidth
+     * Returns the width of the bounds.
+     * 
+     * Returns:
+     * {Float} The width of the bounds (right minus left).
+     */
+    getWidth:function() {
+        return (this.right - this.left);
+    },
+
+    /**
+     * APIMethod: getHeight
+     * Returns the height of the bounds.
+     * 
+     * Returns:
+     * {Float} The height of the bounds (top minus bottom).
+     */
+    getHeight:function() {
+        return (this.top - this.bottom);
+    },
+
+    /**
+     * APIMethod: getSize
+     * Returns an <OpenLayers.Size> object of the bounds.
+     * 
+     * Returns:
+     * {<OpenLayers.Size>} The size of the bounds.
+     */
+    getSize:function() {
+        return new OpenLayers.Size(this.getWidth(), this.getHeight());
+    },
+
+    /**
+     * APIMethod: getCenterPixel
+     * Returns the <OpenLayers.Pixel> object which represents the center of the
+     *     bounds.
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The center of the bounds in pixel space.
+     */
+    getCenterPixel:function() {
+        return new OpenLayers.Pixel( (this.left + this.right) / 2,
+                                     (this.bottom + this.top) / 2);
+    },
+
+    /**
+     * APIMethod: getCenterLonLat
+     * Returns the <OpenLayers.LonLat> object which represents the center of the
+     *     bounds.
+     *
+     * Returns:
+     * {<OpenLayers.LonLat>} The center of the bounds in map space.
+     */
+    getCenterLonLat:function() {
+        if(!this.centerLonLat) {
+            this.centerLonLat = new OpenLayers.LonLat(
+                (this.left + this.right) / 2, (this.bottom + this.top) / 2
+            );
+        }
+        return this.centerLonLat;
+    },
+
+    /**
+     * APIMethod: scale
+     * Scales the bounds around a pixel or lonlat. Note that the new 
+     *     bounds may return non-integer properties, even if a pixel
+     *     is passed. 
+     * 
+     * Parameters:
+     * ratio - {Float} 
+     * origin - {<OpenLayers.Pixel> or <OpenLayers.LonLat>}
+     *          Default is center.
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} 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
+     * Shifts the coordinates of the bound by the given horizontal and vertical
+     *     deltas.
+     *
+     * (start code)
+     * var bounds = new OpenLayers.Bounds(0, 0, 10, 10);
+     * bounds.toString();
+     * // => "0,0,10,10"
+     *
+     * bounds.add(-1.5, 4).toString();
+     * // => "-1.5,4,8.5,14"
+     * (end)
+     *
+     * This method will throw a TypeError if it is passed null as an argument.
+     *
+     * Parameters:
+     * x - {Float} horizontal delta
+     * y - {Float} vertical delta
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} A new bounds whose coordinates are the same as
+     *     this, but shifted by the passed-in x and y values.
+     */
+    add:function(x, y) {
+        if ( (x == null) || (y == null) ) {
+            throw new TypeError('Bounds.add cannot receive null values');
+        }
+        return new OpenLayers.Bounds(this.left + x, this.bottom + y,
+                                     this.right + x, this.top + y);
+    },
+    
+    /**
+     * APIMethod: extend
+     * Extend the bounds to include the <OpenLayers.LonLat>,
+     *     <OpenLayers.Geometry.Point> or <OpenLayers.Bounds> specified.
+     *
+     * Please note that this function assumes that left < right and
+     *     bottom < top.
+     *
+     * Parameters:
+     * object - {<OpenLayers.LonLat>, <OpenLayers.Geometry.Point> or
+     *     <OpenLayers.Bounds>} The object to be included in the new bounds
+     *     object.
+     */
+    extend:function(object) {
+        if (object) {
+            switch(object.CLASS_NAME) {
+                case "OpenLayers.LonLat":
+                    this.extendXY(object.lon, object.lat);
+                    break;
+                case "OpenLayers.Geometry.Point":
+                    this.extendXY(object.x, object.y);
+                    break;
+
+                case "OpenLayers.Bounds":
+                    // clear cached center location
+                    this.centerLonLat = null;
+
+                    if ( (this.left == null) || (object.left < this.left)) {
+                        this.left = object.left;
+                    }
+                    if ( (this.bottom == null) || (object.bottom < this.bottom) ) {
+                        this.bottom = object.bottom;
+                    }
+                    if ( (this.right == null) || (object.right > this.right) ) {
+                        this.right = object.right;
+                    }
+                    if ( (this.top == null) || (object.top > this.top) ) {
+                        this.top = object.top;
+                    }
+                    break;
+            }
+        }
+    },
+
+    /**
+     * APIMethod: extendXY
+     * Extend the bounds to include the XY coordinate specified.
+     *
+     * Parameters:
+     * x - {number} The X part of the the coordinate.
+     * y - {number} The Y part of the the coordinate.
+     */
+    extendXY:function(x, y) {
+        // clear cached center location
+        this.centerLonLat = null;
+
+        if ((this.left == null) || (x < this.left)) {
+            this.left = x;
+        }
+        if ((this.bottom == null) || (y < this.bottom)) {
+            this.bottom = y;
+        }
+        if ((this.right == null) || (x > this.right)) {
+            this.right = x;
+        }
+        if ((this.top == null) || (y > this.top)) {
+            this.top = y;
+        }
+    },
+
+    /**
+     * APIMethod: containsLonLat
+     * Returns whether the bounds object contains the given <OpenLayers.LonLat>.
+     * 
+     * Parameters:
+     * ll - {<OpenLayers.LonLat>|Object} OpenLayers.LonLat or an
+     *     object with a 'lon' and 'lat' properties.
+     * options - {Object} Optional parameters
+     *
+     * Acceptable options:
+     * inclusive - {Boolean} Whether or not to include the border.
+     *     Default is true.
+     * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, the
+     *     ll will be considered as contained if it exceeds the world bounds,
+     *     but can be wrapped around the dateline so it is contained by this
+     *     bounds.
+     *
+     * Returns:
+     * {Boolean} The passed-in lonlat is within this bounds.
+     */
+    containsLonLat: function(ll, options) {
+        if (typeof options === "boolean") {
+            options =  {inclusive: options};
+        }
+        options = options || {};
+        var contains = this.contains(ll.lon, ll.lat, options.inclusive),
+            worldBounds = options.worldBounds;
+        if (worldBounds && !contains) {
+            var worldWidth = worldBounds.getWidth();
+            var worldCenterX = (worldBounds.left + worldBounds.right) / 2;
+            var worldsAway = Math.round((ll.lon - worldCenterX) / worldWidth);
+            contains = this.containsLonLat({
+                lon: ll.lon - worldsAway * worldWidth,
+                lat: ll.lat
+            }, {inclusive: options.inclusive});
+        }
+        return contains;
+    },
+
+    /**
+     * APIMethod: containsPixel
+     * Returns whether the bounds object contains the given <OpenLayers.Pixel>.
+     * 
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     * inclusive - {Boolean} Whether or not to include the border. Default is
+     *     true.
+     *
+     * Returns:
+     * {Boolean} The passed-in pixel is within this bounds.
+     */
+    containsPixel:function(px, inclusive) {
+        return this.contains(px.x, px.y, inclusive);
+    },
+    
+    /**
+     * APIMethod: contains
+     * Returns whether the bounds object contains the given x and y.
+     * 
+     * Parameters:
+     * x - {Float}
+     * y - {Float}
+     * inclusive - {Boolean} Whether or not to include the border. Default is
+     *     true.
+     *
+     * Returns:
+     * {Boolean} Whether or not the passed-in coordinates are within this
+     *     bounds.
+     */
+    contains:function(x, y, inclusive) {
+        //set default
+        if (inclusive == null) {
+            inclusive = true;
+        }
+
+        if (x == null || y == null) {
+            return false;
+        }
+
+        x = OpenLayers.Util.toFloat(x);
+        y = OpenLayers.Util.toFloat(y);
+
+        var contains = false;
+        if (inclusive) {
+            contains = ((x >= this.left) && (x <= this.right) && 
+                        (y >= this.bottom) && (y <= this.top));
+        } else {
+            contains = ((x > this.left) && (x < this.right) && 
+                        (y > this.bottom) && (y < this.top));
+        }              
+        return contains;
+    },
+
+    /**
+     * APIMethod: intersectsBounds
+     * Determine whether the target bounds intersects this bounds.  Bounds are
+     *     considered intersecting if any of their edges intersect or if one
+     *     bounds contains the other.
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>} The target bounds.
+     * options - {Object} Optional parameters.
+     * 
+     * Acceptable options:
+     * 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.
+     * worldBounds - {<OpenLayers.Bounds>} If a worldBounds is provided, two
+     *     bounds will be considered as intersecting if they intersect when 
+     *     shifted to within the world bounds.  This applies only to bounds that
+     *     cross or are completely outside the world bounds.
+     *
+     * Returns:
+     * {Boolean} The passed-in bounds object intersects this bounds.
+     */
+    intersectsBounds:function(bounds, options) {
+        if (typeof options === "boolean") {
+            options =  {inclusive: options};
+        }
+        options = options || {};
+        if (options.worldBounds) {
+            var self = this.wrapDateLine(options.worldBounds);
+            bounds = bounds.wrapDateLine(options.worldBounds);
+        } else {
+            self = this;
+        }
+        if (options.inclusive == null) {
+            options.inclusive = true;
+        }
+        var intersects = false;
+        var mightTouch = (
+            self.left == bounds.right ||
+            self.right == bounds.left ||
+            self.top == bounds.bottom ||
+            self.bottom == bounds.top
+        );
+        
+        // if the two bounds only touch at an edge, and inclusive is false,
+        // then the bounds don't *really* intersect.
+        if (options.inclusive || !mightTouch) {
+            // otherwise, if one of the boundaries even partially contains another,
+            // inclusive of the edges, then they do intersect.
+            var inBottom = (
+                ((bounds.bottom >= self.bottom) && (bounds.bottom <= self.top)) ||
+                ((self.bottom >= bounds.bottom) && (self.bottom <= bounds.top))
+            );
+            var inTop = (
+                ((bounds.top >= self.bottom) && (bounds.top <= self.top)) ||
+                ((self.top > bounds.bottom) && (self.top < bounds.top))
+            );
+            var inLeft = (
+                ((bounds.left >= self.left) && (bounds.left <= self.right)) ||
+                ((self.left >= bounds.left) && (self.left <= bounds.right))
+            );
+            var inRight = (
+                ((bounds.right >= self.left) && (bounds.right <= self.right)) ||
+                ((self.right >= bounds.left) && (self.right <= bounds.right))
+            );
+            intersects = ((inBottom || inTop) && (inLeft || inRight));
+        }
+        // document me
+        if (options.worldBounds && !intersects) {
+            var world = options.worldBounds;
+            var width = world.getWidth();
+            var selfCrosses = !world.containsBounds(self);
+            var boundsCrosses = !world.containsBounds(bounds);
+            if (selfCrosses && !boundsCrosses) {
+                bounds = bounds.add(-width, 0);
+                intersects = self.intersectsBounds(bounds, {inclusive: options.inclusive});
+            } else if (boundsCrosses && !selfCrosses) {
+                self = self.add(-width, 0);
+                intersects = bounds.intersectsBounds(self, {inclusive: options.inclusive});                
+            }
+        }
+        return intersects;
+    },
+    
+    /**
+     * APIMethod: containsBounds
+     * Returns whether the bounds object contains the given <OpenLayers.Bounds>.
+     * 
+     * bounds - {<OpenLayers.Bounds>} The target bounds.
+     * partial - {Boolean} If any of the target corners is within this bounds
+     *     consider the bounds contained.  Default is false.  If false, the
+     *     entire target bounds must be contained within this bounds.
+     * inclusive - {Boolean} Treat shared edges as contained.  Default is
+     *     true.
+     *
+     * Returns:
+     * {Boolean} The passed-in bounds object is contained within this bounds. 
+     */
+    containsBounds:function(bounds, partial, inclusive) {
+        if (partial == null) {
+            partial = false;
+        }
+        if (inclusive == null) {
+            inclusive = true;
+        }
+        var bottomLeft  = this.contains(bounds.left, bounds.bottom, inclusive);
+        var bottomRight = this.contains(bounds.right, bounds.bottom, inclusive);
+        var topLeft  = this.contains(bounds.left, bounds.top, inclusive);
+        var topRight = this.contains(bounds.right, bounds.top, inclusive);
+        
+        return (partial) ? (bottomLeft || bottomRight || topLeft || topRight)
+                         : (bottomLeft && bottomRight && topLeft && topRight);
+    },
+
+    /** 
+     * APIMethod: determineQuadrant
+     * Returns the the quadrant ("br", "tr", "tl", "bl") in which the given
+     *     <OpenLayers.LonLat> lies.
+     *
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     *
+     * Returns:
+     * {String} The quadrant ("br" "tr" "tl" "bl") of the bounds in which the
+     *     coordinate lies.
+     */
+    determineQuadrant: function(lonlat) {
+    
+        var quadrant = "";
+        var center = this.getCenterLonLat();
+        
+        quadrant += (lonlat.lat < center.lat) ? "b" : "t";
+        quadrant += (lonlat.lon < center.lon) ? "l" : "r";
+    
+        return quadrant; 
+    },
+    
+    /**
+     * APIMethod: transform
+     * Transform the Bounds object from source to dest. 
+     *
+     * Parameters: 
+     * source - {<OpenLayers.Projection>} Source projection. 
+     * dest   - {<OpenLayers.Projection>} Destination projection. 
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} Itself, for use in chaining operations.
+     */
+    transform: function(source, dest) {
+        // clear cached center location
+        this.centerLonLat = null;
+        var ll = OpenLayers.Projection.transform(
+            {'x': this.left, 'y': this.bottom}, source, dest);
+        var lr = OpenLayers.Projection.transform(
+            {'x': this.right, 'y': this.bottom}, source, dest);
+        var ul = OpenLayers.Projection.transform(
+            {'x': this.left, 'y': this.top}, source, dest);
+        var ur = OpenLayers.Projection.transform(
+            {'x': this.right, 'y': this.top}, source, dest);
+        this.left   = Math.min(ll.x, ul.x);
+        this.bottom = Math.min(ll.y, lr.y);
+        this.right  = Math.max(lr.x, ur.x);
+        this.top    = Math.max(ul.y, ur.y);
+        return this;
+    },
+
+    /**
+     * APIMethod: wrapDateLine
+     * Wraps the bounds object around the dateline.
+     *  
+     * Parameters:
+     * maxExtent - {<OpenLayers.Bounds>}
+     * options - {Object} Some possible options are:
+     *
+     * Allowed Options:
+     *                    leftTolerance - {float} Allow for a margin of error 
+     *                                            with the 'left' value of this 
+     *                                            bound.
+     *                                            Default is 0.
+     *                    rightTolerance - {float} Allow for a margin of error 
+     *                                             with the 'right' value of 
+     *                                             this bound.
+     *                                             Default is 0.
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} A copy of this bounds, but wrapped around the 
+     *                       "dateline" (as specified by the borders of 
+     *                       maxExtent). Note that this function only returns 
+     *                       a different bounds value if this bounds is 
+     *                       *entirely* outside of the maxExtent. If this 
+     *                       bounds straddles the dateline (is part in/part 
+     *                       out of maxExtent), the returned bounds will always 
+     *                       cross the left edge of the given maxExtent.
+     *.
+     */
+    wrapDateLine: function(maxExtent, options) {    
+        options = options || {};
+        
+        var leftTolerance = options.leftTolerance || 0;
+        var rightTolerance = options.rightTolerance || 0;
+
+        var newBounds = this.clone();
+    
+        if (maxExtent) {
+            var width = maxExtent.getWidth();
+
+            //shift right?
+            while (newBounds.left < maxExtent.left && 
+                   newBounds.right - rightTolerance <= maxExtent.left ) { 
+                newBounds = newBounds.add(width, 0);
+            }
+
+            //shift left?
+            while (newBounds.left + leftTolerance >= maxExtent.right && 
+                   newBounds.right > maxExtent.right ) { 
+                newBounds = newBounds.add(-width, 0);
+            }
+           
+            // crosses right only? force left
+            var newLeft = newBounds.left + leftTolerance;
+            if (newLeft < maxExtent.right && newLeft > maxExtent.left && 
+                   newBounds.right - rightTolerance > maxExtent.right) {
+                newBounds = newBounds.add(-width, 0);
+            }
+        }
+                
+        return newBounds;
+    },
+
+    CLASS_NAME: "OpenLayers.Bounds"
+});
+
+/** 
+ * APIFunction: fromString
+ * Alternative constructor that builds a new OpenLayers.Bounds from a 
+ *     parameter string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromString("5,42,10,45");
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters: 
+ * str - {String} Comma-separated bounds string. (e.g. "5,42,10,45")
+ * reverseAxisOrder - {Boolean} Does the string use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the 
+ *                       passed-in String.
+ */
+OpenLayers.Bounds.fromString = function(str, reverseAxisOrder) {
+    var bounds = str.split(",");
+    return OpenLayers.Bounds.fromArray(bounds, reverseAxisOrder);
+};
+
+/** 
+ * APIFunction: fromArray
+ * Alternative constructor that builds a new OpenLayers.Bounds from an array.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromArray( [5, 42, 10, 45] );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(5, 42, 10, 45);
+ * (end)
+ *
+ * Parameters:
+ * bbox - {Array(Float)} Array of bounds values (e.g. [5,42,10,45])
+ * reverseAxisOrder - {Boolean} Does the array use reverse axis order?
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in Array.
+ */
+OpenLayers.Bounds.fromArray = function(bbox, reverseAxisOrder) {
+    return reverseAxisOrder === true ?
+           new OpenLayers.Bounds(bbox[1], bbox[0], bbox[3], bbox[2]) :
+           new OpenLayers.Bounds(bbox[0], bbox[1], bbox[2], bbox[3]);
+};
+
+/** 
+ * APIFunction: fromSize
+ * Alternative constructor that builds a new OpenLayers.Bounds from a size.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.fromSize( new OpenLayers.Size(10, 20) );
+ * // => equivalent to ...
+ * new OpenLayers.Bounds(0, 20, 10, 0);
+ * (end)
+ *
+ * Parameters:
+ * size - {<OpenLayers.Size> or Object} <OpenLayers.Size> or an object with
+ *     both 'w' and 'h' properties.
+ *
+ * Returns:
+ * {<OpenLayers.Bounds>} New bounds object built from the passed-in size.
+ */
+OpenLayers.Bounds.fromSize = function(size) {
+    return new OpenLayers.Bounds(0,
+                                 size.h,
+                                 size.w,
+                                 0);
+};
+
+/**
+ * Function: oppositeQuadrant
+ * Get the opposite quadrant for a given quadrant string.
+ *
+ * (begin code)
+ * OpenLayers.Bounds.oppositeQuadrant( "tl" );
+ * // => "br"
+ *
+ * OpenLayers.Bounds.oppositeQuadrant( "tr" );
+ * // => "bl"
+ * (end)
+ *
+ * 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;
+};
+/* ======================================================================
+    OpenLayers/BaseTypes/Element.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/BaseTypes.js
+ */
+
+/**
+ * Namespace: OpenLayers.Element
+ */
+OpenLayers.Element = {
+
+    /**
+     * APIFunction: visible
+     * 
+     * Parameters: 
+     * element - {DOMElement}
+     * 
+     * Returns:
+     * {Boolean} Is the element visible?
+     */
+    visible: function(element) {
+        return OpenLayers.Util.getElement(element).style.display != 'none';
+    },
+
+    /**
+     * APIFunction: toggle
+     * Toggle the visibility of element(s) passed in
+     * 
+     * Parameters:
+     * element - {DOMElement} Actually user can pass any number of elements
+     */
+    toggle: function() {
+        for (var i=0, len=arguments.length; i<len; i++) {
+            var element = OpenLayers.Util.getElement(arguments[i]);
+            var display = OpenLayers.Element.visible(element) ? 'none' 
+                                                              : '';
+            element.style.display = display;
+        }
+    },
+
+    /**
+     * APIFunction: remove
+     * Remove the specified element from the DOM.
+     * 
+     * Parameters:
+     * element - {DOMElement}
+     */
+    remove: function(element) {
+        element = OpenLayers.Util.getElement(element);
+        element.parentNode.removeChild(element);
+    },
+
+    /**
+     * APIFunction: getHeight
+     *  
+     * Parameters:
+     * element - {DOMElement}
+     * 
+     * Returns:
+     * {Integer} The offset height of the element passed in
+     */
+    getHeight: function(element) {
+        element = OpenLayers.Util.getElement(element);
+        return element.offsetHeight;
+    },
+
+    /**
+     * Function: hasClass
+     * Tests if an element has the given CSS class name.
+     *
+     * Parameters:
+     * element - {DOMElement} A DOM element node.
+     * name - {String} The CSS class name to search for.
+     *
+     * Returns:
+     * {Boolean} The element has the given class name.
+     */
+    hasClass: function(element, name) {
+        var names = element.className;
+        return (!!names && new RegExp("(^|\\s)" + name + "(\\s|$)").test(names));
+    },
+    
+    /**
+     * Function: addClass
+     * Add a CSS class name to an element.  Safe where element already has
+     *     the class name.
+     *
+     * Parameters:
+     * element - {DOMElement} A DOM element node.
+     * name - {String} The CSS class name to add.
+     *
+     * Returns:
+     * {DOMElement} The element.
+     */
+    addClass: function(element, name) {
+        if(!OpenLayers.Element.hasClass(element, name)) {
+            element.className += (element.className ? " " : "") + name;
+        }
+        return element;
+    },
+
+    /**
+     * Function: removeClass
+     * Remove a CSS class name from an element.  Safe where element does not
+     *     have the class name.
+     *
+     * Parameters:
+     * element - {DOMElement} A DOM element node.
+     * name - {String} The CSS class name to remove.
+     *
+     * Returns:
+     * {DOMElement} The element.
+     */
+    removeClass: function(element, name) {
+        var names = element.className;
+        if(names) {
+            element.className = OpenLayers.String.trim(
+                names.replace(
+                    new RegExp("(^|\\s+)" + name + "(\\s+|$)"), " "
+                )
+            );
+        }
+        return element;
+    },
+
+    /**
+     * Function: toggleClass
+     * Remove a CSS class name from an element if it exists.  Add the class name
+     *     if it doesn't exist.
+     *
+     * Parameters:
+     * element - {DOMElement} A DOM element node.
+     * name - {String} The CSS class name to toggle.
+     *
+     * Returns:
+     * {DOMElement} The element.
+     */
+    toggleClass: function(element, name) {
+        if(OpenLayers.Element.hasClass(element, name)) {
+            OpenLayers.Element.removeClass(element, name);
+        } else {
+            OpenLayers.Element.addClass(element, name);
+        }
+        return element;
+    },
+
+    /**
+     * APIFunction: getStyle
+     * 
+     * Parameters:
+     * element - {DOMElement}
+     * style - {?}
+     * 
+     * Returns:
+     * {?}
+     */
+    getStyle: function(element, style) {
+        element = OpenLayers.Util.getElement(element);
+
+        var value = null;
+        if (element && element.style) {
+            value = element.style[OpenLayers.String.camelize(style)];
+            if (!value) {
+                if (document.defaultView && 
+                    document.defaultView.getComputedStyle) {
+                    
+                    var css = document.defaultView.getComputedStyle(element, null);
+                    value = css ? css.getPropertyValue(style) : null;
+                } else if (element.currentStyle) {
+                    value = element.currentStyle[OpenLayers.String.camelize(style)];
+                }
+            }
+        
+            var positions = ['left', 'top', 'right', 'bottom'];
+            if (window.opera &&
+                (OpenLayers.Util.indexOf(positions,style) != -1) &&
+                (OpenLayers.Element.getStyle(element, 'position') == 'static')) { 
+                value = 'auto';
+            }
+        }
+    
+        return value == 'auto' ? null : value;
+    }
+
+};
+/* ======================================================================
+    OpenLayers/BaseTypes/LonLat.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.LonLat
+ * This class represents a longitude and latitude pair
+ */
+OpenLayers.LonLat = OpenLayers.Class({
+
+    /** 
+     * APIProperty: lon
+     * {Float} The x-axis coodinate in map units
+     */
+    lon: 0.0,
+    
+    /** 
+     * APIProperty: lat
+     * {Float} The y-axis coordinate in map units
+     */
+    lat: 0.0,
+
+    /**
+     * Constructor: OpenLayers.LonLat
+     * Create a new map location. Coordinates can be passed either as two
+     * arguments, or as a single argument.
+     *
+     * Parameters (two arguments):
+     * lon - {Number} The x-axis coordinate in map units.  If your map is in
+     *     a geographic projection, this will be the Longitude.  Otherwise,
+     *     it will be the x coordinate of the map location in your map units.
+     * lat - {Number} The y-axis coordinate in map units.  If your map is in
+     *     a geographic projection, this will be the Latitude.  Otherwise,
+     *     it will be the y coordinate of the map location in your map units.
+     *
+     * Parameters (single argument):
+     * location - {Array(Float)} [lon, lat]
+     */
+    initialize: function(lon, lat) {
+        if (OpenLayers.Util.isArray(lon)) {
+            lat = lon[1];
+            lon = lon[0];
+        }
+        this.lon = OpenLayers.Util.toFloat(lon);
+        this.lat = OpenLayers.Util.toFloat(lat);
+    },
+    
+    /**
+     * Method: toString
+     * Return a readable string version of the lonlat
+     *
+     * Returns:
+     * {String} String representation of OpenLayers.LonLat object. 
+     *           (e.g. <i>"lon=5,lat=42"</i>)
+     */
+    toString:function() {
+        return ("lon=" + this.lon + ",lat=" + this.lat);
+    },
+
+    /** 
+     * APIMethod: toShortString
+     * 
+     * Returns:
+     * {String} Shortened String representation of OpenLayers.LonLat object. 
+     *         (e.g. <i>"5, 42"</i>)
+     */
+    toShortString:function() {
+        return (this.lon + ", " + this.lat);
+    },
+
+    /** 
+     * APIMethod: clone
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} New OpenLayers.LonLat object with the same lon 
+     *                       and lat values
+     */
+    clone:function() {
+        return new OpenLayers.LonLat(this.lon, this.lat);
+    },
+
+    /** 
+     * APIMethod: add
+     * 
+     * Parameters:
+     * lon - {Float}
+     * lat - {Float}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} A new OpenLayers.LonLat object with the lon and 
+     *                       lat passed-in added to this's. 
+     */
+    add:function(lon, lat) {
+        if ( (lon == null) || (lat == null) ) {
+            throw new TypeError('LonLat.add cannot receive null values');
+        }
+        return new OpenLayers.LonLat(this.lon + OpenLayers.Util.toFloat(lon), 
+                                     this.lat + OpenLayers.Util.toFloat(lat));
+    },
+
+    /** 
+     * APIMethod: equals
+     * 
+     * Parameters:
+     * ll - {<OpenLayers.LonLat>}
+     * 
+     * Returns:
+     * {Boolean} Boolean value indicating whether the passed-in 
+     *           <OpenLayers.LonLat> object has the same lon and lat 
+     *           components as this.
+     *           Note: if ll passed in is null, returns false
+     */
+    equals:function(ll) {
+        var equals = false;
+        if (ll != null) {
+            equals = ((this.lon == ll.lon && this.lat == ll.lat) ||
+                      (isNaN(this.lon) && isNaN(this.lat) && isNaN(ll.lon) && isNaN(ll.lat)));
+        }
+        return equals;
+    },
+
+    /**
+     * APIMethod: transform
+     * Transform the LonLat object from source to dest. This transformation is
+     *    *in place*: if you want a *new* lonlat, use .clone() first.
+     *
+     * Parameters: 
+     * source - {<OpenLayers.Projection>} Source projection. 
+     * dest   - {<OpenLayers.Projection>} Destination projection. 
+     *
+     * Returns:
+     * {<OpenLayers.LonLat>} Itself, for use in chaining operations.
+     */
+    transform: function(source, dest) {
+        var point = OpenLayers.Projection.transform(
+            {'x': this.lon, 'y': this.lat}, source, dest);
+        this.lon = point.x;
+        this.lat = point.y;
+        return this;
+    },
+    
+    /**
+     * APIMethod: wrapDateLine
+     * 
+     * Parameters:
+     * maxExtent - {<OpenLayers.Bounds>}
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>} A copy of this lonlat, but wrapped around the 
+     *                       "dateline" (as specified by the borders of 
+     *                       maxExtent)
+     */
+    wrapDateLine: function(maxExtent) {    
+
+        var newLonLat = this.clone();
+    
+        if (maxExtent) {
+            //shift right?
+            while (newLonLat.lon < maxExtent.left) {
+                newLonLat.lon +=  maxExtent.getWidth();
+            }    
+           
+            //shift left?
+            while (newLonLat.lon > maxExtent.right) {
+                newLonLat.lon -= maxExtent.getWidth();
+            }    
+        }
+                
+        return newLonLat;
+    },
+
+    CLASS_NAME: "OpenLayers.LonLat"
+});
+
+/** 
+ * Function: fromString
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from a 
+ *     parameter string
+ * 
+ * Parameters:
+ * str - {String} Comma-separated Lon,Lat coordinate string. 
+ *                 (e.g. <i>"5,40"</i>)
+ * 
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
+ *                       passed-in String.
+ */
+OpenLayers.LonLat.fromString = function(str) {
+    var pair = str.split(",");
+    return new OpenLayers.LonLat(pair[0], pair[1]);
+};
+
+/** 
+ * Function: fromArray
+ * Alternative constructor that builds a new <OpenLayers.LonLat> from an 
+ *     array of two numbers that represent lon- and lat-values.
+ * 
+ * Parameters:
+ * arr - {Array(Float)} Array of lon/lat values (e.g. [5,-42])
+ * 
+ * Returns:
+ * {<OpenLayers.LonLat>} New <OpenLayers.LonLat> object built from the 
+ *                       passed-in array.
+ */
+OpenLayers.LonLat.fromArray = function(arr) {
+    var gotArr = OpenLayers.Util.isArray(arr),
+        lon = gotArr && arr[0],
+        lat = gotArr && arr[1];
+    return new OpenLayers.LonLat(lon, lat);
+};
+/* ======================================================================
+    OpenLayers/BaseTypes/Pixel.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Pixel
+ * This class represents a screen coordinate, in x and y coordinates
+ */
+OpenLayers.Pixel = OpenLayers.Class({
+    
+    /**
+     * APIProperty: x
+     * {Number} The x coordinate
+     */
+    x: 0.0,
+
+    /**
+     * APIProperty: y
+     * {Number} The y coordinate
+     */
+    y: 0.0,
+    
+    /**
+     * Constructor: OpenLayers.Pixel
+     * Create a new OpenLayers.Pixel instance
+     *
+     * Parameters:
+     * x - {Number} The x coordinate
+     * y - {Number} The y coordinate
+     *
+     * Returns:
+     * An instance of OpenLayers.Pixel
+     */
+    initialize: function(x, y) {
+        this.x = parseFloat(x);
+        this.y = parseFloat(y);
+    },
+    
+    /**
+     * Method: toString
+     * Cast this object into a string
+     *
+     * Returns:
+     * {String} The string representation of Pixel. ex: "x=200.4,y=242.2"
+     */
+    toString:function() {
+        return ("x=" + this.x + ",y=" + this.y);
+    },
+
+    /**
+     * APIMethod: clone
+     * Return a clone of this pixel object
+     *
+     * Returns:
+     * {<OpenLayers.Pixel>} A clone pixel
+     */
+    clone:function() {
+        return new OpenLayers.Pixel(this.x, this.y); 
+    },
+    
+    /**
+     * APIMethod: equals
+     * Determine whether one pixel is equivalent to another
+     *
+     * Parameters:
+     * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+     *                                  a 'x' and 'y' properties.
+     *
+     * Returns:
+     * {Boolean} The point passed in as parameter is equal to this. Note that
+     * if px passed in is null, returns false.
+     */
+    equals:function(px) {
+        var equals = false;
+        if (px != null) {
+            equals = ((this.x == px.x && this.y == px.y) ||
+                      (isNaN(this.x) && isNaN(this.y) && isNaN(px.x) && isNaN(px.y)));
+        }
+        return equals;
+    },
+
+    /**
+     * APIMethod: distanceTo
+     * Returns the distance to the pixel point passed in as a parameter.
+     *
+     * Parameters:
+     * px - {<OpenLayers.Pixel>}
+     *
+     * Returns:
+     * {Float} The pixel point passed in as parameter to calculate the
+     *     distance to.
+     */
+    distanceTo:function(px) {
+        return Math.sqrt(
+            Math.pow(this.x - px.x, 2) +
+            Math.pow(this.y - px.y, 2)
+        );
+    },
+
+    /**
+     * APIMethod: add
+     *
+     * Parameters:
+     * x - {Integer}
+     * y - {Integer}
+     *
+     * Returns:
+     * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
+     * values passed in.
+     */
+    add:function(x, y) {
+        if ( (x == null) || (y == null) ) {
+            throw new TypeError('Pixel.add cannot receive null values');
+        }
+        return new OpenLayers.Pixel(this.x + x, this.y + y);
+    },
+
+    /**
+    * APIMethod: offset
+    * 
+    * Parameters
+    * px - {<OpenLayers.Pixel>|Object} An OpenLayers.Pixel or an object with
+    *                                  a 'x' and 'y' properties.
+    * 
+    * Returns:
+    * {<OpenLayers.Pixel>} A new Pixel with this pixel's x&y augmented by the 
+    *                      x&y values of the pixel passed in.
+    */
+    offset:function(px) {
+        var newPx = this.clone();
+        if (px) {
+            newPx = this.add(px.x, px.y);
+        }
+        return newPx;
+    },
+
+    CLASS_NAME: "OpenLayers.Pixel"
+});
+/* ======================================================================
+    OpenLayers/BaseTypes/Size.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Class: OpenLayers.Size
+ * Instances of this class represent a width/height pair
+ */
+OpenLayers.Size = OpenLayers.Class({
+
+    /**
+     * APIProperty: w
+     * {Number} width
+     */
+    w: 0.0,
+    
+    /**
+     * APIProperty: h
+     * {Number} height
+     */
+    h: 0.0,
+
+
+    /**
+     * Constructor: OpenLayers.Size
+     * Create an instance of OpenLayers.Size
+     *
+     * Parameters:
+     * w - {Number} width
+     * h - {Number} height
+     */
+    initialize: function(w, h) {
+        this.w = parseFloat(w);
+        this.h = parseFloat(h);
+    },
+
+    /**
+     * Method: toString
+     * Return the string representation of a size object
+     *
+     * Returns:
+     * {String} The string representation of OpenLayers.Size object. 
+     * (e.g. <i>"w=55,h=66"</i>)
+     */
+    toString:function() {
+        return ("w=" + this.w + ",h=" + this.h);
+    },
+
+    /**
+     * APIMethod: clone
+     * Create a clone of this size object
+     *
+     * Returns:
+     * {<OpenLayers.Size>} A new OpenLayers.Size object with the same w and h
+     * values
+     */
+    clone:function() {
+        return new OpenLayers.Size(this.w, this.h);
+    },
+
+    /**
+     *
+     * APIMethod: equals
+     * Determine where this size is equal to another
+     *
+     * Parameters:
+     * sz - {<OpenLayers.Size>|Object} An OpenLayers.Size or an object with
+     *                                  a 'w' and 'h' properties.
+     *
+     * Returns: 
+     * {Boolean} The passed in size has the same h and w properties as this one.
+     * Note that if sz passed in is null, returns false.
+     */
+    equals:function(sz) {
+        var equals = false;
+        if (sz != null) {
+            equals = ((this.w == sz.w && this.h == sz.h) ||
+                      (isNaN(this.w) && isNaN(this.h) && isNaN(sz.w) && isNaN(sz.h)));
+        }
+        return equals;
+    },
+
+    CLASS_NAME: "OpenLayers.Size"
+});
+/* ======================================================================
+    OpenLayers/Console.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ */
+
+/**
+ * Namespace: OpenLayers.Console
+ * The OpenLayers.Console namespace is used for debugging and error logging.
+ * If the Firebug Lite (../Firebug/firebug.js) is included before this script,
+ * calls to OpenLayers.Console methods will get redirected to window.console.
+ * This makes use of the Firebug extension where available and allows for
+ * cross-browser debugging Firebug style.
+ *
+ * Note:
+ * Note that behavior will differ with the Firebug extention and Firebug Lite.
+ * Most notably, the Firebug Lite console does not currently allow for
+ * hyperlinks to code or for clicking on object to explore their properties.
+ * 
+ */
+OpenLayers.Console = {
+    /**
+     * Create empty functions for all console methods.  The real value of these
+     * properties will be set if Firebug Lite (../Firebug/firebug.js script) is
+     * included.  We explicitly require the Firebug Lite script to trigger
+     * functionality of the OpenLayers.Console methods.
+     */
+    
+    /**
+     * APIFunction: log
+     * Log an object in the console.  The Firebug Lite console logs string
+     * representation of objects.  Given multiple arguments, they will
+     * be cast to strings and logged with a space delimiter.  If the first
+     * argument is a string with printf-like formatting, subsequent arguments
+     * will be used in string substitution.  Any additional arguments (beyond
+     * the number substituted in a format string) will be appended in a space-
+     * delimited line.
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    log: function() {},
+
+    /**
+     * APIFunction: debug
+     * Writes a message to the console, including a hyperlink to the line
+     * where it was called.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    debug: function() {},
+
+    /**
+     * APIFunction: info
+     * Writes a message to the console with the visual "info" icon and color
+     * coding and a hyperlink to the line where it was called.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    info: function() {},
+
+    /**
+     * APIFunction: warn
+     * Writes a message to the console with the visual "warning" icon and
+     * color coding and a hyperlink to the line where it was called.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    warn: function() {},
+
+    /**
+     * APIFunction: error
+     * Writes a message to the console with the visual "error" icon and color
+     * coding and a hyperlink to the line where it was called.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    error: function() {},
+    
+    /**
+     * APIFunction: userError
+     * A single interface for showing error messages to the user. The default
+     * behavior is a Javascript alert, though this can be overridden by
+     * reassigning OpenLayers.Console.userError to a different function.
+     *
+     * Expects a single error message
+     * 
+     * Parameters:
+     * error - {Object}
+     */
+    userError: function(error) {
+        alert(error);
+    },
+
+    /**
+     * APIFunction: assert
+     * Tests that an expression is true. If not, it will write a message to
+     * the console and throw an exception.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    assert: function() {},
+
+    /**
+     * APIFunction: dir
+     * Prints an interactive listing of all properties of the object. This
+     * looks identical to the view that you would see in the DOM tab.
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    dir: function() {},
+
+    /**
+     * APIFunction: dirxml
+     * Prints the XML source tree of an HTML or XML element. This looks
+     * identical to the view that you would see in the HTML tab. You can click
+     * on any node to inspect it in the HTML tab.
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    dirxml: function() {},
+
+    /**
+     * APIFunction: trace
+     * Prints an interactive stack trace of JavaScript execution at the point
+     * where it is called.  The stack trace details the functions on the stack,
+     * as well as the values that were passed as arguments to each function.
+     * You can click each function to take you to its source in the Script tab,
+     * and click each argument value to inspect it in the DOM or HTML tabs.
+     * 
+     */
+    trace: function() {},
+
+    /**
+     * APIFunction: group
+     * Writes a message to the console and opens a nested block to indent all
+     * future messages sent to the console. Call OpenLayers.Console.groupEnd()
+     * to close the block.
+     *
+     * May be called with multiple arguments as with OpenLayers.Console.log().
+     * 
+     * Parameters:
+     * object - {Object}
+     */
+    group: function() {},
+
+    /**
+     * APIFunction: groupEnd
+     * Closes the most recently opened block created by a call to
+     * OpenLayers.Console.group
+     */
+    groupEnd: function() {},
+    
+    /**
+     * APIFunction: time
+     * Creates a new timer under the given name. Call
+     * OpenLayers.Console.timeEnd(name)
+     * with the same name to stop the timer and print the time elapsed.
+     *
+     * Parameters:
+     * name - {String}
+     */
+    time: function() {},
+
+    /**
+     * APIFunction: timeEnd
+     * Stops a timer created by a call to OpenLayers.Console.time(name) and
+     * writes the time elapsed.
+     *
+     * Parameters:
+     * name - {String}
+     */
+    timeEnd: function() {},
+
+    /**
+     * APIFunction: profile
+     * Turns on the JavaScript profiler. The optional argument title would
+     * contain the text to be printed in the header of the profile report.
+     *
+     * This function is not currently implemented in Firebug Lite.
+     * 
+     * Parameters:
+     * title - {String} Optional title for the profiler
+     */
+    profile: function() {},
+
+    /**
+     * APIFunction: profileEnd
+     * Turns off the JavaScript profiler and prints its report.
+     * 
+     * This function is not currently implemented in Firebug Lite.
+     */
+    profileEnd: function() {},
+
+    /**
+     * APIFunction: count
+     * Writes the number of times that the line of code where count was called
+     * was executed. The optional argument title will print a message in
+     * addition to the number of the count.
+     *
+     * This function is not currently implemented in Firebug Lite.
+     *
+     * Parameters:
+     * title - {String} Optional title to be printed with count
+     */
+    count: function() {},
+
+    CLASS_NAME: "OpenLayers.Console"
+};
+
+/**
+ * Execute an anonymous function to extend the OpenLayers.Console namespace
+ * if the firebug.js script is included.  This closure is used so that the
+ * "scripts" and "i" variables don't pollute the global namespace.
+ */
+(function() {
+    /**
+     * If Firebug Lite is included (before this script), re-route all
+     * OpenLayers.Console calls to the console object.
+     */
+    var scripts = document.getElementsByTagName("script");
+    for(var i=0, len=scripts.length; i<len; ++i) {
+        if(scripts[i].src.indexOf("firebug.js") != -1) {
+            if(console) {
+                OpenLayers.Util.extend(OpenLayers.Console, console);
+                break;
+            }
+        }
+    }
+})();
+/* ======================================================================
+    OpenLayers/Lang.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/Console.js
+ */
+
+/**
+ * Namespace: OpenLayers.Lang
+ * Internationalization namespace.  Contains dictionaries in various languages
+ *     and methods to set and get the current language.
+ */
+OpenLayers.Lang = {
+    
+    /** 
+     * Property: code
+     * {String}  Current language code to use in OpenLayers.  Use the
+     *     <setCode> method to set this value and the <getCode> method to
+     *     retrieve it.
+     */
+    code: null,
+
+    /** 
+     * APIProperty: defaultCode
+     * {String} Default language to use when a specific language can't be
+     *     found.  Default is "en".
+     */
+    defaultCode: "en",
+        
+    /**
+     * APIFunction: getCode
+     * Get the current language code.
+     *
+     * Returns:
+     * {String} The current language code.
+     */
+    getCode: function() {
+        if(!OpenLayers.Lang.code) {
+            OpenLayers.Lang.setCode();
+        }
+        return OpenLayers.Lang.code;
+    },
+    
+    /**
+     * APIFunction: setCode
+     * Set the language code for string translation.  This code is used by
+     *     the <OpenLayers.Lang.translate> method.
+     *
+     * Parameters:
+     * code - {String} These codes follow the IETF recommendations at
+     *     http://www.ietf.org/rfc/rfc3066.txt.  If no value is set, the
+     *     browser's language setting will be tested.  If no <OpenLayers.Lang>
+     *     dictionary exists for the code, the <OpenLayers.String.defaultLang>
+     *     will be used.
+     */
+    setCode: function(code) {
+        var lang;
+        if(!code) {
+            code = (OpenLayers.BROWSER_NAME == "msie") ?
+                navigator.userLanguage : navigator.language;
+        }
+        var parts = code.split('-');
+        parts[0] = parts[0].toLowerCase();
+        if(typeof OpenLayers.Lang[parts[0]] == "object") {
+            lang = parts[0];
+        }
+
+        // check for regional extensions
+        if(parts[1]) {
+            var testLang = parts[0] + '-' + parts[1].toUpperCase();
+            if(typeof OpenLayers.Lang[testLang] == "object") {
+                lang = testLang;
+            }
+        }
+        if(!lang) {
+            OpenLayers.Console.warn(
+                'Failed to find OpenLayers.Lang.' + parts.join("-") +
+                ' dictionary, falling back to default language'
+            );
+            lang = OpenLayers.Lang.defaultCode;
+        }
+        
+        OpenLayers.Lang.code = lang;
+    },
+
+    /**
+     * APIMethod: translate
+     * Looks up a key from a dictionary based on the current language string.
+     *     The value of <getCode> will be used to determine the appropriate
+     *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
+     *
+     * Parameters:
+     * key - {String} The key for an i18n string value in the dictionary.
+     * context - {Object} Optional context to be used with
+     *     <OpenLayers.String.format>.
+     * 
+     * Returns:
+     * {String} A internationalized string.
+     */
+    translate: function(key, context) {
+        var dictionary = OpenLayers.Lang[OpenLayers.Lang.getCode()];
+        var message = dictionary && dictionary[key];
+        if(!message) {
+            // Message not found, fall back to message key
+            message = key;
+        }
+        if(context) {
+            message = OpenLayers.String.format(message, context);
+        }
+        return message;
+    }
+    
+};
+
+
+/**
+ * APIMethod: OpenLayers.i18n
+ * Alias for <OpenLayers.Lang.translate>.  Looks up a key from a dictionary
+ *     based on the current language string. The value of
+ *     <OpenLayers.Lang.getCode> will be used to determine the appropriate
+ *     dictionary.  Dictionaries are stored in <OpenLayers.Lang>.
+ *
+ * Parameters:
+ * key - {String} The key for an i18n string value in the dictionary.
+ * context - {Object} Optional context to be used with
+ *     <OpenLayers.String.format>.
+ * 
+ * Returns:
+ * {String} A internationalized string.
+ */
+OpenLayers.i18n = OpenLayers.Lang.translate;
+/* ======================================================================
+    OpenLayers/Util.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes.js
+ * @requires OpenLayers/BaseTypes/Bounds.js
+ * @requires OpenLayers/BaseTypes/Element.js
+ * @requires OpenLayers/BaseTypes/LonLat.js
+ * @requires OpenLayers/BaseTypes/Pixel.js
+ * @requires OpenLayers/BaseTypes/Size.js
+ * @requires OpenLayers/Lang.js
+ */
+
+/**
+ * Namespace: Util
+ */
+OpenLayers.Util = OpenLayers.Util || {};
+
+/** 
+ * Function: getElement
+ * This is the old $() from prototype
+ *
+ * Parameters:
+ * e - {String or DOMElement or Window}
+ *
+ * Returns:
+ * {Array(DOMElement) or DOMElement}
+ */
+OpenLayers.Util.getElement = function() {
+    var elements = [];
+
+    for (var i=0, len=arguments.length; i<len; i++) {
+        var element = arguments[i];
+        if (typeof element == 'string') {
+            element = document.getElementById(element);
+        }
+        if (arguments.length == 1) {
+            return element;
+        }
+        elements.push(element);
+    }
+    return elements;
+};
+
+/**
+ * Function: isElement
+ * A cross-browser implementation of "e instanceof Element".
+ *
+ * Parameters:
+ * o - {Object} The object to test.
+ *
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.isElement = function(o) {
+    return !!(o && o.nodeType === 1);
+};
+
+/**
+ * Function: isArray
+ * Tests that the provided object is an array.
+ * This test handles the cross-IFRAME case not caught
+ * by "a instanceof Array" and should be used instead.
+ * 
+ * Parameters:
+ * a - {Object} the object test.
+ * 
+ * Returns:
+ * {Boolean} true if the object is an array.
+ */
+OpenLayers.Util.isArray = function(a) {
+    return (Object.prototype.toString.call(a) === '[object Array]');
+};
+
+/** 
+ * Function: removeItem
+ * Remove an object from an array. Iterates through the array
+ *     to find the item, then removes it.
+ *
+ * Parameters:
+ * array - {Array}
+ * item - {Object}
+ * 
+ * Returns:
+ * {Array} A reference to the array
+ */
+OpenLayers.Util.removeItem = function(array, item) {
+    for(var i = array.length - 1; i >= 0; i--) {
+        if(array[i] == item) {
+            array.splice(i,1);
+            //break;more than once??
+        }
+    }
+    return array;
+};
+
+/** 
+ * Function: indexOf
+ * Seems to exist already in FF, but not in MOZ.
+ * 
+ * Parameters:
+ * array - {Array}
+ * obj - {*}
+ * 
+ * Returns:
+ * {Integer} The index at which the first object was found in the array.
+ *           If not found, returns -1.
+ */
+OpenLayers.Util.indexOf = function(array, obj) {
+    // use the build-in function if available.
+    if (typeof array.indexOf == "function") {
+        return array.indexOf(obj);
+    } else {
+        for (var i = 0, len = array.length; i < len; i++) {
+            if (array[i] == obj) {
+                return i;
+            }
+        }
+        return -1;   
+    }
+};
+
+
+/**
+ * Property: dotless
+ * {RegExp}
+ * Compiled regular expression to match dots (".").  This is used for replacing
+ *     dots in identifiers.  Because object identifiers are frequently used for
+ *     DOM element identifiers by the library, we avoid using dots to make for
+ *     more sensible CSS selectors.
+ *
+ * TODO: Use a module pattern to avoid bloating the API with stuff like this.
+ */
+OpenLayers.Util.dotless = /\./g;
+
+/**
+ * Function: modifyDOMElement
+ * 
+ * Modifies many properties of a DOM element all at once.  Passing in 
+ * null to an individual parameter will avoid setting the attribute.
+ *
+ * Parameters:
+ * element - {DOMElement} DOM element to modify.
+ * id - {String} The element id attribute to set.  Note that dots (".") will be
+ *     replaced with underscore ("_") in setting the element id.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ *                                  OpenLayers.Pixel or an object with
+ *                                  a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ *                                 OpenLayers.Size or an object with a
+ *                                 'w' and 'h' properties.
+ * position - {String}       The position attribute.  eg: absolute, 
+ *                           relative, etc.
+ * border - {String}         The style.border attribute.  eg:
+ *                           solid black 2px
+ * overflow - {String}       The style.overview attribute.  
+ * opacity - {Float}         Fractional value (0.0 - 1.0)
+ */
+OpenLayers.Util.modifyDOMElement = function(element, id, px, sz, position, 
+                                            border, overflow, opacity) {
+
+    if (id) {
+        element.id = id.replace(OpenLayers.Util.dotless, "_");
+    }
+    if (px) {
+        element.style.left = px.x + "px";
+        element.style.top = px.y + "px";
+    }
+    if (sz) {
+        element.style.width = sz.w + "px";
+        element.style.height = sz.h + "px";
+    }
+    if (position) {
+        element.style.position = position;
+    }
+    if (border) {
+        element.style.border = border;
+    }
+    if (overflow) {
+        element.style.overflow = overflow;
+    }
+    if (parseFloat(opacity) >= 0.0 && parseFloat(opacity) < 1.0) {
+        element.style.filter = 'alpha(opacity=' + (opacity * 100) + ')';
+        element.style.opacity = opacity;
+    } else if (parseFloat(opacity) == 1.0) {
+        element.style.filter = '';
+        element.style.opacity = '';
+    }
+};
+
+/** 
+ * Function: createDiv
+ * Creates a new div and optionally set some standard attributes.
+ * Null may be passed to each parameter if you do not wish to
+ * set a particular attribute.
+ * Note - zIndex is NOT set on the resulting div.
+ * 
+ * Parameters:
+ * id - {String} An identifier for this element.  If no id is
+ *               passed an identifier will be created 
+ *               automatically.  Note that dots (".") will be replaced with
+ *               underscore ("_") when generating ids.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ *                                  OpenLayers.Pixel or an object with
+ *                                  a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ *                                 OpenLayers.Size or an object with a
+ *                                 'w' and 'h' properties.
+ * imgURL - {String} A url pointing to an image to use as a 
+ *                   background image.
+ * position - {String} The style.position value. eg: absolute,
+ *                     relative etc.
+ * border - {String} The the style.border value. 
+ *                   eg: 2px solid black
+ * overflow - {String} The style.overflow value. Eg. hidden
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * 
+ * Returns: 
+ * {DOMElement} A DOM Div created with the specified attributes.
+ */
+OpenLayers.Util.createDiv = function(id, px, sz, imgURL, position, 
+                                     border, overflow, opacity) {
+
+    var dom = document.createElement('div');
+
+    if (imgURL) {
+        dom.style.backgroundImage = 'url(' + imgURL + ')';
+    }
+
+    //set generic properties
+    if (!id) {
+        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+    }
+    if (!position) {
+        position = "absolute";
+    }
+    OpenLayers.Util.modifyDOMElement(dom, id, px, sz, position, 
+                                     border, overflow, opacity);
+
+    return dom;
+};
+
+/**
+ * Function: createImage
+ * Creates an img element with specific attribute values.
+ *  
+ * Parameters:
+ * id - {String} The id field for the img.  If none assigned one will be
+ *               automatically generated.
+ * px - {<OpenLayers.Pixel>|Object} The element left and top position,
+ *                                  OpenLayers.Pixel or an object with
+ *                                  a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} The element width and height,
+ *                                 OpenLayers.Size or an object with a
+ *                                 'w' and 'h' properties.
+ * imgURL - {String} The url to use as the image source.
+ * position - {String} The style.position value.
+ * border - {String} The border to place around the image.
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ *                          loaded.
+ * 
+ * Returns:
+ * {DOMElement} A DOM Image created with the specified attributes.
+ */
+OpenLayers.Util.createImage = function(id, px, sz, imgURL, position, border,
+                                       opacity, delayDisplay) {
+
+    var image = document.createElement("img");
+
+    //set generic properties
+    if (!id) {
+        id = OpenLayers.Util.createUniqueID("OpenLayersDiv");
+    }
+    if (!position) {
+        position = "relative";
+    }
+    OpenLayers.Util.modifyDOMElement(image, id, px, sz, position, 
+                                     border, null, opacity);
+
+    if (delayDisplay) {
+        image.style.display = "none";
+        function display() {
+            image.style.display = "";
+            OpenLayers.Event.stopObservingElement(image);
+        }
+        OpenLayers.Event.observe(image, "load", display);
+        OpenLayers.Event.observe(image, "error", display);
+    }
+    
+    //set special properties
+    image.style.alt = id;
+    image.galleryImg = "no";
+    if (imgURL) {
+        image.src = imgURL;
+    }
+        
+    return image;
+};
+
+/**
+ * Property: IMAGE_RELOAD_ATTEMPTS
+ * {Integer} How many times should we try to reload an image before giving up?
+ *           Default is 0
+ */
+OpenLayers.IMAGE_RELOAD_ATTEMPTS = 0;
+
+/**
+ * Property: alphaHackNeeded
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHackNeeded = null;
+
+/**
+ * Function: alphaHack
+ * Checks whether it's necessary (and possible) to use the png alpha
+ * hack which allows alpha transparency for png images under Internet
+ * Explorer.
+ * 
+ * Returns:
+ * {Boolean} true if the png alpha hack is necessary and possible, false otherwise.
+ */
+OpenLayers.Util.alphaHack = function() {
+    if (OpenLayers.Util.alphaHackNeeded == null) {
+        var arVersion = navigator.appVersion.split("MSIE");
+        var version = parseFloat(arVersion[1]);
+        var filter = false;
+    
+        // IEs4Lin dies when trying to access document.body.filters, because 
+        // the property is there, but requires a DLL that can't be provided. This
+        // means that we need to wrap this in a try/catch so that this can
+        // continue.
+    
+        try { 
+            filter = !!(document.body.filters);
+        } catch (e) {}    
+    
+        OpenLayers.Util.alphaHackNeeded = (filter && 
+                                           (version >= 5.5) && (version < 7));
+    }
+    return OpenLayers.Util.alphaHackNeeded;
+};
+
+/** 
+ * Function: modifyAlphaImageDiv
+ * 
+ * Parameters:
+ * div - {DOMElement} Div containing Alpha-adjusted Image
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ *                                  a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ *                                 a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ */ 
+OpenLayers.Util.modifyAlphaImageDiv = function(div, id, px, sz, imgURL, 
+                                               position, border, sizing, 
+                                               opacity) {
+
+    OpenLayers.Util.modifyDOMElement(div, id, px, sz, position,
+                                     null, null, opacity);
+
+    var img = div.childNodes[0];
+
+    if (imgURL) {
+        img.src = imgURL;
+    }
+    OpenLayers.Util.modifyDOMElement(img, div.id + "_innerImage", null, sz, 
+                                     "relative", border);
+    
+    if (OpenLayers.Util.alphaHack()) {
+        if(div.style.display != "none") {
+            div.style.display = "inline-block";
+        }
+        if (sizing == null) {
+            sizing = "scale";
+        }
+        
+        div.style.filter = "progid:DXImageTransform.Microsoft" +
+                           ".AlphaImageLoader(src='" + img.src + "', " +
+                           "sizingMethod='" + sizing + "')";
+        if (parseFloat(div.style.opacity) >= 0.0 && 
+            parseFloat(div.style.opacity) < 1.0) {
+            div.style.filter += " alpha(opacity=" + div.style.opacity * 100 + ")";
+        }
+
+        img.style.filter = "alpha(opacity=0)";
+    }
+};
+
+/** 
+ * Function: createAlphaImageDiv
+ * 
+ * Parameters:
+ * id - {String}
+ * px - {<OpenLayers.Pixel>|Object} OpenLayers.Pixel or an object with
+ *                                  a 'x' and 'y' properties.
+ * sz - {<OpenLayers.Size>|Object} OpenLayers.Size or an object with
+ *                                 a 'w' and 'h' properties.
+ * imgURL - {String}
+ * position - {String}
+ * border - {String}
+ * sizing - {String} 'crop', 'scale', or 'image'. Default is "scale"
+ * opacity - {Float} Fractional value (0.0 - 1.0)
+ * delayDisplay - {Boolean} If true waits until the image has been
+ *                          loaded.
+ * 
+ * Returns:
+ * {DOMElement} A DOM Div created with a DOM Image inside it. If the hack is 
+ *              needed for transparency in IE, it is added.
+ */ 
+OpenLayers.Util.createAlphaImageDiv = function(id, px, sz, imgURL, 
+                                               position, border, sizing, 
+                                               opacity, delayDisplay) {
+    
+    var div = OpenLayers.Util.createDiv();
+    var img = OpenLayers.Util.createImage(null, null, null, null, null, null, 
+                                          null, delayDisplay);
+    img.className = "olAlphaImg";
+    div.appendChild(img);
+
+    OpenLayers.Util.modifyAlphaImageDiv(div, id, px, sz, imgURL, position, 
+                                        border, sizing, opacity);
+    
+    return div;
+};
+
+
+/** 
+ * Function: upperCaseObject
+ * Creates a new hashtable and copies over all the keys from the 
+ *     passed-in object, but storing them under an uppercased
+ *     version of the key at which they were stored.
+ * 
+ * Parameters: 
+ * object - {Object}
+ * 
+ * Returns: 
+ * {Object} A new Object with all the same keys but uppercased
+ */
+OpenLayers.Util.upperCaseObject = function (object) {
+    var uObject = {};
+    for (var key in object) {
+        uObject[key.toUpperCase()] = object[key];
+    }
+    return uObject;
+};
+
+/** 
+ * Function: applyDefaults
+ * Takes an object and copies any properties that don't exist from
+ *     another properties, by analogy with OpenLayers.Util.extend() from
+ *     Prototype.js.
+ * 
+ * Parameters:
+ * to - {Object} The destination object.
+ * from - {Object} The source object.  Any properties of this object that
+ *     are undefined in the to object will be set on the to object.
+ *
+ * Returns:
+ * {Object} A reference to the to object.  Note that the to argument is modified
+ *     in place and returned by this function.
+ */
+OpenLayers.Util.applyDefaults = function (to, from) {
+    to = to || {};
+    /*
+     * FF/Windows < 2.0.0.13 reports "Illegal operation on WrappedNative
+     * prototype object" when calling hawOwnProperty if the source object is an
+     * instance of window.Event.
+     */
+    var fromIsEvt = typeof window.Event == "function"
+                    && from instanceof window.Event;
+
+    for (var key in from) {
+        if (to[key] === undefined ||
+            (!fromIsEvt && from.hasOwnProperty
+             && from.hasOwnProperty(key) && !to.hasOwnProperty(key))) {
+            to[key] = from[key];
+        }
+    }
+    /**
+     * IE doesn't include the toString property when iterating over an object's
+     * properties with the for(property in object) syntax.  Explicitly check if
+     * the source has its own toString property.
+     */
+    if(!fromIsEvt && from && from.hasOwnProperty
+       && from.hasOwnProperty('toString') && !to.hasOwnProperty('toString')) {
+        to.toString = from.toString;
+    }
+    
+    return to;
+};
+
+/**
+ * Function: getParameterString
+ * 
+ * Parameters:
+ * params - {Object}
+ * 
+ * Returns:
+ * {String} A concatenation of the properties of an object in 
+ *          http parameter notation. 
+ *          (ex. <i>"key1=value1&key2=value2&key3=value3"</i>)
+ *          If a parameter is actually a list, that parameter will then
+ *          be set to a comma-seperated list of values (foo,bar) instead
+ *          of being URL escaped (foo%3Abar). 
+ */
+OpenLayers.Util.getParameterString = function(params) {
+    var paramsArray = [];
+    
+    for (var key in params) {
+      var value = params[key];
+      if ((value != null) && (typeof value != 'function')) {
+        var encodedValue;
+        if (typeof value == 'object' && value.constructor == Array) {
+          /* value is an array; encode items and separate with "," */
+          var encodedItemArray = [];
+          var item;
+          for (var itemIndex=0, len=value.length; itemIndex<len; itemIndex++) {
+            item = value[itemIndex];
+            encodedItemArray.push(encodeURIComponent(
+                (item === null || item === undefined) ? "" : item)
+            );
+          }
+          encodedValue = encodedItemArray.join(",");
+        }
+        else {
+          /* value is a string; simply encode */
+          encodedValue = encodeURIComponent(value);
+        }
+        paramsArray.push(encodeURIComponent(key) + "=" + encodedValue);
+      }
+    }
+    
+    return paramsArray.join("&");
+};
+
+/**
+ * Function: urlAppend
+ * Appends a parameter string to a url. This function includes the logic for
+ * using the appropriate character (none, & or ?) to append to the url before
+ * appending the param string.
+ * 
+ * Parameters:
+ * url - {String} The url to append to
+ * paramStr - {String} The param string to append
+ * 
+ * Returns:
+ * {String} The new url
+ */
+OpenLayers.Util.urlAppend = function(url, paramStr) {
+    var newUrl = url;
+    if(paramStr) {
+        var parts = (url + " ").split(/[?&]/);
+        newUrl += (parts.pop() === " " ?
+            paramStr :
+            parts.length ? "&" + paramStr : "?" + paramStr);
+    }
+    return newUrl;
+};
+
+/** 
+ * Function: getImagesLocation
+ * 
+ * Returns:
+ * {String} The fully formatted image location string
+ */
+OpenLayers.Util.getImagesLocation = function() {
+    return OpenLayers.ImgPath || (OpenLayers._getScriptLocation() + "img/");
+};
+
+/** 
+ * Function: getImageLocation
+ * 
+ * Returns:
+ * {String} The fully formatted location string for a specified image
+ */
+OpenLayers.Util.getImageLocation = function(image) {
+    return OpenLayers.Util.getImagesLocation() + image;
+};
+
+
+/** 
+ * Function: Try
+ * Execute functions until one of them doesn't throw an error. 
+ *     Capitalized because "try" is a reserved word in JavaScript.
+ *     Taken directly from OpenLayers.Util.Try()
+ * 
+ * Parameters:
+ * [*] - {Function} Any number of parameters may be passed to Try()
+ *    It will attempt to execute each of them until one of them 
+ *    successfully executes. 
+ *    If none executes successfully, returns null.
+ * 
+ * Returns:
+ * {*} The value returned by the first successfully executed function.
+ */
+OpenLayers.Util.Try = function() {
+    var returnValue = null;
+
+    for (var i=0, len=arguments.length; i<len; i++) {
+      var lambda = arguments[i];
+      try {
+        returnValue = lambda();
+        break;
+      } catch (e) {}
+    }
+
+    return returnValue;
+};
+
+/**
+ * Function: getXmlNodeValue
+ * 
+ * Parameters:
+ * node - {XMLNode}
+ * 
+ * Returns:
+ * {String} The text value of the given node, without breaking in firefox or IE
+ */
+OpenLayers.Util.getXmlNodeValue = function(node) {
+    var val = null;
+    OpenLayers.Util.Try( 
+        function() {
+            val = node.text;
+            if (!val) {
+                val = node.textContent;
+            }
+            if (!val) {
+                val = node.firstChild.nodeValue;
+            }
+        }, 
+        function() {
+            val = node.textContent;
+        }); 
+    return val;
+};
+
+/** 
+ * Function: mouseLeft
+ * 
+ * Parameters:
+ * evt - {Event}
+ * div - {HTMLDivElement}
+ * 
+ * Returns:
+ * {Boolean}
+ */
+OpenLayers.Util.mouseLeft = function (evt, div) {
+    // start with the element to which the mouse has moved
+    var target = (evt.relatedTarget) ? evt.relatedTarget : evt.toElement;
+    // walk up the DOM tree.
+    while (target != div && target != null) {
+        target = target.parentNode;
+    }
+    // if the target we stop at isn't the div, then we've left the div.
+    return (target != div);
+};
+
+/**
+ * Property: precision
+ * {Number} The number of significant digits to retain to avoid
+ * floating point precision errors.
+ *
+ * We use 14 as a "safe" default because, although IEEE 754 double floats
+ * (standard on most modern operating systems) support up to about 16
+ * significant digits, 14 significant digits are sufficient to represent
+ * sub-millimeter accuracy in any coordinate system that anyone is likely to
+ * use with OpenLayers.
+ *
+ * If DEFAULT_PRECISION is set to 0, the original non-truncating behavior
+ * of OpenLayers <2.8 is preserved. Be aware that this will cause problems
+ * with certain projections, e.g. spherical Mercator.
+ *
+ */
+OpenLayers.Util.DEFAULT_PRECISION = 14;
+
+/**
+ * Function: toFloat
+ * Convenience method to cast an object to a Number, rounded to the
+ * desired floating point precision.
+ *
+ * Parameters:
+ * number    - {Number} The number to cast and round.
+ * precision - {Number} An integer suitable for use with
+ *      Number.toPrecision(). Defaults to OpenLayers.Util.DEFAULT_PRECISION.
+ *      If set to 0, no rounding is performed.
+ *
+ * Returns:
+ * {Number} The cast, rounded number.
+ */
+OpenLayers.Util.toFloat = function (number, precision) {
+    if (precision == null) {
+        precision = OpenLayers.Util.DEFAULT_PRECISION;
+    }
+    if (typeof number !== "number") {
+        number = parseFloat(number);
+    }
+    return precision === 0 ? number :
+                             parseFloat(number.toPrecision(precision));
+};
+
+/**
+ * Function: rad
+ * 
+ * Parameters:
+ * x - {Float}
+ * 
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.rad = function(x) {return x*Math.PI/180;};
+
+/**
+ * Function: deg
+ *
+ * Parameters:
+ * x - {Float}
+ *
+ * Returns:
+ * {Float}
+ */
+OpenLayers.Util.deg = function(x) {return x*180/Math.PI;};
+
+/**
+ * Property: VincentyConstants
+ * {Object} Constants for Vincenty functions.
+ */
+OpenLayers.Util.VincentyConstants = {
+    a: 6378137,
+    b: 6356752.3142,
+    f: 1/298.257223563
+};
+
+/**
+ * APIFunction: distVincenty
+ * Given two objects representing points with geographic coordinates, this
+ *     calculates the distance between those points on the surface of an
+ *     ellipsoid.
+ *
+ * Parameters:
+ * p1 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ * p2 - {<OpenLayers.LonLat>} (or any object with both .lat, .lon properties)
+ *
+ * Returns:
+ * {Float} The distance (in km) between the two input points as measured on an
+ *     ellipsoid.  Note that the input point objects must be in geographic
+ *     coordinates (decimal degrees) and the return distance is in kilometers.
+ */
+OpenLayers.Util.distVincenty = function(p1, p2) {
+    var ct = OpenLayers.Util.VincentyConstants;
+    var a = ct.a, b = ct.b, f = ct.f;
+
+    var L = OpenLayers.Util.rad(p2.lon - p1.lon);
+    var U1 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p1.lat)));
+    var U2 = Math.atan((1-f) * Math.tan(OpenLayers.Util.rad(p2.lat)));
+    var sinU1 = Math.sin(U1), cosU1 = Math.cos(U1);
+    var sinU2 = Math.sin(U2), cosU2 = Math.cos(U2);
+    var lambda = L, lambdaP = 2*Math.PI;
+    var iterLimit = 20;
+    while (Math.abs(lambda-lambdaP) > 1e-12 && --iterLimit>0) {
+        var sinLambda = Math.sin(lambda), cosLambda = Math.cos(lambda);
+        var sinSigma = Math.sqrt((cosU2*sinLambda) * (cosU2*sinLambda) +
+        (cosU1*sinU2-sinU1*cosU2*cosLambda) * (cosU1*sinU2-sinU1*cosU2*cosLambda));
+        if (sinSigma==0) {
+            return 0;  // co-incident points
+        }
+        var cosSigma = sinU1*sinU2 + cosU1*cosU2*cosLambda;
+        var sigma = Math.atan2(sinSigma, cosSigma);
+        var alpha = Math.asin(cosU1 * cosU2 * sinLambda / sinSigma);
+        var cosSqAlpha = Math.cos(alpha) * Math.cos(alpha);
+        var cos2SigmaM = cosSigma - 2*sinU1*sinU2/cosSqAlpha;
+        var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+        lambdaP = lambda;
+        lambda = L + (1-C) * f * Math.sin(alpha) *
+        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+    }
+    if (iterLimit==0) {
+        return NaN;  // formula failed to converge
+    }
+    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+    var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+        B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+    var s = b*A*(sigma-deltaSigma);
+    var d = s.toFixed(3)/1000; // round to 1mm precision
+    return d;
+};
+
+/**
+ * APIFunction: destinationVincenty
+ * Calculate destination point given start point lat/long (numeric degrees),
+ * bearing (numeric degrees) & distance (in m).
+ * Adapted from Chris Veness work, see
+ * http://www.movable-type.co.uk/scripts/latlong-vincenty-direct.html
+ *
+ * Parameters:
+ * lonlat  - {<OpenLayers.LonLat>} (or any object with both .lat, .lon
+ *     properties) The start point.
+ * brng     - {Float} The bearing (degrees).
+ * dist     - {Float} The ground distance (meters).
+ *
+ * Returns:
+ * {<OpenLayers.LonLat>} The destination point.
+ */
+OpenLayers.Util.destinationVincenty = function(lonlat, brng, dist) {
+    var u = OpenLayers.Util;
+    var ct = u.VincentyConstants;
+    var a = ct.a, b = ct.b, f = ct.f;
+
+    var lon1 = lonlat.lon;
+    var lat1 = lonlat.lat;
+
+    var s = dist;
+    var alpha1 = u.rad(brng);
+    var sinAlpha1 = Math.sin(alpha1);
+    var cosAlpha1 = Math.cos(alpha1);
+
+    var tanU1 = (1-f) * Math.tan(u.rad(lat1));
+    var cosU1 = 1 / Math.sqrt((1 + tanU1*tanU1)), sinU1 = tanU1*cosU1;
+    var sigma1 = Math.atan2(tanU1, cosAlpha1);
+    var sinAlpha = cosU1 * sinAlpha1;
+    var cosSqAlpha = 1 - sinAlpha*sinAlpha;
+    var uSq = cosSqAlpha * (a*a - b*b) / (b*b);
+    var A = 1 + uSq/16384*(4096+uSq*(-768+uSq*(320-175*uSq)));
+    var B = uSq/1024 * (256+uSq*(-128+uSq*(74-47*uSq)));
+
+    var sigma = s / (b*A), sigmaP = 2*Math.PI;
+    while (Math.abs(sigma-sigmaP) > 1e-12) {
+        var cos2SigmaM = Math.cos(2*sigma1 + sigma);
+        var sinSigma = Math.sin(sigma);
+        var cosSigma = Math.cos(sigma);
+        var deltaSigma = B*sinSigma*(cos2SigmaM+B/4*(cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)-
+            B/6*cos2SigmaM*(-3+4*sinSigma*sinSigma)*(-3+4*cos2SigmaM*cos2SigmaM)));
+        sigmaP = sigma;
+        sigma = s / (b*A) + deltaSigma;
+    }
+
+    var tmp = sinU1*sinSigma - cosU1*cosSigma*cosAlpha1;
+    var lat2 = Math.atan2(sinU1*cosSigma + cosU1*sinSigma*cosAlpha1,
+        (1-f)*Math.sqrt(sinAlpha*sinAlpha + tmp*tmp));
+    var lambda = Math.atan2(sinSigma*sinAlpha1, cosU1*cosSigma - sinU1*sinSigma*cosAlpha1);
+    var C = f/16*cosSqAlpha*(4+f*(4-3*cosSqAlpha));
+    var L = lambda - (1-C) * f * sinAlpha *
+        (sigma + C*sinSigma*(cos2SigmaM+C*cosSigma*(-1+2*cos2SigmaM*cos2SigmaM)));
+
+    var revAz = Math.atan2(sinAlpha, -tmp);  // final bearing
+
+    return new OpenLayers.LonLat(lon1+u.deg(L), u.deg(lat2));
+};
+
+/**
+ * Function: getParameters
+ * Parse the parameters from a URL or from the current page itself into a 
+ *     JavaScript Object. Note that parameter values with commas are separated
+ *     out into an Array.
+ * 
+ * Parameters:
+ * url - {String} Optional url used to extract the query string.
+ *                If url is null or is not supplied, query string is taken 
+ *                from the page location.
+ * options - {Object} Additional options. Optional.
+ *
+ * Valid options:
+ *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ *       true.
+ * 
+ * Returns:
+ * {Object} An object of key/value pairs from the query string.
+ */
+OpenLayers.Util.getParameters = function(url, options) {
+    options = options || {};
+    // if no url specified, take it from the location bar
+    url = (url === null || url === undefined) ? window.location.href : url;
+
+    //parse out parameters portion of url string
+    var paramsString = "";
+    if (OpenLayers.String.contains(url, '?')) {
+        var start = url.indexOf('?') + 1;
+        var end = OpenLayers.String.contains(url, "#") ?
+                    url.indexOf('#') : url.length;
+        paramsString = url.substring(start, end);
+    }
+
+    var parameters = {};
+    var pairs = paramsString.split(/[&;]/);
+    for(var i=0, len=pairs.length; i<len; ++i) {
+        var keyValue = pairs[i].split('=');
+        if (keyValue[0]) {
+
+            var key = keyValue[0];
+            try {
+                key = decodeURIComponent(key);
+            } catch (err) {
+                key = unescape(key);
+            }
+            
+            // being liberal by replacing "+" with " "
+            var value = (keyValue[1] || '').replace(/\+/g, " ");
+
+            try {
+                value = decodeURIComponent(value);
+            } catch (err) {
+                value = unescape(value);
+            }
+            
+            // follow OGC convention of comma delimited values
+            if (options.splitArgs !== false) {
+                value = value.split(",");
+            }
+
+            //if there's only one value, do not return as array                    
+            if (value.length == 1) {
+                value = value[0];
+            }                
+            
+            parameters[key] = value;
+         }
+     }
+    return parameters;
+};
+
+/**
+ * Property: lastSeqID
+ * {Integer} The ever-incrementing count variable.
+ *           Used for generating unique ids.
+ */
+OpenLayers.Util.lastSeqID = 0;
+
+/**
+ * Function: createUniqueID
+ * Create a unique identifier for this session.  Each time this function
+ *     is called, a counter is incremented.  The return will be the optional
+ *     prefix (defaults to "id_") appended with the counter value.
+ * 
+ * Parameters:
+ * prefix - {String} Optional string to prefix unique id. Default is "id_".
+ *     Note that dots (".") in the prefix will be replaced with underscore ("_").
+ * 
+ * Returns:
+ * {String} A unique id string, built on the passed in prefix.
+ */
+OpenLayers.Util.createUniqueID = function(prefix) {
+    if (prefix == null) {
+        prefix = "id_";
+    } else {
+        prefix = prefix.replace(OpenLayers.Util.dotless, "_");
+    }
+    OpenLayers.Util.lastSeqID += 1; 
+    return prefix + OpenLayers.Util.lastSeqID;        
+};
+
+/**
+ * Constant: INCHES_PER_UNIT
+ * {Object} Constant inches per unit -- borrowed from MapServer mapscale.c
+ * derivation of nautical miles from http://en.wikipedia.org/wiki/Nautical_mile
+ * Includes the full set of units supported by CS-MAP (http://trac.osgeo.org/csmap/)
+ * and PROJ.4 (http://trac.osgeo.org/proj/)
+ * The hardcoded table is maintain in a CS-MAP source code module named CSdataU.c
+ * The hardcoded table of PROJ.4 units are in pj_units.c.
+ */
+OpenLayers.INCHES_PER_UNIT = { 
+    'inches': 1.0,
+    'ft': 12.0,
+    'mi': 63360.0,
+    'm': 39.37,
+    'km': 39370,
+    'dd': 4374754,
+    'yd': 36
+};
+OpenLayers.INCHES_PER_UNIT["in"]= OpenLayers.INCHES_PER_UNIT.inches;
+OpenLayers.INCHES_PER_UNIT["degrees"] = OpenLayers.INCHES_PER_UNIT.dd;
+OpenLayers.INCHES_PER_UNIT["nmi"] = 1852 * OpenLayers.INCHES_PER_UNIT.m;
+
+// Units from CS-Map
+OpenLayers.METERS_PER_INCH = 0.02540005080010160020;
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+    "Inch": OpenLayers.INCHES_PER_UNIT.inches,
+    "Meter": 1.0 / OpenLayers.METERS_PER_INCH,   //EPSG:9001
+    "Foot": 0.30480060960121920243 / OpenLayers.METERS_PER_INCH,   //EPSG:9003
+    "IFoot": 0.30480000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9002
+    "ClarkeFoot": 0.3047972651151 / OpenLayers.METERS_PER_INCH,   //EPSG:9005
+    "SearsFoot": 0.30479947153867624624 / OpenLayers.METERS_PER_INCH,   //EPSG:9041
+    "GoldCoastFoot": 0.30479971018150881758 / OpenLayers.METERS_PER_INCH,   //EPSG:9094
+    "IInch": 0.02540000000000000000 / OpenLayers.METERS_PER_INCH,
+    "MicroInch": 0.00002540000000000000 / OpenLayers.METERS_PER_INCH,
+    "Mil": 0.00000002540000000000 / OpenLayers.METERS_PER_INCH,
+    "Centimeter": 0.01000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "Kilometer": 1000.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9036
+    "Yard": 0.91440182880365760731 / OpenLayers.METERS_PER_INCH,
+    "SearsYard": 0.914398414616029 / OpenLayers.METERS_PER_INCH,   //EPSG:9040
+    "IndianYard": 0.91439853074444079983 / OpenLayers.METERS_PER_INCH,   //EPSG:9084
+    "IndianYd37": 0.91439523 / OpenLayers.METERS_PER_INCH,   //EPSG:9085
+    "IndianYd62": 0.9143988 / OpenLayers.METERS_PER_INCH,   //EPSG:9086
+    "IndianYd75": 0.9143985 / OpenLayers.METERS_PER_INCH,   //EPSG:9087
+    "IndianFoot": 0.30479951 / OpenLayers.METERS_PER_INCH,   //EPSG:9080
+    "IndianFt37": 0.30479841 / OpenLayers.METERS_PER_INCH,   //EPSG:9081
+    "IndianFt62": 0.3047996 / OpenLayers.METERS_PER_INCH,   //EPSG:9082
+    "IndianFt75": 0.3047995 / OpenLayers.METERS_PER_INCH,   //EPSG:9083
+    "Mile": 1609.34721869443738887477 / OpenLayers.METERS_PER_INCH,
+    "IYard": 0.91440000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9096
+    "IMile": 1609.34400000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9093
+    "NautM": 1852.00000000000000000000 / OpenLayers.METERS_PER_INCH,   //EPSG:9030
+    "Lat-66": 110943.316488932731 / OpenLayers.METERS_PER_INCH,
+    "Lat-83": 110946.25736872234125 / OpenLayers.METERS_PER_INCH,
+    "Decimeter": 0.10000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "Millimeter": 0.00100000000000000000 / OpenLayers.METERS_PER_INCH,
+    "Dekameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "Decameter": 10.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "Hectometer": 100.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "GermanMeter": 1.0000135965 / OpenLayers.METERS_PER_INCH,   //EPSG:9031
+    "CaGrid": 0.999738 / OpenLayers.METERS_PER_INCH,
+    "ClarkeChain": 20.1166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9038
+    "GunterChain": 20.11684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9033
+    "BenoitChain": 20.116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9062
+    "SearsChain": 20.11676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9042
+    "ClarkeLink": 0.201166194976 / OpenLayers.METERS_PER_INCH,   //EPSG:9039
+    "GunterLink": 0.2011684023368047 / OpenLayers.METERS_PER_INCH,   //EPSG:9034
+    "BenoitLink": 0.20116782494375872 / OpenLayers.METERS_PER_INCH,   //EPSG:9063
+    "SearsLink": 0.2011676512155 / OpenLayers.METERS_PER_INCH,   //EPSG:9043
+    "Rod": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+    "IntnlChain": 20.1168 / OpenLayers.METERS_PER_INCH,   //EPSG:9097
+    "IntnlLink": 0.201168 / OpenLayers.METERS_PER_INCH,   //EPSG:9098
+    "Perch": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+    "Pole": 5.02921005842012 / OpenLayers.METERS_PER_INCH,
+    "Furlong": 201.1684023368046 / OpenLayers.METERS_PER_INCH,
+    "Rood": 3.778266898 / OpenLayers.METERS_PER_INCH,
+    "CapeFoot": 0.3047972615 / OpenLayers.METERS_PER_INCH,
+    "Brealey": 375.00000000000000000000 / OpenLayers.METERS_PER_INCH,
+    "ModAmFt": 0.304812252984505969011938 / OpenLayers.METERS_PER_INCH,
+    "Fathom": 1.8288 / OpenLayers.METERS_PER_INCH,
+    "NautM-UK": 1853.184 / OpenLayers.METERS_PER_INCH,
+    "50kilometers": 50000.0 / OpenLayers.METERS_PER_INCH,
+    "150kilometers": 150000.0 / OpenLayers.METERS_PER_INCH
+});
+
+//unit abbreviations supported by PROJ.4
+OpenLayers.Util.extend(OpenLayers.INCHES_PER_UNIT, {
+    "mm": OpenLayers.INCHES_PER_UNIT["Meter"] / 1000.0,
+    "cm": OpenLayers.INCHES_PER_UNIT["Meter"] / 100.0,
+    "dm": OpenLayers.INCHES_PER_UNIT["Meter"] * 100.0,
+    "km": OpenLayers.INCHES_PER_UNIT["Meter"] * 1000.0,
+    "kmi": OpenLayers.INCHES_PER_UNIT["nmi"],    //International Nautical Mile
+    "fath": OpenLayers.INCHES_PER_UNIT["Fathom"], //International Fathom
+    "ch": OpenLayers.INCHES_PER_UNIT["IntnlChain"],  //International Chain
+    "link": OpenLayers.INCHES_PER_UNIT["IntnlLink"], //International Link
+    "us-in": OpenLayers.INCHES_PER_UNIT["inches"], //U.S. Surveyor's Inch
+    "us-ft": OpenLayers.INCHES_PER_UNIT["Foot"], //U.S. Surveyor's Foot
+    "us-yd": OpenLayers.INCHES_PER_UNIT["Yard"], //U.S. Surveyor's Yard
+    "us-ch": OpenLayers.INCHES_PER_UNIT["GunterChain"], //U.S. Surveyor's Chain
+    "us-mi": OpenLayers.INCHES_PER_UNIT["Mile"],   //U.S. Surveyor's Statute Mile
+    "ind-yd": OpenLayers.INCHES_PER_UNIT["IndianYd37"],  //Indian Yard
+    "ind-ft": OpenLayers.INCHES_PER_UNIT["IndianFt37"],  //Indian Foot
+    "ind-ch": 20.11669506 / OpenLayers.METERS_PER_INCH  //Indian Chain
+});
+
+/** 
+ * Constant: DOTS_PER_INCH
+ * {Integer} 72 (A sensible default)
+ */
+OpenLayers.DOTS_PER_INCH = 72;
+
+/**
+ * Function: normalizeScale
+ * 
+ * Parameters:
+ * scale - {float}
+ * 
+ * Returns:
+ * {Float} A normalized scale value, in 1 / X format. 
+ *         This means that if a value less than one ( already 1/x) is passed
+ *         in, it just returns scale directly. Otherwise, it returns 
+ *         1 / scale
+ */
+OpenLayers.Util.normalizeScale = function (scale) {
+    var normScale = (scale > 1.0) ? (1.0 / scale) 
+                                  : scale;
+    return normScale;
+};
+
+/**
+ * Function: getResolutionFromScale
+ * 
+ * Parameters:
+ * scale - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ *                  Default is degrees
+ * 
+ * Returns:
+ * {Float} The corresponding resolution given passed-in scale and unit 
+ *     parameters.  If the given scale is falsey, the returned resolution will
+ *     be undefined.
+ */
+OpenLayers.Util.getResolutionFromScale = function (scale, units) {
+    var resolution;
+    if (scale) {
+        if (units == null) {
+            units = "degrees";
+        }
+        var normScale = OpenLayers.Util.normalizeScale(scale);
+        resolution = 1 / (normScale * OpenLayers.INCHES_PER_UNIT[units]
+                                        * OpenLayers.DOTS_PER_INCH);        
+    }
+    return resolution;
+};
+
+/**
+ * Function: getScaleFromResolution
+ * 
+ * Parameters:
+ * resolution - {Float}
+ * units - {String} Index into OpenLayers.INCHES_PER_UNIT hashtable.
+ *                  Default is degrees
+ * 
+ * Returns:
+ * {Float} The corresponding scale given passed-in resolution and unit 
+ *         parameters.
+ */
+OpenLayers.Util.getScaleFromResolution = function (resolution, units) {
+
+    if (units == null) {
+        units = "degrees";
+    }
+
+    var scale = resolution * OpenLayers.INCHES_PER_UNIT[units] *
+                    OpenLayers.DOTS_PER_INCH;
+    return scale;
+};
+
+/**
+ * Function: pagePosition
+ * Calculates the position of an element on the page (see
+ * http://code.google.com/p/doctype/wiki/ArticlePageOffset)
+ *
+ * OpenLayers.Util.pagePosition is based on Yahoo's getXY method, which is
+ * Copyright (c) 2006, Yahoo! Inc.
+ * All rights reserved.
+ * 
+ * Redistribution and use of this software in source and binary forms, with or
+ * without modification, are permitted provided that the following conditions
+ * are met:
+ * 
+ * * Redistributions of source code must retain the above copyright notice,
+ *   this list of conditions and the following disclaimer.
+ * 
+ * * Redistributions in binary form must reproduce the above copyright notice,
+ *   this list of conditions and the following disclaimer in the documentation
+ *   and/or other materials provided with the distribution.
+ * 
+ * * Neither the name of Yahoo! Inc. nor the names of its contributors may be
+ *   used to endorse or promote products derived from this software without
+ *   specific prior written permission of Yahoo! Inc.
+ * 
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
+ * POSSIBILITY OF SUCH DAMAGE.
+ *
+ * Parameters:
+ * forElement - {DOMElement}
+ * 
+ * Returns:
+ * {Array} two item array, Left value then Top value.
+ */
+OpenLayers.Util.pagePosition =  function(forElement) {
+    // NOTE: If element is hidden (display none or disconnected or any the
+    // ancestors are hidden) we get (0,0) by default but we still do the
+    // accumulation of scroll position.
+
+    var pos = [0, 0];
+    var viewportElement = OpenLayers.Util.getViewportElement();
+    if (!forElement || forElement == window || forElement == viewportElement) {
+        // viewport is always at 0,0 as that defined the coordinate system for
+        // this function - this avoids special case checks in the code below
+        return pos;
+    }
+
+    // Gecko browsers normally use getBoxObjectFor to calculate the position.
+    // When invoked for an element with an implicit absolute position though it
+    // can be off by one. Therefore the recursive implementation is used in
+    // those (relatively rare) cases.
+    var BUGGY_GECKO_BOX_OBJECT =
+        OpenLayers.IS_GECKO && document.getBoxObjectFor &&
+        OpenLayers.Element.getStyle(forElement, 'position') == 'absolute' &&
+        (forElement.style.top == '' || forElement.style.left == '');
+
+    var parent = null;
+    var box;
+
+    if (forElement.getBoundingClientRect) { // IE
+        box = forElement.getBoundingClientRect();
+        var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+        var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+        
+        pos[0] = box.left + scrollLeft;
+        pos[1] = box.top + scrollTop;
+
+    } else if (document.getBoxObjectFor && !BUGGY_GECKO_BOX_OBJECT) { // gecko
+        // Gecko ignores the scroll values for ancestors, up to 1.9.  See:
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=328881 and
+        // https://bugzilla.mozilla.org/show_bug.cgi?id=330619
+
+        box = document.getBoxObjectFor(forElement);
+        var vpBox = document.getBoxObjectFor(viewportElement);
+        pos[0] = box.screenX - vpBox.screenX;
+        pos[1] = box.screenY - vpBox.screenY;
+
+    } else { // safari/opera
+        pos[0] = forElement.offsetLeft;
+        pos[1] = forElement.offsetTop;
+        parent = forElement.offsetParent;
+        if (parent != forElement) {
+            while (parent) {
+                pos[0] += parent.offsetLeft;
+                pos[1] += parent.offsetTop;
+                parent = parent.offsetParent;
+            }
+        }
+
+        var browser = OpenLayers.BROWSER_NAME;
+
+        // opera & (safari absolute) incorrectly account for body offsetTop
+        if (browser == "opera" || (browser == "safari" &&
+              OpenLayers.Element.getStyle(forElement, 'position') == 'absolute')) {
+            pos[1] -= document.body.offsetTop;
+        }
+
+        // accumulate the scroll positions for everything but the body element
+        parent = forElement.offsetParent;
+        while (parent && parent != document.body) {
+            pos[0] -= parent.scrollLeft;
+            // see https://bugs.opera.com/show_bug.cgi?id=249965
+            if (browser != "opera" || parent.tagName != 'TR') {
+                pos[1] -= parent.scrollTop;
+            }
+            parent = parent.offsetParent;
+        }
+    }
+    
+    return pos;
+};
+
+/**
+ * Function: getViewportElement
+ * Returns die viewport element of the document. The viewport element is
+ * usually document.documentElement, except in IE,where it is either
+ * document.body or document.documentElement, depending on the document's
+ * compatibility mode (see
+ * http://code.google.com/p/doctype/wiki/ArticleClientViewportElement)
+ *
+ * Returns:
+ * {DOMElement}
+ */
+OpenLayers.Util.getViewportElement = function() {
+    var viewportElement = arguments.callee.viewportElement;
+    if (viewportElement == undefined) {
+        viewportElement = (OpenLayers.BROWSER_NAME == "msie" &&
+            document.compatMode != 'CSS1Compat') ? document.body :
+            document.documentElement;
+        arguments.callee.viewportElement = viewportElement;
+    }
+    return viewportElement;
+};
+
+/** 
+ * Function: isEquivalentUrl
+ * Test two URLs for equivalence. 
+ * 
+ * Setting 'ignoreCase' allows for case-independent comparison.
+ * 
+ * Comparison is based on: 
+ *  - Protocol
+ *  - Host (evaluated without the port)
+ *  - Port (set 'ignorePort80' to ignore "80" values)
+ *  - Hash ( set 'ignoreHash' to disable)
+ *  - Pathname (for relative <-> absolute comparison) 
+ *  - Arguments (so they can be out of order)
+ *  
+ * Parameters:
+ * url1 - {String}
+ * url2 - {String}
+ * options - {Object} Allows for customization of comparison:
+ *                    'ignoreCase' - Default is True
+ *                    'ignorePort80' - Default is True
+ *                    'ignoreHash' - Default is True
+ *
+ * Returns:
+ * {Boolean} Whether or not the two URLs are equivalent
+ */
+OpenLayers.Util.isEquivalentUrl = function(url1, url2, options) {
+    options = options || {};
+
+    OpenLayers.Util.applyDefaults(options, {
+        ignoreCase: true,
+        ignorePort80: true,
+        ignoreHash: true,
+        splitArgs: false
+    });
+
+    var urlObj1 = OpenLayers.Util.createUrlObject(url1, options);
+    var urlObj2 = OpenLayers.Util.createUrlObject(url2, options);
+
+    //compare all keys except for "args" (treated below)
+    for(var key in urlObj1) {
+        if(key !== "args") {
+            if(urlObj1[key] != urlObj2[key]) {
+                return false;
+            }
+        }
+    }
+
+    // compare search args - irrespective of order
+    for(var key in urlObj1.args) {
+        if(urlObj1.args[key] != urlObj2.args[key]) {
+            return false;
+        }
+        delete urlObj2.args[key];
+    }
+    // urlObj2 shouldn't have any args left
+    for(var key in urlObj2.args) {
+        return false;
+    }
+    
+    return true;
+};
+
+/**
+ * Function: createUrlObject
+ * 
+ * Parameters:
+ * url - {String}
+ * options - {Object} A hash of options.
+ *
+ * Valid options:
+ *   ignoreCase - {Boolean} lowercase url,
+ *   ignorePort80 - {Boolean} don't include explicit port if port is 80,
+ *   ignoreHash - {Boolean} Don't include part of url after the hash (#).
+ *   splitArgs - {Boolean} Split comma delimited params into arrays? Default is
+ *       true.
+ * 
+ * Returns:
+ * {Object} An object with separate url, a, port, host, and args parsed out 
+ *          and ready for comparison
+ */
+OpenLayers.Util.createUrlObject = function(url, options) {
+    options = options || {};
+
+    // deal with relative urls first
+    if(!(/^\w+:\/\//).test(url)) {
+        var loc = window.location;
+        var port = loc.port ? ":" + loc.port : "";
+        var fullUrl = loc.protocol + "//" + loc.host.split(":").shift() + port;
+        if(url.indexOf("/") === 0) {
+            // full pathname
+            url = fullUrl + url;
+        } else {
+            // relative to current path
+            var parts = loc.pathname.split("/");
+            parts.pop();
+            url = fullUrl + parts.join("/") + "/" + url;
+        }
+    }
+  
+    if (options.ignoreCase) {
+        url = url.toLowerCase(); 
+    }
+
+    var a = document.createElement('a');
+    a.href = url;
+    
+    var urlObject = {};
+    
+    //host (without port)
+    urlObject.host = a.host.split(":").shift();
+
+    //protocol
+    urlObject.protocol = a.protocol;  
+
+    //port (get uniform browser behavior with port 80 here)
+    if(options.ignorePort80) {
+        urlObject.port = (a.port == "80" || a.port == "0") ? "" : a.port;
+    } else {
+        urlObject.port = (a.port == "" || a.port == "0") ? "80" : a.port;
+    }
+
+    //hash
+    urlObject.hash = (options.ignoreHash || a.hash === "#") ? "" : a.hash;  
+    
+    //args
+    var queryString = a.search;
+    if (!queryString) {
+        var qMark = url.indexOf("?");
+        queryString = (qMark != -1) ? url.substr(qMark) : "";
+    }
+    urlObject.args = OpenLayers.Util.getParameters(queryString,
+            {splitArgs: options.splitArgs});
+
+    // pathname
+    //
+    // This is a workaround for Internet Explorer where
+    // window.location.pathname has a leading "/", but
+    // a.pathname has no leading "/".
+    urlObject.pathname = (a.pathname.charAt(0) == "/") ? a.pathname : "/" + a.pathname;
+    
+    return urlObject; 
+};
+/**
+ * Function: removeTail
+ * Takes a url and removes everything after the ? and #
+ * 
+ * Parameters:
+ * url - {String} The url to process
+ * 
+ * Returns:
+ * {String} The string with all queryString and Hash removed
+ */
+OpenLayers.Util.removeTail = function(url) {
+    var head = null;
+    
+    var qMark = url.indexOf("?");
+    var hashMark = url.indexOf("#");
+
+    if (qMark == -1) {
+        head = (hashMark != -1) ? url.substr(0,hashMark) : url;
+    } else {
+        head = (hashMark != -1) ? url.substr(0,Math.min(qMark, hashMark)) 
+                                  : url.substr(0, qMark);
+    }
+    return head;
+};
+
+/**
+ * Constant: IS_GECKO
+ * {Boolean} True if the userAgent reports the browser to use the Gecko engine
+ */
+OpenLayers.IS_GECKO = (function() {
+    var ua = navigator.userAgent.toLowerCase();
+    return ua.indexOf("webkit") == -1 && ua.indexOf("gecko") != -1;
+})();
+
+/**
+ * Constant: CANVAS_SUPPORTED
+ * {Boolean} True if canvas 2d is supported.
+ */
+OpenLayers.CANVAS_SUPPORTED = (function() {
+    var elem = document.createElement('canvas');
+    return !!(elem.getContext && elem.getContext('2d'));
+})();
+
+/**
+ * Constant: BROWSER_NAME
+ * {String}
+ * A substring of the navigator.userAgent property.  Depending on the userAgent
+ *     property, this will be the empty string or one of the following:
+ *     * "opera" -- Opera
+ *     * "msie"  -- Internet Explorer
+ *     * "safari" -- Safari
+ *     * "firefox" -- Firefox
+ *     * "mozilla" -- Mozilla
+ */
+OpenLayers.BROWSER_NAME = (function() {
+    var name = "";
+    var ua = navigator.userAgent.toLowerCase();
+    if (ua.indexOf("opera") != -1) {
+        name = "opera";
+    } else if (ua.indexOf("msie") != -1) {
+        name = "msie";
+    } else if (ua.indexOf("safari") != -1) {
+        name = "safari";
+    } else if (ua.indexOf("mozilla") != -1) {
+        if (ua.indexOf("firefox") != -1) {
+            name = "firefox";
+        } else {
+            name = "mozilla";
+        }
+    }
+    return name;
+})();
+
+/**
+ * Function: getBrowserName
+ * 
+ * Returns:
+ * {String} A string which specifies which is the current 
+ *          browser in which we are running. 
+ * 
+ *          Currently-supported browser detection and codes:
+ *           * 'opera' -- Opera
+ *           * 'msie'  -- Internet Explorer
+ *           * 'safari' -- Safari
+ *           * 'firefox' -- Firefox
+ *           * 'mozilla' -- Mozilla
+ * 
+ *          If we are unable to property identify the browser, we 
+ *           return an empty string.
+ */
+OpenLayers.Util.getBrowserName = function() {
+    return OpenLayers.BROWSER_NAME;
+};
+
+/**
+ * Method: getRenderedDimensions
+ * Renders the contentHTML offscreen to determine actual dimensions for
+ *     popup sizing. As we need layout to determine dimensions the content
+ *     is rendered -9999px to the left and absolute to ensure the 
+ *     scrollbars do not flicker
+ *     
+ * Parameters:
+ * contentHTML
+ * size - {<OpenLayers.Size>} If either the 'w' or 'h' properties is 
+ *     specified, we fix that dimension of the div to be measured. This is 
+ *     useful in the case where we have a limit in one dimension and must 
+ *     therefore meaure the flow in the other dimension.
+ * options - {Object}
+ *
+ * Allowed Options:
+ *     displayClass - {String} Optional parameter.  A CSS class name(s) string
+ *         to provide the CSS context of the rendered content.
+ *     containerElement - {DOMElement} Optional parameter. Insert the HTML to 
+ *         this node instead of the body root when calculating dimensions. 
+ * 
+ * Returns:
+ * {<OpenLayers.Size>}
+ */
+OpenLayers.Util.getRenderedDimensions = function(contentHTML, size, options) {
+    
+    var w, h;
+    
+    // create temp container div with restricted size
+    var container = document.createElement("div");
+    container.style.visibility = "hidden";
+        
+    var containerElement = (options && options.containerElement) 
+        ? options.containerElement : document.body;
+    
+    // Opera and IE7 can't handle a node with position:aboslute if it inherits
+    // position:absolute from a parent.
+    var parentHasPositionAbsolute = false;
+    var superContainer = null;
+    var parent = containerElement;
+    while (parent && parent.tagName.toLowerCase()!="body") {
+        var parentPosition = OpenLayers.Element.getStyle(parent, "position");
+        if(parentPosition == "absolute") {
+            parentHasPositionAbsolute = true;
+            break;
+        } else if (parentPosition && parentPosition != "static") {
+            break;
+        }
+        parent = parent.parentNode;
+    }
+    if(parentHasPositionAbsolute && (containerElement.clientHeight === 0 || 
+                                     containerElement.clientWidth === 0) ){
+        superContainer = document.createElement("div");
+        superContainer.style.visibility = "hidden";
+        superContainer.style.position = "absolute";
+        superContainer.style.overflow = "visible";
+        superContainer.style.width = document.body.clientWidth + "px";
+        superContainer.style.height = document.body.clientHeight + "px";
+        superContainer.appendChild(container);
+    }
+    container.style.position = "absolute";
+
+    //fix a dimension, if specified.
+    if (size) {
+        if (size.w) {
+            w = size.w;
+            container.style.width = w + "px";
+        } else if (size.h) {
+            h = size.h;
+            container.style.height = h + "px";
+        }
+    }
+
+    //add css classes, if specified
+    if (options && options.displayClass) {
+        container.className = options.displayClass;
+    }
+    
+    // create temp content div and assign content
+    var content = document.createElement("div");
+    content.innerHTML = contentHTML;
+    
+    // we need overflow visible when calculating the size
+    content.style.overflow = "visible";
+    if (content.childNodes) {
+        for (var i=0, l=content.childNodes.length; i<l; i++) {
+            if (!content.childNodes[i].style) continue;
+            content.childNodes[i].style.overflow = "visible";
+        }
+    }
+    
+    // add content to restricted container 
+    container.appendChild(content);
+    
+    // append container to body for rendering
+    if (superContainer) {
+        containerElement.appendChild(superContainer);
+    } else {
+        containerElement.appendChild(container);
+    }
+    
+    // calculate scroll width of content and add corners and shadow width
+    if (!w) {
+        w = parseInt(content.scrollWidth);
+    
+        // update container width to allow height to adjust
+        container.style.width = w + "px";
+    }        
+    // capture height and add shadow and corner image widths
+    if (!h) {
+        h = parseInt(content.scrollHeight);
+    }
+
+    // remove elements
+    container.removeChild(content);
+    if (superContainer) {
+        superContainer.removeChild(container);
+        containerElement.removeChild(superContainer);
+    } else {
+        containerElement.removeChild(container);
+    }
+    
+    return new OpenLayers.Size(w, h);
+};
+
+/**
+ * APIFunction: getScrollbarWidth
+ * This function has been modified by the OpenLayers from the original version,
+ *     written by Matthew Eernisse and released under the Apache 2 
+ *     license here:
+ * 
+ *     http://www.fleegix.org/articles/2006/05/30/getting-the-scrollbar-width-in-pixels
+ * 
+ *     It has been modified simply to cache its value, since it is physically 
+ *     impossible that this code could ever run in more than one browser at 
+ *     once. 
+ * 
+ * Returns:
+ * {Integer}
+ */
+OpenLayers.Util.getScrollbarWidth = function() {
+    
+    var scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+    
+    if (scrollbarWidth == null) {
+        var scr = null;
+        var inn = null;
+        var wNoScroll = 0;
+        var wScroll = 0;
+    
+        // Outer scrolling div
+        scr = document.createElement('div');
+        scr.style.position = 'absolute';
+        scr.style.top = '-1000px';
+        scr.style.left = '-1000px';
+        scr.style.width = '100px';
+        scr.style.height = '50px';
+        // Start with no scrollbar
+        scr.style.overflow = 'hidden';
+    
+        // Inner content div
+        inn = document.createElement('div');
+        inn.style.width = '100%';
+        inn.style.height = '200px';
+    
+        // Put the inner div in the scrolling div
+        scr.appendChild(inn);
+        // Append the scrolling div to the doc
+        document.body.appendChild(scr);
+    
+        // Width of the inner div sans scrollbar
+        wNoScroll = inn.offsetWidth;
+    
+        // Add the scrollbar
+        scr.style.overflow = 'scroll';
+        // Width of the inner div width scrollbar
+        wScroll = inn.offsetWidth;
+    
+        // Remove the scrolling div from the doc
+        document.body.removeChild(document.body.lastChild);
+    
+        // Pixel width of the scroller
+        OpenLayers.Util._scrollbarWidth = (wNoScroll - wScroll);
+        scrollbarWidth = OpenLayers.Util._scrollbarWidth;
+    }
+
+    return scrollbarWidth;
+};
+
+/**
+ * APIFunction: getFormattedLonLat
+ * This function will return latitude or longitude value formatted as 
+ *
+ * Parameters:
+ * coordinate - {Float} the coordinate value to be formatted
+ * axis - {String} value of either 'lat' or 'lon' to indicate which axis is to
+ *          to be formatted (default = lat)
+ * dmsOption - {String} specify the precision of the output can be one of:
+ *           'dms' show degrees minutes and seconds
+ *           'dm' show only degrees and minutes
+ *           'd' show only degrees
+ * 
+ * Returns:
+ * {String} the coordinate value formatted as a string
+ */
+OpenLayers.Util.getFormattedLonLat = function(coordinate, axis, dmsOption) {
+    if (!dmsOption) {
+        dmsOption = 'dms';    //default to show degree, minutes, seconds
+    }
+
+    coordinate = (coordinate+540)%360 - 180; // normalize for sphere being round
+
+    var abscoordinate = Math.abs(coordinate);
+    var coordinatedegrees = Math.floor(abscoordinate);
+
+    var coordinateminutes = (abscoordinate - coordinatedegrees)/(1/60);
+    var tempcoordinateminutes = coordinateminutes;
+    coordinateminutes = Math.floor(coordinateminutes);
+    var coordinateseconds = (tempcoordinateminutes - coordinateminutes)/(1/60);
+    coordinateseconds =  Math.round(coordinateseconds*10);
+    coordinateseconds /= 10;
+
+    if( coordinateseconds >= 60) { 
+        coordinateseconds -= 60; 
+        coordinateminutes += 1; 
+        if( coordinateminutes >= 60) { 
+            coordinateminutes -= 60; 
+            coordinatedegrees += 1; 
+        } 
+    }
+    
+    if( coordinatedegrees < 10 ) {
+        coordinatedegrees = "0" + coordinatedegrees;
+    }
+    var str = coordinatedegrees + "\u00B0";
+
+    if (dmsOption.indexOf('dm') >= 0) {
+        if( coordinateminutes < 10 ) {
+            coordinateminutes = "0" + coordinateminutes;
+        }
+        str += coordinateminutes + "'";
+  
+        if (dmsOption.indexOf('dms') >= 0) {
+            if( coordinateseconds < 10 ) {
+                coordinateseconds = "0" + coordinateseconds;
+            }
+            str += coordinateseconds + '"';
+        }
+    }
+    
+    if (axis == "lon") {
+        str += coordinate < 0 ? OpenLayers.i18n("W") : OpenLayers.i18n("E");
+    } else {
+        str += coordinate < 0 ? OpenLayers.i18n("S") : OpenLayers.i18n("N");
+    }
+    return str;
+};
+
+/* ======================================================================
+    OpenLayers/Events.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+
+/**
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Event
+ * Utility functions for event handling.
+ */
+OpenLayers.Event = {
+
+    /** 
+     * Property: observers 
+     * {Object} A hashtable cache of the event observers. Keyed by
+     * element._eventCacheID 
+     */
+    observers: false,
+
+    /**
+     * Constant: KEY_SPACE
+     * {int}
+     */
+    KEY_SPACE: 32,
+    
+    /** 
+     * Constant: KEY_BACKSPACE 
+     * {int} 
+     */
+    KEY_BACKSPACE: 8,
+
+    /** 
+     * Constant: KEY_TAB 
+     * {int} 
+     */
+    KEY_TAB: 9,
+
+    /** 
+     * Constant: KEY_RETURN 
+     * {int} 
+     */
+    KEY_RETURN: 13,
+
+    /** 
+     * Constant: KEY_ESC 
+     * {int} 
+     */
+    KEY_ESC: 27,
+
+    /** 
+     * Constant: KEY_LEFT 
+     * {int} 
+     */
+    KEY_LEFT: 37,
+
+    /** 
+     * Constant: KEY_UP 
+     * {int} 
+     */
+    KEY_UP: 38,
+
+    /** 
+     * Constant: KEY_RIGHT 
+     * {int} 
+     */
+    KEY_RIGHT: 39,
+
+    /** 
+     * Constant: KEY_DOWN 
+     * {int} 
+     */
+    KEY_DOWN: 40,
+
+    /** 
+     * Constant: KEY_DELETE 
+     * {int} 
+     */
+    KEY_DELETE: 46,
+
+
+    /**
+     * Method: element
+     * Cross browser event element detection.
+     * 
+     * Parameters:
+     * event - {Event} 
+     * 
+     * Returns:
+     * {DOMElement} The element that caused the event 
+     */
+    element: function(event) {
+        return event.target || event.srcElement;
+    },
+
+    /**
+     * Method: isSingleTouch
+     * Determine whether event was caused by a single touch
+     *
+     * Parameters:
+     * event - {Event}
+     *
+     * Returns:
+     * {Boolean}
+     */
+    isSingleTouch: function(event) {
+        return event.touches && event.touches.length == 1;
+    },
+
+    /**
+     * Method: isMultiTouch
+     * Determine whether event was caused by a multi touch
+     *
+     * Parameters:
+     * event - {Event}
+     *
+     * Returns:
+     * {Boolean}
+     */
+    isMultiTouch: function(event) {
+        return event.touches && event.touches.length > 1;
+    },
+
+    /**
+     * Method: isLeftClick
+     * Determine whether event was caused by a left click. 
+     *
+     * Parameters:
+     * event - {Event} 
+     * 
+     * Returns:
+     * {Boolean}
+     */
+    isLeftClick: function(event) {
+        return (((event.which) && (event.which == 1)) ||
+                ((event.button) && (event.button == 1)));
+    },
+
+    /**
+     * Method: isRightClick
+     * Determine whether event was caused by a right mouse click. 
+     *
+     * Parameters:
+     * event - {Event} 
+     * 
+     * Returns:
+     * {Boolean}
+     */
+     isRightClick: function(event) {
+        return (((event.which) && (event.which == 3)) ||
+                ((event.button) && (event.button == 2)));
+    },
+     
+    /**
+     * Method: stop
+     * Stops an event from propagating. 
+     *
+     * Parameters: 
+     * event - {Event} 
+     * allowDefault - {Boolean} If true, we stop the event chain but 
+     *     still allow the default browser behaviour (text selection,
+     *     radio-button clicking, etc).  Default is false.
+     */
+    stop: function(event, allowDefault) {
+        
+        if (!allowDefault) { 
+            OpenLayers.Event.preventDefault(event);
+        }
+                
+        if (event.stopPropagation) {
+            event.stopPropagation();
+        } else {
+            event.cancelBubble = true;
+        }
+    },
+
+    /**
+     * Method: preventDefault
+     * Cancels the event if it is cancelable, without stopping further
+     * propagation of the event.
+     *
+     * Parameters:
+     * event - {Event}
+     */
+    preventDefault: function(event) {
+        if (event.preventDefault) {
+            event.preventDefault();
+        } else {
+            event.returnValue = false;
+        }
+    },
+
+    /** 
+     * Method: findElement
+     * 
+     * Parameters:
+     * event - {Event} 
+     * tagName - {String} 
+     * 
+     * Returns:
+     * {DOMElement} The first node with the given tagName, starting from the
+     * node the event was triggered on and traversing the DOM upwards
+     */
+    findElement: function(event, tagName) {
+        var element = OpenLayers.Event.element(event);
+        while (element.parentNode && (!element.tagName ||
+              (element.tagName.toUpperCase() != tagName.toUpperCase()))){
+            element = element.parentNode;
+        }
+        return element;
+    },
+
+    /** 
+     * Method: observe
+     * 
+     * Parameters:
+     * elementParam - {DOMElement || String} 
+     * name - {String} 
+     * observer - {function} 
+     * useCapture - {Boolean} 
+     */
+    observe: function(elementParam, name, observer, useCapture) {
+        var element = OpenLayers.Util.getElement(elementParam);
+        useCapture = useCapture || false;
+
+        if (name == 'keypress' &&
+           (navigator.appVersion.match(/Konqueror|Safari|KHTML/)
+           || element.attachEvent)) {
+            name = 'keydown';
+        }
+
+        //if observers cache has not yet been created, create it
+        if (!this.observers) {
+            this.observers = {};
+        }
+
+        //if not already assigned, make a new unique cache ID
+        if (!element._eventCacheID) {
+            var idPrefix = "eventCacheID_";
+            if (element.id) {
+                idPrefix = element.id + "_" + idPrefix;
+            }
+            element._eventCacheID = OpenLayers.Util.createUniqueID(idPrefix);
+        }
+
+        var cacheID = element._eventCacheID;
+
+        //if there is not yet a hash entry for this element, add one
+        if (!this.observers[cacheID]) {
+            this.observers[cacheID] = [];
+        }
+
+        //add a new observer to this element's list
+        this.observers[cacheID].push({
+            'element': element,
+            'name': name,
+            'observer': observer,
+            'useCapture': useCapture
+        });
+
+        //add the actual browser event listener
+        if (element.addEventListener) {
+            element.addEventListener(name, observer, useCapture);
+        } else if (element.attachEvent) {
+            element.attachEvent('on' + name, observer);
+        }
+    },
+
+    /** 
+     * Method: stopObservingElement
+     * Given the id of an element to stop observing, cycle through the 
+     *   element's cached observers, calling stopObserving on each one, 
+     *   skipping those entries which can no longer be removed.
+     * 
+     * parameters:
+     * elementParam - {DOMElement || String} 
+     */
+    stopObservingElement: function(elementParam) {
+        var element = OpenLayers.Util.getElement(elementParam);
+        var cacheID = element._eventCacheID;
+
+        this._removeElementObservers(OpenLayers.Event.observers[cacheID]);
+    },
+
+    /**
+     * Method: _removeElementObservers
+     *
+     * Parameters:
+     * elementObservers - {Array(Object)} Array of (element, name, 
+     *                                         observer, usecapture) objects, 
+     *                                         taken directly from hashtable
+     */
+    _removeElementObservers: function(elementObservers) {
+        if (elementObservers) {
+            for(var i = elementObservers.length-1; i >= 0; i--) {
+                var entry = elementObservers[i];
+                OpenLayers.Event.stopObserving.apply(this, [
+                    entry.element, entry.name, entry.observer, entry.useCapture
+                ]);
+            }
+        }
+    },
+
+    /**
+     * Method: stopObserving
+     * 
+     * Parameters:
+     * elementParam - {DOMElement || String} 
+     * name - {String} 
+     * observer - {function} 
+     * useCapture - {Boolean} 
+     *  
+     * Returns:
+     * {Boolean} Whether or not the event observer was removed
+     */
+    stopObserving: function(elementParam, name, observer, useCapture) {
+        useCapture = useCapture || false;
+    
+        var element = OpenLayers.Util.getElement(elementParam);
+        var cacheID = element._eventCacheID;
+
+        if (name == 'keypress') {
+            if ( navigator.appVersion.match(/Konqueror|Safari|KHTML/) || 
+                 element.detachEvent) {
+              name = 'keydown';
+            }
+        }
+
+        // find element's entry in this.observers cache and remove it
+        var foundEntry = false;
+        var elementObservers = OpenLayers.Event.observers[cacheID];
+        if (elementObservers) {
+    
+            // find the specific event type in the element's list
+            var i=0;
+            while(!foundEntry && i < elementObservers.length) {
+                var cacheEntry = elementObservers[i];
+    
+                if ((cacheEntry.name == name) &&
+                    (cacheEntry.observer == observer) &&
+                    (cacheEntry.useCapture == useCapture)) {
+    
+                    elementObservers.splice(i, 1);
+                    if (elementObservers.length == 0) {
+                        delete OpenLayers.Event.observers[cacheID];
+                    }
+                    foundEntry = true;
+                    break; 
+                }
+                i++;           
+            }
+        }
+    
+        //actually remove the event listener from browser
+        if (foundEntry) {
+            if (element.removeEventListener) {
+                element.removeEventListener(name, observer, useCapture);
+            } else if (element && element.detachEvent) {
+                element.detachEvent('on' + name, observer);
+            }
+        }
+        return foundEntry;
+    },
+    
+    /** 
+     * Method: unloadCache
+     * Cycle through all the element entries in the events cache and call
+     *   stopObservingElement on each. 
+     */
+    unloadCache: function() {
+        // check for OpenLayers.Event before checking for observers, because
+        // OpenLayers.Event may be undefined in IE if no map instance was
+        // created
+        if (OpenLayers.Event && OpenLayers.Event.observers) {
+            for (var cacheID in OpenLayers.Event.observers) {
+                var elementObservers = OpenLayers.Event.observers[cacheID];
+                OpenLayers.Event._removeElementObservers.apply(this, 
+                                                           [elementObservers]);
+            }
+            OpenLayers.Event.observers = false;
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Event"
+};
+
+/* prevent memory leaks in IE */
+OpenLayers.Event.observe(window, 'unload', OpenLayers.Event.unloadCache, false);
+
+/**
+ * Class: OpenLayers.Events
+ */
+OpenLayers.Events = OpenLayers.Class({
+
+    /** 
+     * Constant: BROWSER_EVENTS
+     * {Array(String)} supported events 
+     */
+    BROWSER_EVENTS: [
+        "mouseover", "mouseout",
+        "mousedown", "mouseup", "mousemove", 
+        "click", "dblclick", "rightclick", "dblrightclick",
+        "resize", "focus", "blur",
+        "touchstart", "touchmove", "touchend",
+        "keydown"
+    ],
+
+    /** 
+     * Property: listeners 
+     * {Object} Hashtable of Array(Function): events listener functions  
+     */
+    listeners: null,
+
+    /** 
+     * Property: object 
+     * {Object}  the code object issuing application events 
+     */
+    object: null,
+
+    /** 
+     * Property: element 
+     * {DOMElement}  the DOM element receiving browser events 
+     */
+    element: null,
+
+    /** 
+     * Property: eventHandler 
+     * {Function}  bound event handler attached to elements 
+     */
+    eventHandler: null,
+
+    /** 
+     * APIProperty: fallThrough 
+     * {Boolean} 
+     */
+    fallThrough: null,
+
+    /** 
+     * APIProperty: includeXY
+     * {Boolean} Should the .xy property automatically be created for browser
+     *    mouse events? In general, this should be false. If it is true, then
+     *    mouse events will automatically generate a '.xy' property on the 
+     *    event object that is passed. (Prior to OpenLayers 2.7, this was true
+     *    by default.) Otherwise, you can call the getMousePosition on the
+     *    relevant events handler on the object available via the 'evt.object'
+     *    property of the evt object. So, for most events, you can call:
+     *    function named(evt) { 
+     *        this.xy = this.object.events.getMousePosition(evt) 
+     *    } 
+     *
+     *    This option typically defaults to false for performance reasons:
+     *    when creating an events object whose primary purpose is to manage
+     *    relatively positioned mouse events within a div, it may make
+     *    sense to set it to true.
+     *
+     *    This option is also used to control whether the events object caches
+     *    offsets. If this is false, it will not: the reason for this is that
+     *    it is only expected to be called many times if the includeXY property
+     *    is set to true. If you set this to true, you are expected to clear 
+     *    the offset cache manually (using this.clearMouseCache()) if:
+     *        the border of the element changes
+     *        the location of the element in the page changes
+    */
+    includeXY: false,      
+    
+    /**
+     * APIProperty: extensions
+     * {Object} Event extensions registered with this instance. Keys are
+     *     event types, values are {OpenLayers.Events.*} extension instances or
+     *     {Boolean} for events that an instantiated extension provides in
+     *     addition to the one it was created for.
+     *
+     * Extensions create an event in addition to browser events, which usually
+     * fires when a sequence of browser events is completed. Extensions are
+     * automatically instantiated when a listener is registered for an event
+     * provided by an extension.
+     *
+     * Extensions are created in the <OpenLayers.Events> namespace using
+     * <OpenLayers.Class>, and named after the event they provide.
+     * The constructor receives the target <OpenLayers.Events> instance as
+     * argument. Extensions that need to capture browser events before they
+     * propagate can register their listeners events using <register>, with
+     * {extension: true} as 4th argument.
+     *
+     * If an extension creates more than one event, an alias for each event
+     * type should be created and reference the same class. The constructor
+     * should set a reference in the target's extensions registry to itself.
+     *
+     * Below is a minimal extension that provides the "foostart" and "fooend"
+     * event types, which replace the native "click" event type if clicked on
+     * an element with the css class "foo":
+     *
+     * (code)
+     *   OpenLayers.Events.foostart = OpenLayers.Class({
+     *       initialize: function(target) {
+     *           this.target = target;
+     *           this.target.register("click", this, this.doStuff, {extension: true});
+     *           // only required if extension provides more than one event type
+     *           this.target.extensions["foostart"] = true;
+     *           this.target.extensions["fooend"] = true;
+     *       },
+     *       destroy: function() {
+     *           var target = this.target;
+     *           target.unregister("click", this, this.doStuff);
+     *           delete this.target;
+     *           // only required if extension provides more than one event type
+     *           delete target.extensions["foostart"];
+     *           delete target.extensions["fooend"];
+     *       },
+     *       doStuff: function(evt) {
+     *           var propagate = true;
+     *           if (OpenLayers.Event.element(evt).className === "foo") {
+     *               propagate = false;
+     *               var target = this.target;
+     *               target.triggerEvent("foostart");
+     *               window.setTimeout(function() {
+     *                   target.triggerEvent("fooend");
+     *               }, 1000);
+     *           }
+     *           return propagate;
+     *       }
+     *   });
+     *   // only required if extension provides more than one event type
+     *   OpenLayers.Events.fooend = OpenLayers.Events.foostart;
+     * (end)
+     * 
+     */
+    extensions: null,
+    
+    /**
+     * Property: extensionCount
+     * {Object} Keys are event types (like in <listeners>), values are the
+     *     number of extension listeners for each event type.
+     */
+    extensionCount: null,
+
+    /**
+     * Method: clearMouseListener
+     * A version of <clearMouseCache> that is bound to this instance so that
+     *     it can be used with <OpenLayers.Event.observe> and
+     *     <OpenLayers.Event.stopObserving>.
+     */
+    clearMouseListener: null,
+
+    /**
+     * Constructor: OpenLayers.Events
+     * Construct an OpenLayers.Events object.
+     *
+     * Parameters:
+     * object - {Object} The js object to which this Events object  is being added
+     * element - {DOMElement} A dom element to respond to browser events
+     * eventTypes - {Array(String)} Deprecated.  Array of custom application
+     *     events.  A listener may be registered for any named event, regardless
+     *     of the values provided here.
+     * fallThrough - {Boolean} Allow events to fall through after these have
+     *                         been handled?
+     * options - {Object} Options for the events object.
+     */
+    initialize: function (object, element, eventTypes, fallThrough, options) {
+        OpenLayers.Util.extend(this, options);
+        this.object     = object;
+        this.fallThrough = fallThrough;
+        this.listeners  = {};
+        this.extensions = {};
+        this.extensionCount = {};
+        this._msTouches = [];
+        
+        // if a dom element is specified, add a listeners list 
+        // for browser events on the element and register them
+        if (element != null) {
+            this.attachToElement(element);
+        }
+    },
+
+    /**
+     * APIMethod: destroy
+     */
+    destroy: function () {
+        for (var e in this.extensions) {
+            if (typeof this.extensions[e] !== "boolean") {
+                this.extensions[e].destroy();
+            }
+        }
+        this.extensions = null;
+        if (this.element) {
+            OpenLayers.Event.stopObservingElement(this.element);
+            if(this.element.hasScrollEvent) {
+                OpenLayers.Event.stopObserving(
+                    window, "scroll", this.clearMouseListener
+                );
+            }
+        }
+        this.element = null;
+
+        this.listeners = null;
+        this.object = null;
+        this.fallThrough = null;
+        this.eventHandler = null;
+    },
+
+    /**
+     * APIMethod: addEventType
+     * Deprecated.  Any event can be triggered without adding it first.
+     * 
+     * Parameters:
+     * eventName - {String}
+     */
+    addEventType: function(eventName) {
+    },
+
+    /**
+     * Method: attachToElement
+     *
+     * Parameters:
+     * element - {HTMLDOMElement} a DOM element to attach browser events to
+     */
+    attachToElement: function (element) {
+        if (this.element) {
+            OpenLayers.Event.stopObservingElement(this.element);
+        } else {
+            // keep a bound copy of handleBrowserEvent() so that we can
+            // pass the same function to both Event.observe() and .stopObserving()
+            this.eventHandler = OpenLayers.Function.bindAsEventListener(
+                this.handleBrowserEvent, this
+            );
+            
+            // to be used with observe and stopObserving
+            this.clearMouseListener = OpenLayers.Function.bind(
+                this.clearMouseCache, this
+            );
+        }
+        this.element = element;
+        var msTouch = !!window.navigator.msMaxTouchPoints;
+        var type;
+        for (var i = 0, len = this.BROWSER_EVENTS.length; i < len; i++) {
+            type = this.BROWSER_EVENTS[i];
+            // register the event cross-browser
+            OpenLayers.Event.observe(element, type, this.eventHandler
+            );
+            if (msTouch && type.indexOf('touch') === 0) {
+                this.addMsTouchListener(element, type, this.eventHandler);
+            }
+        }
+        // disable dragstart in IE so that mousedown/move/up works normally
+        OpenLayers.Event.observe(element, "dragstart", OpenLayers.Event.stop);
+    },
+    
+    /**
+     * APIMethod: on
+     * Convenience method for registering listeners with a common scope.
+     *     Internally, this method calls <register> as shown in the examples
+     *     below.
+     *
+     * Example use:
+     * (code)
+     * // register a single listener for the "loadstart" event
+     * events.on({"loadstart": loadStartListener});
+     *
+     * // this is equivalent to the following
+     * events.register("loadstart", undefined, loadStartListener);
+     *
+     * // register multiple listeners to be called with the same `this` object
+     * events.on({
+     *     "loadstart": loadStartListener,
+     *     "loadend": loadEndListener,
+     *     scope: object
+     * });
+     *
+     * // this is equivalent to the following
+     * events.register("loadstart", object, loadStartListener);
+     * events.register("loadend", object, loadEndListener);
+     * (end)
+     *
+     * Parameters:
+     *  object - {Object}     
+     */
+    on: function(object) {
+        for(var type in object) {
+            if(type != "scope" && object.hasOwnProperty(type)) {
+                this.register(type, object.scope, object[type]);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: register
+     * Register an event on the events object.
+     *
+     * When the event is triggered, the 'func' function will be called, in the
+     * context of 'obj'. Imagine we were to register an event, specifying an 
+     * OpenLayers.Bounds Object as 'obj'. When the event is triggered, the 
+     * context in the callback function will be our Bounds object. This means
+     * that within our callback function, we can access the properties and 
+     * methods of the Bounds object through the "this" variable. So our 
+     * callback could execute something like: 
+     * :    leftStr = "Left: " + this.left;
+     *   
+     *                   or
+     *  
+     * :    centerStr = "Center: " + this.getCenterLonLat();
+     *
+     * Parameters:
+     * type - {String} Name of the event to register
+     * obj - {Object} The object to bind the context to for the callback#.
+     *     If no object is specified, default is the Events's 'object' property.
+     * func - {Function} The callback function. If no callback is 
+     *     specified, this function does nothing.
+     * priority - {Boolean|Object} If true, adds the new listener to the
+     *     *front* of the events queue instead of to the end.
+     *
+     * Valid options for priority:
+     * extension - {Boolean} If true, then the event will be registered as
+     *     extension event. Extension events are handled before all other
+     *     events.
+     */
+    register: function (type, obj, func, priority) {
+        if (type in OpenLayers.Events && !this.extensions[type]) {
+            this.extensions[type] = new OpenLayers.Events[type](this);
+        }
+        if (func != null) {
+            if (obj == null)  {
+                obj = this.object;
+            }
+            var listeners = this.listeners[type];
+            if (!listeners) {
+                listeners = [];
+                this.listeners[type] = listeners;
+                this.extensionCount[type] = 0;
+            }
+            var listener = {obj: obj, func: func};
+            if (priority) {
+                listeners.splice(this.extensionCount[type], 0, listener);
+                if (typeof priority === "object" && priority.extension) {
+                    this.extensionCount[type]++;
+                }
+            } else {
+                listeners.push(listener);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: registerPriority
+     * Same as register() but adds the new listener to the *front* of the
+     *     events queue instead of to the end.
+     *    
+     *     TODO: get rid of this in 3.0 - Decide whether listeners should be 
+     *     called in the order they were registered or in reverse order.
+     *
+     *
+     * Parameters:
+     * type - {String} Name of the event to register
+     * obj - {Object} The object to bind the context to for the callback#.
+     *                If no object is specified, default is the Events's 
+     *                'object' property.
+     * func - {Function} The callback function. If no callback is 
+     *                   specified, this function does nothing.
+     */
+    registerPriority: function (type, obj, func) {
+        this.register(type, obj, func, true);
+    },
+    
+    /**
+     * APIMethod: un
+     * Convenience method for unregistering listeners with a common scope.
+     *     Internally, this method calls <unregister> as shown in the examples
+     *     below.
+     *
+     * Example use:
+     * (code)
+     * // unregister a single listener for the "loadstart" event
+     * events.un({"loadstart": loadStartListener});
+     *
+     * // this is equivalent to the following
+     * events.unregister("loadstart", undefined, loadStartListener);
+     *
+     * // unregister multiple listeners with the same `this` object
+     * events.un({
+     *     "loadstart": loadStartListener,
+     *     "loadend": loadEndListener,
+     *     scope: object
+     * });
+     *
+     * // this is equivalent to the following
+     * events.unregister("loadstart", object, loadStartListener);
+     * events.unregister("loadend", object, loadEndListener);
+     * (end)
+     */
+    un: function(object) {
+        for(var type in object) {
+            if(type != "scope" && object.hasOwnProperty(type)) {
+                this.unregister(type, object.scope, object[type]);
+            }
+        }
+    },
+
+    /**
+     * APIMethod: unregister
+     *
+     * Parameters:
+     * type - {String} 
+     * obj - {Object} If none specified, defaults to this.object
+     * func - {Function} 
+     */
+    unregister: function (type, obj, func) {
+        if (obj == null)  {
+            obj = this.object;
+        }
+        var listeners = this.listeners[type];
+        if (listeners != null) {
+            for (var i=0, len=listeners.length; i<len; i++) {
+                if (listeners[i].obj == obj && listeners[i].func == func) {
+                    listeners.splice(i, 1);
+                    break;
+                }
+            }
+        }
+    },
+
+    /** 
+     * Method: remove
+     * Remove all listeners for a given event type. If type is not registered,
+     *     does nothing.
+     *
+     * Parameters:
+     * type - {String} 
+     */
+    remove: function(type) {
+        if (this.listeners[type] != null) {
+            this.listeners[type] = [];
+        }
+    },
+
+    /**
+     * APIMethod: triggerEvent
+     * Trigger a specified registered event.  
+     * 
+     * Parameters:
+     * type - {String} 
+     * evt - {Event || Object} will be passed to the listeners.
+     *
+     * Returns:
+     * {Boolean} The last listener return.  If a listener returns false, the
+     *     chain of listeners will stop getting called.
+     */
+    triggerEvent: function (type, evt) {
+        var listeners = this.listeners[type];
+
+        // fast path
+        if(!listeners || listeners.length == 0) {
+            return undefined;
+        }
+
+        // prep evt object with object & div references
+        if (evt == null) {
+            evt = {};
+        }
+        evt.object = this.object;
+        evt.element = this.element;
+        if(!evt.type) {
+            evt.type = type;
+        }
+    
+        // execute all callbacks registered for specified type
+        // get a clone of the listeners array to
+        // allow for splicing during callbacks
+        listeners = listeners.slice();
+        var continueChain;
+        for (var i=0, len=listeners.length; i<len; i++) {
+            var callback = listeners[i];
+            // bind the context to callback.obj
+            continueChain = callback.func.apply(callback.obj, [evt]);
+
+            if ((continueChain != undefined) && (continueChain == false)) {
+                // if callback returns false, execute no more callbacks.
+                break;
+            }
+        }
+        // don't fall through to other DOM elements
+        if (!this.fallThrough) {           
+            OpenLayers.Event.stop(evt, true);
+        }
+        return continueChain;
+    },
+
+    /**
+     * Method: handleBrowserEvent
+     * Basically just a wrapper to the triggerEvent() function, but takes 
+     *     care to set a property 'xy' on the event with the current mouse 
+     *     position.
+     *
+     * Parameters:
+     * evt - {Event} 
+     */
+    handleBrowserEvent: function (evt) {
+        var type = evt.type, listeners = this.listeners[type];
+        if(!listeners || listeners.length == 0) {
+            // noone's listening, bail out
+            return;
+        }
+        // add clientX & clientY to all events - corresponds to average x, y
+        var touches = evt.touches;
+        if (touches && touches[0]) {
+            var x = 0;
+            var y = 0;
+            var num = touches.length;
+            var touch;
+            for (var i=0; i<num; ++i) {
+                touch = this.getTouchClientXY(touches[i]);
+                x += touch.clientX;
+                y += touch.clientY;
+            }
+            evt.clientX = x / num;
+            evt.clientY = y / num;
+        }
+        if (this.includeXY) {
+            evt.xy = this.getMousePosition(evt);
+        } 
+        this.triggerEvent(type, evt);
+    },
+    
+    /**
+     * Method: getTouchClientXY
+     * WebKit has a few bugs for clientX/clientY. This method detects them
+     * and calculate the correct values.
+     *
+     * Parameters:
+     * evt - {Touch} a Touch object from a TouchEvent
+     * 
+     * Returns:
+     * {Object} An object with only clientX and clientY properties with the
+     * calculated values.
+     */
+    getTouchClientXY: function (evt) {
+        // olMochWin is to override window, used for testing
+        var win = window.olMockWin || window,
+            winPageX = win.pageXOffset,
+            winPageY = win.pageYOffset,
+            x = evt.clientX,
+            y = evt.clientY;
+        
+        if (evt.pageY === 0 && Math.floor(y) > Math.floor(evt.pageY) ||
+            evt.pageX === 0 && Math.floor(x) > Math.floor(evt.pageX)) {
+            // iOS4 include scroll offset in clientX/Y
+            x = x - winPageX;
+            y = y - winPageY;
+        } else if (y < (evt.pageY - winPageY) || x < (evt.pageX - winPageX) ) {
+            // Some Android browsers have totally bogus values for clientX/Y
+            // when scrolling/zooming a page
+            x = evt.pageX - winPageX;
+            y = evt.pageY - winPageY;
+        }
+        
+        evt.olClientX = x;
+        evt.olClientY = y;
+        
+        return {
+            clientX: x,
+            clientY: y
+        };
+    },
+    
+    /**
+     * APIMethod: clearMouseCache
+     * Clear cached data about the mouse position. This should be called any 
+     *     time the element that events are registered on changes position 
+     *     within the page.
+     */
+    clearMouseCache: function() { 
+        this.element.scrolls = null;
+        this.element.lefttop = null;
+        this.element.offsets = null;
+    },      
+
+    /**
+     * Method: getMousePosition
+     * 
+     * Parameters:
+     * evt - {Event} 
+     * 
+     * Returns:
+     * {<OpenLayers.Pixel>} The current xy coordinate of the mouse, adjusted
+     *                      for offsets
+     */
+    getMousePosition: function (evt) {
+        if (!this.includeXY) {
+            this.clearMouseCache();
+        } else if (!this.element.hasScrollEvent) {
+            OpenLayers.Event.observe(window, "scroll", this.clearMouseListener);
+            this.element.hasScrollEvent = true;
+        }
+        
+        if (!this.element.scrolls) {
+            var viewportElement = OpenLayers.Util.getViewportElement();
+            this.element.scrolls = [
+                window.pageXOffset || viewportElement.scrollLeft,
+                window.pageYOffset || viewportElement.scrollTop
+            ];
+        }
+
+        if (!this.element.lefttop) {
+            this.element.lefttop = [
+                (document.documentElement.clientLeft || 0),
+                (document.documentElement.clientTop  || 0)
+            ];
+        }
+        
+        if (!this.element.offsets) {
+            this.element.offsets = OpenLayers.Util.pagePosition(this.element);
+        }
+
+        return new OpenLayers.Pixel(
+            (evt.clientX + this.element.scrolls[0]) - this.element.offsets[0]
+                         - this.element.lefttop[0], 
+            (evt.clientY + this.element.scrolls[1]) - this.element.offsets[1]
+                         - this.element.lefttop[1]
+        ); 
+    },
+
+    /**
+     * Method: addMsTouchListener
+     *
+     * Parameters:
+     * element - {DOMElement} The DOM element to register the listener on
+     * type - {String} The event type
+     * handler - {Function} the handler
+     */
+    addMsTouchListener: function (element, type, handler) {
+        var eventHandler = this.eventHandler;
+        var touches = this._msTouches;
+
+        function msHandler(evt) {
+            handler(OpenLayers.Util.applyDefaults({
+                stopPropagation: function() {
+                    for (var i=touches.length-1; i>=0; --i) {
+                        touches[i].stopPropagation();
+                    }
+                },
+                preventDefault: function() {
+                    for (var i=touches.length-1; i>=0; --i) {
+                        touches[i].preventDefault();
+                    }
+                },
+                type: type
+            }, evt));
+        }
+
+        switch (type) {
+            case 'touchstart':
+                return this.addMsTouchListenerStart(element, type, msHandler);
+            case 'touchend':
+                return this.addMsTouchListenerEnd(element, type, msHandler);
+            case 'touchmove':
+                return this.addMsTouchListenerMove(element, type, msHandler);
+            default:
+                throw 'Unknown touch event type';
+        }
+    },
+
+    /**
+     * Method: addMsTouchListenerStart
+     *
+     * Parameters:
+     * element - {DOMElement} The DOM element to register the listener on
+     * type - {String} The event type
+     * handler - {Function} the handler
+     */
+    addMsTouchListenerStart: function(element, type, handler) {
+        var touches = this._msTouches;
+
+        var cb = function(e) {
+
+            var alreadyInArray = false;
+            for (var i=0, ii=touches.length; i<ii; ++i) {
+                if (touches[i].pointerId == e.pointerId) {
+                    alreadyInArray = true;
+                    break;
+                }
+            }
+            if (!alreadyInArray) {
+                touches.push(e);
+            }
+
+            e.touches = touches.slice();
+            handler(e);
+        };
+
+        OpenLayers.Event.observe(element, 'MSPointerDown', cb);
+
+        // Need to also listen for end events to keep the _msTouches list
+        // accurate
+        var internalCb = function(e) {
+            for (var i=0, ii=touches.length; i<ii; ++i) {
+                if (touches[i].pointerId == e.pointerId) {
+                    touches.splice(i, 1);
+                    break;
+                }
+            }
+        };
+        OpenLayers.Event.observe(element, 'MSPointerUp', internalCb);
+    },
+
+    /**
+     * Method: addMsTouchListenerMove
+     *
+     * Parameters:
+     * element - {DOMElement} The DOM element to register the listener on
+     * type - {String} The event type
+     * handler - {Function} the handler
+     */
+    addMsTouchListenerMove: function (element, type, handler) {
+        var touches = this._msTouches;
+        var cb = function(e) {
+
+            //Don't fire touch moves when mouse isn't down
+            if (e.pointerType == e.MSPOINTER_TYPE_MOUSE && e.buttons == 0) {
+                return;
+            }
+
+            if (touches.length == 1 && touches[0].pageX == e.pageX &&
+                    touches[0].pageY == e.pageY) {
+                // don't trigger event when pointer has not moved
+                return;
+            }
+            for (var i=0, ii=touches.length; i<ii; ++i) {
+                if (touches[i].pointerId == e.pointerId) {
+                    touches[i] = e;
+                    break;
+                }
+            }
+
+            e.touches = touches.slice();
+            handler(e);
+        };
+
+        OpenLayers.Event.observe(element, 'MSPointerMove', cb);
+    },
+
+    /**
+     * Method: addMsTouchListenerEnd
+     *
+     * Parameters:
+     * element - {DOMElement} The DOM element to register the listener on
+     * type - {String} The event type
+     * handler - {Function} the handler
+     */
+    addMsTouchListenerEnd: function (element, type, handler) {
+        var touches = this._msTouches;
+
+        var cb = function(e) {
+
+            for (var i=0, ii=touches.length; i<ii; ++i) {
+                if (touches[i].pointerId == e.pointerId) {
+                    touches.splice(i, 1);
+                    break;
+                }
+            }
+            
+            e.touches = touches.slice();
+            handler(e);
+        };
+
+        OpenLayers.Event.observe(element, 'MSPointerUp', cb);
+    },
+
+    CLASS_NAME: "OpenLayers.Events"
+});
+/* ======================================================================
+    OpenLayers/Events/buttonclick.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Events.js
+ */
+
+/**
+ * Class: OpenLayers.Events.buttonclick
+ * Extension event type for handling buttons on top of a dom element. This
+ *     event type fires "buttonclick" on its <target> when a button was
+ *     clicked. Buttons are detected by the "olButton" class.
+ *
+ * This event type makes sure that button clicks do not interfere with other
+ *     events that are registered on the same <element>.
+ *
+ * Event types provided by this extension:
+ * - *buttonclick* Triggered when a button is clicked. Listeners receive an
+ *     object with a *buttonElement* property referencing the dom element of
+ *     the clicked button, and an *buttonXY* property with the click position
+ *     relative to the button.
+ */
+OpenLayers.Events.buttonclick = OpenLayers.Class({
+    
+    /**
+     * Property: target
+     * {<OpenLayers.Events>} The events instance that the buttonclick event will
+     * be triggered on.
+     */
+    target: null,
+    
+    /**
+     * Property: events
+     * {Array} Events to observe and conditionally stop from propagating when
+     *     an element with the olButton class (or its olAlphaImg child) is
+     *     clicked.
+     */
+    events: [
+        'mousedown', 'mouseup', 'click', 'dblclick',
+        'touchstart', 'touchmove', 'touchend', 'keydown'
+    ],
+    
+    /**
+     * Property: startRegEx
+     * {RegExp} Regular expression to test Event.type for events that start
+     *     a buttonclick sequence.
+     */
+    startRegEx: /^mousedown|touchstart$/,
+
+    /**
+     * Property: cancelRegEx
+     * {RegExp} Regular expression to test Event.type for events that cancel
+     *     a buttonclick sequence.
+     */
+    cancelRegEx: /^touchmove$/,
+
+    /**
+     * Property: completeRegEx
+     * {RegExp} Regular expression to test Event.type for events that complete
+     *     a buttonclick sequence.
+     */
+    completeRegEx: /^mouseup|touchend$/,
+    
+    /**
+     * Property: startEvt
+     * {Event} The event that started the click sequence
+     */
+    
+    /**
+     * Constructor: OpenLayers.Events.buttonclick
+     * Construct a buttonclick event type. Applications are not supposed to
+     *     create instances of this class - they are created on demand by
+     *     <OpenLayers.Events> instances.
+     *
+     * Parameters:
+     * target - {<OpenLayers.Events>} The events instance that the buttonclick
+     *     event will be triggered on.
+     */
+    initialize: function(target) {
+        this.target = target;
+        for (var i=this.events.length-1; i>=0; --i) {
+            this.target.register(this.events[i], this, this.buttonClick, {
+                extension: true
+            });
+        }
+    },
+    
+    /**
+     * Method: destroy
+     */
+    destroy: function() {
+        for (var i=this.events.length-1; i>=0; --i) {
+            this.target.unregister(this.events[i], this, this.buttonClick);
+        }
+        delete this.target;
+    },
+
+    /**
+     * Method: getPressedButton
+     * Get the pressed button, if any. Returns undefined if no button
+     * was pressed.
+     *
+     * Arguments:
+     * element - {DOMElement} The event target.
+     *
+     * Returns:
+     * {DOMElement} The button element, or undefined.
+     */
+    getPressedButton: function(element) {
+        var depth = 3, // limit the search depth
+            button;
+        do {
+            if(OpenLayers.Element.hasClass(element, "olButton")) {
+                // hit!
+                button = element;
+                break;
+            }
+            element = element.parentNode;
+        } while(--depth > 0 && element);
+        return button;
+    },
+    
+    /**
+     * Method: ignore
+     * Check for event target elements that should be ignored by OpenLayers.
+     *
+     * Parameters:
+     * element - {DOMElement} The event target.
+     */
+    ignore: function(element) {
+        var depth = 3,
+            ignore = false;
+        do {
+            if (element.nodeName.toLowerCase() === 'a') {
+                ignore = true;
+                break;
+            }
+            element = element.parentNode;
+        } while (--depth > 0 && element);
+        return ignore;
+    },
+
+    /**
+     * Method: buttonClick
+     * Check if a button was clicked, and fire the buttonclick event
+     *
+     * Parameters:
+     * evt - {Event}
+     */
+    buttonClick: function(evt) {
+        var propagate = true,
+            element = OpenLayers.Event.element(evt);
+        if (element && (OpenLayers.Event.isLeftClick(evt) || !~evt.type.indexOf("mouse"))) {
+            // was a button pressed?
+            var button = this.getPressedButton(element);
+            if (button) {
+                if (evt.type === "keydown") {
+                    switch (evt.keyCode) {
+                    case OpenLayers.Event.KEY_RETURN:
+                    case OpenLayers.Event.KEY_SPACE:
+                        this.target.triggerEvent("buttonclick", {
+                            buttonElement: button
+                        });
+                        OpenLayers.Event.stop(evt);
+                        propagate = false;
+                        break;
+                    }
+                } else if (this.startEvt) {
+                    if (this.completeRegEx.test(evt.type)) {
+                        var pos = OpenLayers.Util.pagePosition(button);
+                        var viewportElement = OpenLayers.Util.getViewportElement();
+                        var scrollTop = window.pageYOffset || viewportElement.scrollTop;
+                        var scrollLeft = window.pageXOffset || viewportElement.scrollLeft;
+                        pos[0] = pos[0] - scrollLeft;
+                        pos[1] = pos[1] - scrollTop;
+                        
+                        this.target.triggerEvent("buttonclick", {
+                            buttonElement: button,
+                            buttonXY: {
+                                x: this.startEvt.clientX - pos[0],
+                                y: this.startEvt.clientY - pos[1]
+                            }
+                        });
+                    }
+                    if (this.cancelRegEx.test(evt.type)) {
+                        delete this.startEvt;
+                    }
+                    OpenLayers.Event.stop(evt);
+                    propagate = false;
+                }
+                if (this.startRegEx.test(evt.type)) {
+                    this.startEvt = evt;
+                    OpenLayers.Event.stop(evt);
+                    propagate = false;
+                }
+            } else {
+                propagate = !this.ignore(OpenLayers.Event.element(evt));
+                delete this.startEvt;
+            }
+        }
+        return propagate;
+    }
+    
+});
+/* ======================================================================
+    OpenLayers/Util/vendorPrefix.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ */
+
+OpenLayers.Util = OpenLayers.Util || {};
+/**
+ * Namespace: OpenLayers.Util.vendorPrefix
+ * A collection of utility functions to detect vendor prefixed features
+ */
+OpenLayers.Util.vendorPrefix = (function() {
+    "use strict";
+    
+    var VENDOR_PREFIXES = ["", "O", "ms", "Moz", "Webkit"],
+        divStyle = document.createElement("div").style,
+        cssCache = {},
+        jsCache = {};
+
+    
+    /**
+     * Function: domToCss
+     * Converts a upper camel case DOM style property name to a CSS property
+     *      i.e. transformOrigin -> transform-origin
+     *      or   WebkitTransformOrigin -> -webkit-transform-origin
+     *
+     * Parameters:
+     * prefixedDom - {String} The property to convert
+     *
+     * Returns:
+     * {String} The CSS property
+     */
+    function domToCss(prefixedDom) {
+        if (!prefixedDom) { return null; }
+        return prefixedDom.
+            replace(/([A-Z])/g, function(c) { return "-" + c.toLowerCase(); }).
+            replace(/^ms-/, "-ms-");
+    }
+
+    /**
+     * APIMethod: css
+     * Detect which property is used for a CSS property
+     *
+     * Parameters:
+     * property - {String} The standard (unprefixed) CSS property name
+     *
+     * Returns:
+     * {String} The standard CSS property, prefixed property or null if not
+     *          supported
+     */
+    function css(property) {
+        if (cssCache[property] === undefined) {
+            var domProperty = property.
+                replace(/(-[\s\S])/g, function(c) { return c.charAt(1).toUpperCase(); });
+            var prefixedDom = style(domProperty);
+            cssCache[property] = domToCss(prefixedDom);
+        }
+        return cssCache[property];
+    }
+
+    /**
+     * APIMethod: js
+     * Detect which property is used for a JS property/method
+     *
+     * Parameters:
+     * obj - {Object} The object to test on
+     * property - {String} The standard (unprefixed) JS property name
+     *
+     * Returns:
+     * {String} The standard JS property, prefixed property or null if not
+     *          supported
+     */
+    function js(obj, property) {
+        if (jsCache[property] === undefined) {
+            var tmpProp,
+                i = 0,
+                l = VENDOR_PREFIXES.length,
+                prefix,
+                isStyleObj = (typeof obj.cssText !== "undefined");
+
+            jsCache[property] = null;
+            for(; i<l; i++) {
+                prefix = VENDOR_PREFIXES[i];
+                if(prefix) {
+                    if (!isStyleObj) {
+                        // js prefix should be lower-case, while style
+                        // properties have upper case on first character
+                        prefix = prefix.toLowerCase();
+                    }
+                    tmpProp = prefix + property.charAt(0).toUpperCase() + property.slice(1);
+                } else {
+                    tmpProp = property;
+                }
+
+                if(obj[tmpProp] !== undefined) {
+                    jsCache[property] = tmpProp;
+                    break;
+                }
+            }
+        }
+        return jsCache[property];
+    }
+    
+    /**
+     * APIMethod: style
+     * Detect which property is used for a DOM style property
+     *
+     * Parameters:
+     * property - {String} The standard (unprefixed) style property name
+     *
+     * Returns:
+     * {String} The standard style property, prefixed property or null if not
+     *          supported
+     */
+    function style(property) {
+        return js(divStyle, property);
+    }
+    
+    return {
+        css:      css,
+        js:       js,
+        style:    style,
+        
+        // used for testing
+        cssCache:       cssCache,
+        jsCache:        jsCache
+    };
+}());
+/* ======================================================================
+    OpenLayers/Animation.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/SingleFile.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ */
+
+/**
+ * Namespace: OpenLayers.Animation
+ * A collection of utility functions for executing methods that repaint a 
+ *     portion of the browser window.  These methods take advantage of the
+ *     browser's scheduled repaints where requestAnimationFrame is available.
+ */
+OpenLayers.Animation = (function(window) {
+    
+    /**
+     * Property: isNative
+     * {Boolean} true if a native requestAnimationFrame function is available
+     */
+    var requestAnimationFrame = OpenLayers.Util.vendorPrefix.js(window, "requestAnimationFrame");
+    var isNative = !!(requestAnimationFrame);
+    
+    /**
+     * Function: requestFrame
+     * Schedule a function to be called at the next available animation frame.
+     *     Uses the native method where available.  Where requestAnimationFrame is
+     *     not available, setTimeout will be called with a 16ms delay.
+     *
+     * Parameters:
+     * callback - {Function} The function to be called at the next animation frame.
+     * element - {DOMElement} Optional element that visually bounds the animation.
+     */
+    var requestFrame = (function() {
+        var request = window[requestAnimationFrame] ||
+            function(callback, element) {
+                window.setTimeout(callback, 16);
+            };
+        // bind to window to avoid illegal invocation of native function
+        return function(callback, element) {
+            request.apply(window, [callback, element]);
+        };
+    })();
+    
+    // private variables for animation loops
+    var counter = 0;
+    var loops = {};
+    
+    /**
+     * Function: start
+     * Executes a method with <requestFrame> in series for some 
+     *     duration.
+     *
+     * Parameters:
+     * callback - {Function} The function to be called at the next animation frame.
+     * duration - {Number} Optional duration for the loop.  If not provided, the
+     *     animation loop will execute indefinitely.
+     * element - {DOMElement} Optional element that visually bounds the animation.
+     *
+     * Returns:
+     * {Number} Identifier for the animation loop.  Used to stop animations with
+     *     <stop>.
+     */
+    function start(callback, duration, element) {
+        duration = duration > 0 ? duration : Number.POSITIVE_INFINITY;
+        var id = ++counter;
+        var start = +new Date;
+        loops[id] = function() {
+            if (loops[id] && +new Date - start <= duration) {
+                callback();
+                if (loops[id]) {
+                    requestFrame(loops[id], element);
+                }
+            } else {
+                delete loops[id];
+            }
+        };
+        requestFrame(loops[id], element);
+        return id;
+    }
+    
+    /**
+     * Function: stop
+     * Terminates an animation loop started with <start>.
+     *
+     * Parameters:
+     * id - {Number} Identifier returned from <start>.
+     */
+    function stop(id) {
+        delete loops[id];
+    }
+    
+    return {
+        isNative: isNative,
+        requestFrame: requestFrame,
+        start: start,
+        stop: stop
+    };
+    
+})(window);
+/* ======================================================================
+    OpenLayers/Tween.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Animation.js
+ */
+
+/**
+ * Namespace: OpenLayers.Tween
+ */
+OpenLayers.Tween = OpenLayers.Class({
+    
+    /**
+     * APIProperty: easing
+     * {<OpenLayers.Easing>(Function)} Easing equation used for the animation
+     *     Defaultly set to OpenLayers.Easing.Expo.easeOut
+     */
+    easing: null,
+    
+    /**
+     * APIProperty: begin
+     * {Object} Values to start the animation with
+     */
+    begin: null,
+    
+    /**
+     * APIProperty: finish
+     * {Object} Values to finish the animation with
+     */
+    finish: null,
+    
+    /**
+     * APIProperty: duration
+     * {int} duration of the tween (number of steps)
+     */
+    duration: null,
+    
+    /**
+     * APIProperty: callbacks
+     * {Object} An object with start, eachStep and done properties whose values
+     *     are functions to be call during the animation. They are passed the
+     *     current computed value as argument.
+     */
+    callbacks: null,
+    
+    /**
+     * Property: time
+     * {int} Step counter
+     */
+    time: null,
+    
+    /**
+     * APIProperty: minFrameRate
+     * {Number} The minimum framerate for animations in frames per second. After
+     * each step, the time spent in the animation is compared to the calculated
+     * time at this frame rate. If the animation runs longer than the calculated
+     * time, the next step is skipped. Default is 30.
+     */
+    minFrameRate: null,
+
+    /**
+     * Property: startTime
+     * {Number} The timestamp of the first execution step. Used for skipping
+     * frames
+     */
+    startTime: null,
+    
+    /**
+     * Property: animationId
+     * {int} Loop id returned by OpenLayers.Animation.start
+     */
+    animationId: null,
+    
+    /**
+     * Property: playing
+     * {Boolean} Tells if the easing is currently playing
+     */
+    playing: false,
+    
+    /** 
+     * Constructor: OpenLayers.Tween
+     * Creates a Tween.
+     *
+     * Parameters:
+     * easing - {<OpenLayers.Easing>(Function)} easing function method to use
+     */ 
+    initialize: function(easing) {
+        this.easing = (easing) ? easing : OpenLayers.Easing.Expo.easeOut;
+    },
+    
+    /**
+     * APIMethod: start
+     * Plays the Tween, and calls the callback method on each step
+     * 
+     * Parameters:
+     * begin - {Object} values to start the animation with
+     * finish - {Object} values to finish the animation with
+     * duration - {int} duration of the tween (number of steps)
+     * options - {Object} hash of options (callbacks (start, eachStep, done),
+     *     minFrameRate)
+     */
+    start: function(begin, finish, duration, options) {
+        this.playing = true;
+        this.begin = begin;
+        this.finish = finish;
+        this.duration = duration;
+        this.callbacks = options.callbacks;
+        this.minFrameRate = options.minFrameRate || 30;
+        this.time = 0;
+        this.startTime = new Date().getTime();
+        OpenLayers.Animation.stop(this.animationId);
+        this.animationId = null;
+        if (this.callbacks && this.callbacks.start) {
+            this.callbacks.start.call(this, this.begin);
+        }
+        this.animationId = OpenLayers.Animation.start(
+            OpenLayers.Function.bind(this.play, this)
+        );
+    },
+    
+    /**
+     * APIMethod: stop
+     * Stops the Tween, and calls the done callback
+     *     Doesn't do anything if animation is already finished
+     */
+    stop: function() {
+        if (!this.playing) {
+            return;
+        }
+        
+        if (this.callbacks && this.callbacks.done) {
+            this.callbacks.done.call(this, this.finish);
+        }
+        OpenLayers.Animation.stop(this.animationId);
+        this.animationId = null;
+        this.playing = false;
+    },
+    
+    /**
+     * Method: play
+     * Calls the appropriate easing method
+     */
+    play: function() {
+        var value = {};
+        for (var i in this.begin) {
+            var b = this.begin[i];
+            var f = this.finish[i];
+            if (b == null || f == null || isNaN(b) || isNaN(f)) {
+                throw new TypeError('invalid value for Tween');
+            }
+
+            var c = f - b;
+            value[i] = this.easing.apply(this, [this.time, b, c, this.duration]);
+        }
+        this.time++;
+        
+        if (this.callbacks && this.callbacks.eachStep) {
+            // skip frames if frame rate drops below threshold
+            if ((new Date().getTime() - this.startTime) / this.time <= 1000 / this.minFrameRate) {
+                this.callbacks.eachStep.call(this, value);
+            }
+        }
+        
+        if (this.time > this.duration) {
+            this.stop();
+        }
+    },
+    
+    /**
+     * Create empty functions for all easing methods.
+     */
+    CLASS_NAME: "OpenLayers.Tween"
+});
+
+/**
+ * Namespace: OpenLayers.Easing
+ * 
+ * Credits:
+ *      Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>
+ */
+OpenLayers.Easing = {
+    /**
+     * Create empty functions for all easing methods.
+     */
+    CLASS_NAME: "OpenLayers.Easing"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Linear
+ */
+OpenLayers.Easing.Linear = {
+    
+    /**
+     * Function: easeIn
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeIn: function(t, b, c, d) {
+        return c*t/d + b;
+    },
+    
+    /**
+     * Function: easeOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeOut: function(t, b, c, d) {
+        return c*t/d + b;
+    },
+    
+    /**
+     * Function: easeInOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeInOut: function(t, b, c, d) {
+        return c*t/d + b;
+    },
+
+    CLASS_NAME: "OpenLayers.Easing.Linear"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Expo
+ */
+OpenLayers.Easing.Expo = {
+    
+    /**
+     * Function: easeIn
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeIn: function(t, b, c, d) {
+        return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
+    },
+    
+    /**
+     * Function: easeOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeOut: function(t, b, c, d) {
+        return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
+    },
+    
+    /**
+     * Function: easeInOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeInOut: function(t, b, c, d) {
+        if (t==0) return b;
+        if (t==d) return b+c;
+        if ((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
+        return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
+    },
+
+    CLASS_NAME: "OpenLayers.Easing.Expo"
+};
+
+/**
+ * Namespace: OpenLayers.Easing.Quad
+ */
+OpenLayers.Easing.Quad = {
+    
+    /**
+     * Function: easeIn
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeIn: function(t, b, c, d) {
+        return c*(t/=d)*t + b;
+    },
+    
+    /**
+     * Function: easeOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeOut: function(t, b, c, d) {
+        return -c *(t/=d)*(t-2) + b;
+    },
+    
+    /**
+     * Function: easeInOut
+     * 
+     * Parameters:
+     * t - {Float} time
+     * b - {Float} beginning position
+     * c - {Float} total change
+     * d - {Float} duration of the transition
+     *
+     * Returns:
+     * {Float}
+     */
+    easeInOut: function(t, b, c, d) {
+        if ((t/=d/2) < 1) return c/2*t*t + b;
+        return -c/2 * ((--t)*(t-2) - 1) + b;
+    },
+
+    CLASS_NAME: "OpenLayers.Easing.Quad"
+};
+/* ======================================================================
+    OpenLayers/Projection.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ */
+
+/**
+ * Namespace: OpenLayers.Projection
+ * Methods for coordinate transforms between coordinate systems.  By default,
+ *     OpenLayers ships with the ability to transform coordinates between
+ *     geographic (EPSG:4326) and web or spherical mercator (EPSG:900913 et al.)
+ *     coordinate reference systems.  See the <transform> method for details
+ *     on usage.
+ *
+ * Additional transforms may be added by using the <proj4js at http://proj4js.org/>
+ *     library.  If the proj4js library is included, the <transform> method 
+ *     will work between any two coordinate reference systems with proj4js 
+ *     definitions.
+ *
+ * If the proj4js library is not included, or if you wish to allow transforms
+ *     between arbitrary coordinate reference systems, use the <addTransform>
+ *     method to register a custom transform method.
+ */
+OpenLayers.Projection = OpenLayers.Class({
+
+    /**
+     * Property: proj
+     * {Object} Proj4js.Proj instance.
+     */
+    proj: null,
+    
+    /**
+     * Property: projCode
+     * {String}
+     */
+    projCode: null,
+    
+    /**
+     * Property: titleRegEx
+     * {RegExp} regular expression to strip the title from a proj4js definition
+     */
+    titleRegEx: /\+title=[^\+]*/,
+
+    /**
+     * Constructor: OpenLayers.Projection
+     * This class offers several methods for interacting with a wrapped 
+     *     pro4js projection object. 
+     *
+     * Parameters:
+     * projCode - {String} A string identifying the Well Known Identifier for
+     *    the projection.
+     * options - {Object} An optional object to set additional properties
+     *     on the projection.
+     *
+     * Returns:
+     * {<OpenLayers.Projection>} A projection object.
+     */
+    initialize: function(projCode, options) {
+        OpenLayers.Util.extend(this, options);
+        this.projCode = projCode;
+        if (typeof Proj4js == "object") {
+            this.proj = new Proj4js.Proj(projCode);
+        }
+    },
+    
+    /**
+     * APIMethod: getCode
+     * Get the string SRS code.
+     *
+     * Returns:
+     * {String} The SRS code.
+     */
+    getCode: function() {
+        return this.proj ? this.proj.srsCode : this.projCode;
+    },
+   
+    /**
+     * APIMethod: getUnits
+     * Get the units string for the projection -- returns null if 
+     *     proj4js is not available.
+     *
+     * Returns:
+     * {String} The units abbreviation.
+     */
+    getUnits: function() {
+        return this.proj ? this.proj.units : null;
+    },
+
+    /**
+     * Method: toString
+     * Convert projection to string (getCode wrapper).
+     *
+     * Returns:
+     * {String} The projection code.
+     */
+    toString: function() {
+        return this.getCode();
+    },
+
+    /**
+     * Method: equals
+     * Test equality of two projection instances.  Determines equality based
+     *     soley on the projection code.
+     *
+     * Returns:
+     * {Boolean} The two projections are equivalent.
+     */
+    equals: function(projection) {
+        var p = projection, equals = false;
+        if (p) {
+            if (!(p instanceof OpenLayers.Projection)) {
+                p = new OpenLayers.Projection(p);
+            }
+            if ((typeof Proj4js == "object") && this.proj.defData && p.proj.defData) {
+                equals = this.proj.defData.replace(this.titleRegEx, "") ==
+                    p.proj.defData.replace(this.titleRegEx, "");
+            } else if (p.getCode) {
+                var source = this.getCode(), target = p.getCode();
+                equals = source == target ||
+                    !!OpenLayers.Projection.transforms[source] &&
+                    OpenLayers.Projection.transforms[source][target] ===
+                        OpenLayers.Projection.nullTransform;
+            }
+        }
+        return equals;   
+    },
+
+    /* Method: destroy
+     * Destroy projection object.
+     */
+    destroy: function() {
+        delete this.proj;
+        delete this.projCode;
+    },
+    
+    CLASS_NAME: "OpenLayers.Projection" 
+});     
+
+/**
+ * Property: transforms
+ * {Object} Transforms is an object, with from properties, each of which may
+ * have a to property. This allows you to define projections without 
+ * requiring support for proj4js to be included.
+ *
+ * This object has keys which correspond to a 'source' projection object.  The
+ * keys should be strings, corresponding to the projection.getCode() value.
+ * Each source projection object should have a set of destination projection
+ * keys included in the object. 
+ * 
+ * Each value in the destination object should be a transformation function,
+ * where the function is expected to be passed an object with a .x and a .y
+ * property.  The function should return the object, with the .x and .y
+ * transformed according to the transformation function.
+ *
+ * Note - Properties on this object should not be set directly.  To add a
+ *     transform method to this object, use the <addTransform> method.  For an
+ *     example of usage, see the OpenLayers.Layer.SphericalMercator file.
+ */
+OpenLayers.Projection.transforms = {};
+
+/**
+ * APIProperty: defaults
+ * {Object} Defaults for the SRS codes known to OpenLayers (currently
+ * EPSG:4326, CRS:84, urn:ogc:def:crs:EPSG:6.6:4326, EPSG:900913, EPSG:3857,
+ * EPSG:102113 and EPSG:102100). Keys are the SRS code, values are units,
+ * maxExtent (the validity extent for the SRS) and yx (true if this SRS is
+ * known to have a reverse axis order).
+ */
+OpenLayers.Projection.defaults = {
+    "EPSG:4326": {
+        units: "degrees",
+        maxExtent: [-180, -90, 180, 90],
+        yx: true
+    },
+    "CRS:84": {
+        units: "degrees",
+        maxExtent: [-180, -90, 180, 90]
+    },
+    "EPSG:900913": {
+        units: "m",
+        maxExtent: [-20037508.34, -20037508.34, 20037508.34, 20037508.34]
+    }
+};
+
+/**
+ * APIMethod: addTransform
+ * Set a custom transform method between two projections.  Use this method in
+ *     cases where the proj4js lib is not available or where custom projections
+ *     need to be handled.
+ *
+ * Parameters:
+ * from - {String} The code for the source projection
+ * to - {String} the code for the destination projection
+ * method - {Function} A function that takes a point as an argument and
+ *     transforms that point from the source to the destination projection
+ *     in place.  The original point should be modified.
+ */
+OpenLayers.Projection.addTransform = function(from, to, method) {
+    if (method === OpenLayers.Projection.nullTransform) {
+        var defaults = OpenLayers.Projection.defaults[from];
+        if (defaults && !OpenLayers.Projection.defaults[to]) {
+            OpenLayers.Projection.defaults[to] = defaults;
+        }
+    }
+    if(!OpenLayers.Projection.transforms[from]) {
+        OpenLayers.Projection.transforms[from] = {};
+    }
+    OpenLayers.Projection.transforms[from][to] = method;
+};
+
+/**
+ * APIMethod: transform
+ * Transform a point coordinate from one projection to another.  Note that
+ *     the input point is transformed in place.
+ * 
+ * Parameters:
+ * point - {<OpenLayers.Geometry.Point> | Object} An object with x and y
+ *     properties representing coordinates in those dimensions.
+ * source - {OpenLayers.Projection} Source map coordinate system
+ * dest - {OpenLayers.Projection} Destination map coordinate system
+ *
+ * Returns:
+ * point - {object} A transformed coordinate.  The original point is modified.
+ */
+OpenLayers.Projection.transform = function(point, source, dest) {
+    if (source && dest) {
+        if (!(source instanceof OpenLayers.Projection)) {
+            source = new OpenLayers.Projection(source);
+        }
+        if (!(dest instanceof OpenLayers.Projection)) {
+            dest = new OpenLayers.Projection(dest);
+        }
+        if (source.proj && dest.proj) {
+            point = Proj4js.transform(source.proj, dest.proj, point);
+        } else {
+            var sourceCode = source.getCode();
+            var destCode = dest.getCode();
+            var transforms = OpenLayers.Projection.transforms;
+            if (transforms[sourceCode] && transforms[sourceCode][destCode]) {
+                transforms[sourceCode][destCode](point);
+            }
+        }
+    }
+    return point;
+};
+
+/**
+ * APIFunction: nullTransform
+ * A null transformation - useful for defining projection aliases when
+ * proj4js is not available:
+ *
+ * (code)
+ * OpenLayers.Projection.addTransform("EPSG:3857", "EPSG:900913",
+ *     OpenLayers.Projection.nullTransform);
+ * OpenLayers.Projection.addTransform("EPSG:900913", "EPSG:3857",
+ *     OpenLayers.Projection.nullTransform);
+ * (end)
+ */
+OpenLayers.Projection.nullTransform = function(point) {
+    return point;
+};
+
+/**
+ * Note: Transforms for web mercator <-> geographic
+ * OpenLayers recognizes EPSG:3857, EPSG:900913, EPSG:102113 and EPSG:102100.
+ * OpenLayers originally started referring to EPSG:900913 as web mercator.
+ * The EPSG has declared EPSG:3857 to be web mercator.
+ * ArcGIS 10 recognizes the EPSG:3857, EPSG:102113, and EPSG:102100 as
+ * equivalent.  See http://blogs.esri.com/Dev/blogs/arcgisserver/archive/2009/11/20/ArcGIS-Online-moving-to-Google-_2F00_-Bing-tiling-scheme_3A00_-What-does-this-mean-for-you_3F00_.aspx#12084.
+ * For geographic, OpenLayers recognizes EPSG:4326, CRS:84 and
+ * urn:ogc:def:crs:EPSG:6.6:4326. OpenLayers also knows about the reverse axis
+ * order for EPSG:4326. 
+ */
+(function() {
+
+    var pole = 20037508.34;
+
+    function inverseMercator(xy) {
+        xy.x = 180 * xy.x / pole;
+        xy.y = 180 / Math.PI * (2 * Math.atan(Math.exp((xy.y / pole) * Math.PI)) - Math.PI / 2);
+        return xy;
+    }
+
+    function forwardMercator(xy) {
+        xy.x = xy.x * pole / 180;
+        var y = Math.log(Math.tan((90 + xy.y) * Math.PI / 360)) / Math.PI * pole;
+        xy.y = Math.max(-20037508.34, Math.min(y, 20037508.34));
+        return xy;
+    }
+
+    function map(base, codes) {
+        var add = OpenLayers.Projection.addTransform;
+        var same = OpenLayers.Projection.nullTransform;
+        var i, len, code, other, j;
+        for (i=0, len=codes.length; i<len; ++i) {
+            code = codes[i];
+            add(base, code, forwardMercator);
+            add(code, base, inverseMercator);
+            for (j=i+1; j<len; ++j) {
+                other = codes[j];
+                add(code, other, same);
+                add(other, code, same);
+            }
+        }
+    }
+    
+    // list of equivalent codes for web mercator
+    var mercator = ["EPSG:900913", "EPSG:3857", "EPSG:102113", "EPSG:102100"],
+        geographic = ["CRS:84", "urn:ogc:def:crs:EPSG:6.6:4326", "EPSG:4326"],
+        i;
+    for (i=mercator.length-1; i>=0; --i) {
+        map(mercator[i], geographic);
+    }
+    for (i=geographic.length-1; i>=0; --i) {
+        map(geographic[i], mercator);
+    }
+
+})();
+/* ======================================================================
+    OpenLayers/Map.js
+   ====================================================================== */
+
+/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for
+ * full list of contributors). Published under the 2-clause BSD license.
+ * See license.txt in the OpenLayers distribution or repository for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/BaseTypes/Class.js
+ * @requires OpenLayers/Util.js
+ * @requires OpenLayers/Util/vendorPrefix.js
+ * @requires OpenLayers/Events.js
+ * @requires OpenLayers/Tween.js
+ * @requires OpenLayers/Projection.js
+ */
+
+/**
+ * Class: OpenLayers.Map
+ * Instances of OpenLayers.Map are interactive maps embedded in a web page.
+ * Create a new map with the <OpenLayers.Map> constructor.
+ * 
+ * On their own maps do not provide much functionality.  To extend a map
+ * it's necessary to add controls (<OpenLayers.Control>) and 
+ * layers (<OpenLayers.Layer>) to the map. 
+ */
+OpenLayers.Map = OpenLayers.Class({
+    
+    /**
+     * Constant: Z_INDEX_BASE
+     * {Object} Base z-indexes for different classes of thing 
+     */
+    Z_INDEX_BASE: {
+        BaseLayer: 100,
+        Overlay: 325,
+        Feature: 725,
+        Popup: 750,
+        Control: 1000
+    },
+
+    /**
+     * APIProperty: events
+     * {<OpenLayers.Events>}
+     *
+     * Register a listener for a particular event with the following syntax:
+     * (code)
+     * map.events.register(type, obj, listener);
+     * (end)
+     *
+     * Listeners will be called with a reference to an event object.  The
+     *     properties of this event depends on exactly what happened.
+     *
+     * All event objects have at least the following properties:
+     * object - {Object} A reference to map.events.object.
+     * element - {DOMElement} A reference to map.events.element.
+     *
+     * Browser events have the following additional properties:
+     * xy - {<OpenLayers.Pixel>} The pixel location of the event (relative
+     *     to the the map viewport).
+     *
+     * Supported map event types:
+     * preaddlayer - triggered before a layer has been added.  The event
+     *     object will include a *layer* property that references the layer  
+     *     to be added. When a listener returns "false" the adding will be 
+     *     aborted.
+     * addlayer - triggered after a layer has been added.  The event object
+     *     will include a *layer* property that references the added layer.
+     * preremovelayer - triggered before a layer has been removed. The event
+     *     object will include a *layer* property that references the layer  
+     *     to be removed. When a listener returns "false" the removal will be 
+     *     aborted.
+     * removelayer - triggered after a layer has been removed.  The event
+     *     object will include a *layer* property that references the removed
+     *     layer.
+     * changelayer - triggered after a layer name change, order change,
+     *     opacity change, params change, visibility change (actual visibility,
+     *     not the layer's visibility property) or attribution change (due to
+     *     extent change). Listeners will receive an event object with *layer*
+     *     and *property* properties. The *layer* property will be a reference
+     *     to the changed layer. The *property* property will be a key to the
+     *     changed property (name, order, opacity, params, visibility or
+     *     attribution).
+     * movestart - triggered after the start of a drag, pan, or zoom. The event
+     *     object may include a *zoomChanged* property that tells whether the
+     *     zoom has changed.
+     * move - triggered after each drag, pan, or zoom
+     * moveend - triggered after a drag, pan, or zoom completes
+     * zoomend - triggered after a zoom completes
+     * mouseover - triggered after mouseover the map
+     * mouseout - triggered after mouseout the map
+     * mousemove - triggered after mousemove the map
+     * changebaselayer - triggered after the base layer changes
+     * updatesize - triggered after the <updateSize> method was executed
+     */
+
+    /**
+     * Property: id
+     * {String} Unique identifier for the map
+     */
+    id: null,
+    
+    /**
+     * Property: fractionalZoom
+     * {Boolean} For a base layer that supports it, allow the map resolution
+     *     to be set to a value between one of the values in the resolutions
+     *     array.  Default is false.
+     *
+     * When fractionalZoom is set to true, it is possible to zoom to
+     *     an arbitrary extent.  This requires a base layer from a source
+     *     that supports requests for arbitrary extents (i.e. not cached
+     *     tiles on a regular lattice).  This means that fractionalZoom
+     *     will not work with commercial layers (Google, Yahoo, VE), layers
+     *     using TileCache, or any other pre-cached data sources.
+     *
+     * If you are using fractionalZoom, then you should also use
+     *     <getResolutionForZoom> instead of layer.resolutions[zoom] as the
+     *     former works for non-integer zoom levels.
+     */
+    fractionalZoom: false,
+    
+    /**
+     * APIProperty: events
+     * {<OpenLayers.Events>} An events object that handles all 
+     *                       events on the map
+     */
+    events: null,
+    
+    /**
+     * APIProperty: allOverlays
+     * {Boolean} Allow the map to function with "overlays" only.  Defaults to
+     *     false.  If true, the lowest layer in the draw order will act as
+     *     the base layer.  In addition, if set to true, all layers will
+     *     have isBaseLayer set to false when they are added to the map.
+     *
+     * Note:
+     * If you set map.allOverlays to true, then you *cannot* use
+     *     map.setBaseLayer or layer.setIsBaseLayer.  With allOverlays true,
+     *     the lowest layer in the draw layer is the base layer.  So, to change
+     *     the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
+     *     index to 0.
+     */
+    allOverlays: false,
+
+    /**
+     * APIProperty: div
+     * {DOMElement|String} The element that contains the map (or an id for
+     *     that element).  If the <OpenLayers.Map> constructor is called
+     *     with two arguments, this should be provided as the first argument.
+     *     Alternatively, the map constructor can be called with the options
+     *     object as the only argument.  In this case (one argument), a
+     *     div property may or may not be provided.  If the div property
+     *     is not provided, the map can be rendered to a container later
+     *     using the <render> method.
+     *     
+     * Note:
+     * If you are calling <render> after map construction, do not use
+     *     <maxResolution>  auto.  Instead, divide your <maxExtent> by your
+     *     maximum expected dimension.
+     */
+    div: null,
+    
+    /**
+     * Property: dragging
+     * {Boolean} The map is currently being dragged.
+     */
+    dragging: false,
+
+    /**
+     * Property: size
+     * {<OpenLayers.Size>} Size of the main div (this.div)
+     */
+    size: null,
+    
+    /**
+     * Property: viewPortDiv
+     * {HTMLDivElement} The element that represents the map viewport
+     */
+    viewPortDiv: null,
+
+    /**
+     * Property: layerContainerOrigin
+     * {<OpenLayers.LonLat>} The lonlat at which the later container was
+     *                       re-initialized (on-zoom)
+     */
+    layerContainerOrigin: null,
+
+    /**
+     * Property: layerContainerDiv
+     * {HTMLDivElement} The element that contains the layers.
+     */
+    layerContainerDiv: null,
+
+    /**
+     * APIProperty: layers
+     * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
+     */
+    layers: null,
+
+    /**
+     * APIProperty: controls
+     * {Array(<OpenLayers.Control>)} List of controls associated with the map.
+     *
+     * If not provided in the map options at construction, the map will
+     *     by default be given the following controls if present in the build:
+     *  - <OpenLayers.Control.Navigation> or <OpenLayers.Control.TouchNavigation>
+     *  - <OpenLayers.Control.Zoom> or <OpenLayers.Control.PanZoom>
+     *  - <OpenLayers.Control.ArgParser>
+     *  - <OpenLayers.Control.Attribution>
+     */
+    controls: null,
+
+    /**
+     * Property: popups
+     * {Array(<OpenLayers.Popup>)} List of popups associated with the map
+     */
+    popups: null,
+
+    /**
+     * APIProperty: baseLayer
+     * {<OpenLayers.Layer>} The currently selected base layer.  This determines
+     * min/max zoom level, projection, etc.
+     */
+    baseLayer: null,
+    
+    /**
+     * Property: center
+     * {<OpenLayers.LonLat>} The current center of the map
+     */
+    center: null,
+
+    /**
+     * Property: resolution
+     * {Float} The resolution of the map.
+     */
+    resolution: null,
+
+    /**
+     * Property: zoom
+     * {Integer} The current zoom level of the map
+     */
+    zoom: 0,    
+
+    /**
+     * Property: panRatio
+     * {Float} The ratio of the current extent within
+     *         which panning will tween.
+     */
+    panRatio: 1.5,    
+
+    /**
+     * APIProperty: options
+     * {Object} The options object passed to the class constructor. Read-only.
+     */
+    options: null,
+
+  // Options
+
+    /**
+     * APIProperty: tileSize
+     * {<OpenLayers.Size>} Set in the map options to override the default tile
+     *                     size for this map.
+     */
+    tileSize: null,
+
+    /**
+     * APIProperty: projection
+     * {String} Set in the map options to specify the default projection 
+     *          for layers added to this map. When using a projection other than EPSG:4326
+     *          (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator),
+     *          also set maxExtent, maxResolution or resolutions.  Default is "EPSG:4326".
+     *          Note that the projection of the map is usually determined
+     *          by that of the current baseLayer (see <baseLayer> and <getProjectionObject>).
+     */
+    projection: "EPSG:4326",    
+        
+    /**
+     * APIProperty: units
+     * {String} The map units.  Possible values are 'degrees' (or 'dd'), 'm', 
+     *     'ft', 'km', 'mi', 'inches'.  Normally taken from the projection.
+     *     Only required if both map and layers do not define a projection,
+     *     or if they define a projection which does not define units
+     */
+    units: null,
+
+    /**
+     * APIProperty: resolutions
+     * {Array(Float)} A list of map resolutions (map units per pixel) in 
+     *     descending order.  If this is not set in the layer constructor, it 
+     *     will be set based on other resolution related properties 
+     *     (maxExtent, maxResolution, maxScale, etc.).
+     */
+    resolutions: null,
+
+    /**
+     * APIProperty: maxResolution
+     * {Float} Required if you are not displaying the whole world on a tile
+     * with the size specified in <tileSize>.
+     */
+    maxResolution: null,
+
+    /**
+     * APIProperty: minResolution
+     * {Float}
+     */
+    minResolution: null,
+
+    /**
+     * APIProperty: maxScale
+     * {Float}
+     */
+    maxScale: null,
+
+    /**
+     * APIProperty: minScale
+     * {Float}
+     */
+    minScale: null,
+
+    /**
+     * APIProperty: maxExtent
+     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+     *     should consist of four values (left, bottom, right, top).
+     *     The maximum extent for the map.
+     *     Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults
+     *     (EPSG:4326 or web mercator), maxExtent will be set to the value defined there;
+     *     else, defaults to null.
+     *     To restrict user panning and zooming of the map, use <restrictedExtent> instead.
+     *     The value for <maxExtent> will change calculations for tile URLs.
+     */
+    maxExtent: null,
+    
+    /**
+     * APIProperty: minExtent
+     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+     *     should consist of four values (left, bottom, right, top).
+     *     The minimum extent for the map.  Defaults to null.
+     */
+    minExtent: null,
+    
+    /**
+     * APIProperty: restrictedExtent
+     * {<OpenLayers.Bounds>|Array} If provided as an array, the array
+     *     should consist of four values (left, bottom, right, top).
+     *     Limit map navigation to this extent where possible.
+     *     If a non-null restrictedExtent is set, panning will be restricted
+     *     to the given bounds.  In addition, zooming to a resolution that
+     *     displays more than the restricted extent will center the map
+     *     on the restricted extent.  If you wish to limit the zoom level
+     *     or resolution, use maxResolution.
+     */
+    restrictedExtent: null,
+
+    /**
+     * APIProperty: numZoomLevels
+     * {Integer} Number of zoom levels for the map.  Defaults to 16.  Set a
+     *           different value in the map options if needed.
+     */
+    numZoomLevels: 16,
+
+    /**
+     * APIProperty: theme
+     * {String} Relative path to a CSS file from which to load theme styles.
+     *          Specify null in the map options (e.g. {theme: null}) if you 
+     *          want to get cascading style declarations - by putting links to 
+     *          stylesheets or style declarations directly in your page.
+     */
+    theme: null,
+    
+    /** 
+     * APIProperty: displayProjection
+     * {<OpenLayers.Projection>} Requires proj4js support for projections other
+     *     than EPSG:4326 or EPSG:900913/EPSG:3857. Projection used by
+     *     several controls to display data to user. If this property is set,
+     *     it will be set on any control which has a null displayProjection
+     *     property at the time the control is added to the map. 
+     */
+    displayProjection: null,
+
+    /**
+     * APIProperty: tileManager
+     * {<OpenLayers.TileManager>|Object} By default, and if the build contains
+     * TileManager.js, the map will use the TileManager to queue image requests
+     * and to cache tile image elements. To create a map without a TileManager
+     * configure the map with tileManager: null. To create a TileManager with
+     * non-default options, supply the options instead or alternatively supply
+     * an instance of {<OpenLayers.TileManager>}.
+     */
+
+    /**
+     * APIProperty: fallThrough
+     * {Boolean} Should OpenLayers allow events on the map to fall through to
+     *           other elements on the page, or should it swallow them? (#457)
+     *           Default is to swallow.
+     */
+    fallThrough: false,
+
+    /**
+     * APIProperty: autoUpdateSize
+     * {Boolean} Should OpenLayers automatically update the size of the map
+     * when the resize event is fired. Default is true.
+     */
+    autoUpdateSize: true,
+    
+    /**
+     * APIProperty: eventListeners
+     * {Object} If set as an option at construction, the eventListeners
+     *     object will be registered with <OpenLayers.Events.on>.  Object
+     *     structure must be a listeners object as shown in the example for
+     *     the events.on method.
+     */
+    eventListeners: null,
+
+    /**
+     * Property: panTween
+     * {<OpenLayers.Tween>} Animated panning tween object, see panTo()
+     */
+    panTween: null,
+
+    /**
+     * APIProperty: panMethod
+     * {Function} The Easing function to be used for tweening.  Default is
+     * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
+     * animated panning.
+     */
+    panMethod: OpenLayers.Easing.Expo.easeOut,
+    
+    /**
+     * Property: panDuration
+     * {Integer} The number of steps to be passed to the
+     * OpenLayers.Tween.start() method when the map is
+     * panned.
+     * Default is 50.
+     */
+    panDuration: 50,
+    
+    /**
+     * Property: zoomTween
+     * {<OpenLayers.Tween>} Animated zooming tween object, see zoomTo()
+     */
+    zoomTween: null,
+
+    /**
+     * APIProperty: zoomMethod
+     * {Function} The Easing function to be used for tweening.  Default is
+     * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off
+     * animated zooming.
+     */
+    zoomMethod: OpenLayers.Easing.Quad.easeOut,
+    
+    /**
+     * Property: zoomDuration
+     * {Integer} The number of steps to be passed to the
+     * OpenLayers.Tween.start() method when the map is zoomed.
+     * Default is 20.
+     */
+    zoomDuration: 20,
+    
+    /**
+     * Property: paddingForPopups
+     * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent 
+     *     the popup from getting too close to the map border.
+     */
+    paddingForPopups : null,
+    
+    /**
+     * Property: layerContainerOriginPx
+     * {Object} Cached object representing the layer container origin (in pixels).
+     */
+    layerContainerOriginPx: null,
+    
+    /**
+     * Property: minPx
+     * {Object} An object with a 'x' and 'y' values that is the lower
+     *     left of maxExtent in viewport pixel space.
+     *     Used to verify in moveByPx that the new location we're moving to
+     *     is valid. It is also used in the getLonLatFromViewPortPx function
+     *     of Layer.
+     */
+    minPx: null,
+    
+    /**
+     * Property: maxPx
+     * {Object} An object with a 'x' and 'y' values that is the top
+     *     right of maxExtent in viewport pixel space.
+     *     Used to verify in moveByPx that the new location we're moving to
+     *     is valid.
+     */
+    maxPx: null,
+    
+    /**
+     * Constructor: OpenLayers.Map
+     * Constructor for a new OpenLayers.Map instance.  There are two possible
+     *     ways to call the map constructor.  See the examples below.
+     *
+     * Parameters:
+     * div - {DOMElement|String}  The element or id of an element in your page
+     *     that will contain the map.  May be omitted if the <div> option is
+     *     provided or if you intend to call the <render> method later.
+     * options - {Object} Optional object with properties to tag onto the map.
+     *
+     * Valid options (in addition to the listed API properties):
+     * center - {<OpenLayers.LonLat>|Array} The default initial center of the map.
+     *     If provided as array, the first value is the x coordinate,
+     *     and the 2nd value is the y coordinate.
+     *     Only specify if <layers> is provided.
+     *     Note that if an ArgParser/Permalink control is present,
+     *     and the querystring contains coordinates, center will be set
+     *     by that, and this option will be ignored.
+     * zoom - {Number} The initial zoom level for the map. Only specify if
+     *     <layers> is provided.
+     *     Note that if an ArgParser/Permalink control is present,
+     *     and the querystring contains a zoom level, zoom will be set
+     *     by that, and this option will be ignored.
+     * extent - {<OpenLayers.Bounds>|Array} The initial extent of the map.
+     *     If provided as an array, the array should consist of
+     *     four values (left, bottom, right, top).
+     *     Only specify if <center> and <zoom> are not provided.
+     * 
+     * Examples:
+     * (code)
+     * // create a map with default options in an element with the id "map1"
+     * var map = new OpenLayers.Map("map1");
+     *
+     * // create a map with non-default options in an element with id "map2"
+     * var options = {
+     *     projection: "EPSG:3857",
+     *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
+     *     center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)
+     * };
+     * var map = new OpenLayers.Map("map2", options);
+     *
+     * // map with non-default options - same as above but with a single argument,
+     * // a restricted extent, and using arrays for bounds and center
+     * var map = new OpenLayers.Map({
+     *     div: "map_id",
+     *     projection: "EPSG:3857",
+     *     maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],
+     *     restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],
+     *     center: [-12356463.476333, 5621521.4854095]
+     * });
+     *
+     * // create a map without a reference to a container - call render later
+     * var map = new OpenLayers.Map({
+     *     projection: "EPSG:3857",
+     *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)
+     * });
+     * (end)
+     */    
+    initialize: function (div, options) {
+        
+        // If only one argument is provided, check if it is an object.
+        if(arguments.length === 1 && typeof div === "object") {
+            options = div;
+            div = options && options.div;
+        }
+
+        // Simple-type defaults are set in class definition. 
+        //  Now set complex-type defaults 
+        this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
+                                            OpenLayers.Map.TILE_HEIGHT);
+        
+        this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
+
+        this.theme = OpenLayers._getScriptLocation() + 
+                             'theme/default/style.css'; 
+
+        // backup original options
+        this.options = OpenLayers.Util.extend({}, options);
+
+        // now override default options 
+        OpenLayers.Util.extend(this, options);
+        
+        var projCode = this.projection instanceof OpenLayers.Projection ?
+            this.projection.projCode : this.projection;
+        OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);
+        
+        // allow extents and center to be arrays
+        if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {
+            this.maxExtent = new OpenLayers.Bounds(this.maxExtent);
+        }
+        if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {
+            this.minExtent = new OpenLayers.Bounds(this.minExtent);
+        }
+        if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {
+            this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);
+        }
+        if (this.center && !(this.center instanceof OpenLayers.LonLat)) {
+            this.center = new OpenLayers.LonLat(this.center);
+        }
+
+        // initialize layers array
+        this.layers = [];
+
+        this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
+
+        this.div = OpenLayers.Util.getElement(div);
+        if(!this.div) {
+            this.div = document.createElement("div");
+            this.div.style.height = "1px";
+            this.div.style.width = "1px";
+        }
+        
+        OpenLayers.Element.addClass(this.div, 'olMap');
+
+        // the viewPortDiv is the outermost div we modify
+        var id = this.id + "_OpenLayers_ViewPort";
+        this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
+                                                     "relative", null,
+                                                     "hidden");
+        this.viewPortDiv.style.width = "100%";
+        this.viewPortDiv.style.height = "100%";
+        this.viewPortDiv.className = "olMapViewport";
+        this.div.appendChild(this.viewPortDiv);
+
+        this.events = new OpenLayers.Events(
+            this, this.viewPortDiv, null, this.fallThrough, 
+            {includeXY: true}
+        );
+        
+        if (OpenLayers.TileManager && this.tileManager !== null) {
+            if (!(this.tileManager instanceof OpenLayers.TileManager)) {
+                this.tileManager = new OpenLayers.TileManager(this.tileManager);
+            }
+            this.tileManager.addMap(this);
+        }
+
+        // the layerContainerDiv is the one that holds all the layers
+        id = this.id + "_OpenLayers_Container";
+        this.layerContainerDiv = OpenLayers.Util.createDiv(id);
+        this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
+        this.layerContainerOriginPx = {x: 0, y: 0};
+        this.applyTransform();
+        
+        this.viewPortDiv.appendChild(this.layerContainerDiv);
+
+        this.updateSize();
+        if(this.eventListeners instanceof Object) {
+            this.events.on(this.eventListeners);
+        }
+
+        if (this.autoUpdateSize === true) {
+            // updateSize on catching the window's resize
+            // Note that this is ok, as updateSize() does nothing if the 
+            // map's size has not actually changed.
+            this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, 
+                this);
+            OpenLayers.Event.observe(window, 'resize',
+                            this.updateSizeDestroy);
+        }
+        
+        // only append link stylesheet if the theme property is set
+        if(this.theme) {
+            // check existing links for equivalent url
+            var addNode = true;
+            var nodes = document.getElementsByTagName('link');
+            for(var i=0, len=nodes.length; i<len; ++i) {
+                if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
+                                                   this.theme)) {
+                    addNode = false;
+                    break;
+                }
+            }
+            // only add a new node if one with an equivalent url hasn't already
+            // been added
+            if(addNode) {
+                var cssNode = document.createElement('link');
+                cssNode.setAttribute('rel', 'stylesheet');
+                cssNode.setAttribute('type', 'text/css');
+                cssNode.setAttribute('href', this.theme);
+                document.getElementsByTagName('head')[0].appendChild(cssNode);
+            }
+        }
+        
+        if (this.controls == null) { // default controls
+            this.controls = [];
+            if (OpenLayers.Control != null) { // running full or lite?
+                // Navigation or TouchNavigation depending on what is in build
+                if (OpenLayers.Control.Navigation) {
+                    this.controls.push(new OpenLayers.Control.Navigation());
+                } else if (OpenLayers.Control.TouchNavigation) {
+                    this.controls.push(new OpenLayers.Control.TouchNavigation());
+                }
+                if (OpenLayers.Control.Zoom) {
+                    this.controls.push(new OpenLayers.Control.Zoom());
+                } else if (OpenLayers.Control.PanZoom) {
+                    this.controls.push(new OpenLayers.Control.PanZoom());
+                }
+
+                if (OpenLayers.Control.ArgParser) {
+                    this.controls.push(new OpenLayers.Control.ArgParser());
+                }
+                if (OpenLayers.Control.Attribution) {
+                    this.controls.push(new OpenLayers.Control.Attribution());
+                }
+            }
+        }
+
+        for(var i=0, len=this.controls.length; i<len; i++) {
+            this.addControlToMap(this.controls[i]);
+        }
+
+        this.popups = [];
+
+        this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
+        
+
+        // always call map.destroy()
+        OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
+        
+        // add any initial layers
+        if (options && options.layers) {
+            /** 
+             * If you have set options.center, the map center property will be
+             * set at this point.  However, since setCenter has not been called,
+             * addLayers gets confused.  So we delete the map center in this 
+             * case.  Because the check below uses options.center, it will
+             * be properly set below.
+             */
+            delete this.center;
+            delete this.zoom;
+            this.addLayers(options.layers);
+            // set center (and optionally zoom)
+            if (options.center && !this.getCenter()) {
+                // zoom can be undefined here
+                this.setCenter(options.center, options.zoom);
+            }
+        }
+
+        if (this.panMethod) {
+            this.panTween = new OpenLayers.Tween(this.panMethod);
+        }
+        if (this.zoomMethod && this.applyTransform.transform) {
+            this.zoomTween = new OpenLayers.Tween(this.zoomMethod);
+        }
+    },
+
+    /** 
+     * APIMethod: getViewport
+     * Get the DOMElement representing the view port.
+     *
+     * Returns:
+     * {DOMElement}
+     */
+    getViewport: function() {
+        return this.viewPortDiv;
+    },
+    
+    /**
+     * APIMethod: render
+     * Render the map to a specified container.
+     * 
+     * Parameters:
+     * div - {String|DOMElement} The container that the map should be rendered
+     *     to. If different than the current container, the map viewport
+     *     will be moved from the current to the new container.
+     */
+    render: function(div) {
+        this.div = OpenLayers.Util.getElement(div);
+        OpenLayers.Element.addClass(this.div, 'olMap');
+        this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+        this.div.appendChild(this.viewPortDiv);
+        this.updateSize();
+    },
+
+    /**
+     * Method: unloadDestroy
+     * Function that is called to destroy the map on page unload. stored here
+     *     so that if map is manually destroyed, we can unregister this.
+     */
+    unloadDestroy: null,
+    
+    /**
+     * Method: updateSizeDestroy
+     * When the map is destroyed, we need to stop listening to updateSize
+     *    events: this method stores the function we need to unregister in 
+     *    non-IE browsers.
+     */
+    updateSizeDestroy: null,
+
+    /**
+     * APIMethod: destroy
+     * Destroy this map.
+     *    Note that if you are using an application which removes a container
+     *    of the map from the DOM, you need to ensure that you destroy the
+     *    map *before* this happens; otherwise, the page unload handler
+     *    will fail because the DOM elements that map.destroy() wants
+     *    to clean up will be gone. (See 
+     *    http://trac.osgeo.org/openlayers/ticket/2277 for more information).
+     *    This will apply to GeoExt and also to other applications which
+     *    modify the DOM of the container of the OpenLayers Map.
+     */
+    destroy:function() {
+        // if unloadDestroy is null, we've already been destroyed
+        if (!this.unloadDestroy) {
+            return false;
+        }
+        
+        // make sure panning doesn't continue after destruction
+        if(this.panTween) {
+            this.panTween.stop();
+            this.panTween = null;
+        }
+        // make sure zooming doesn't continue after destruction
+        if(this.zoomTween) {
+            this.zoomTween.stop();
+            this.zoomTween = null;
+        }
+
+        // map has been destroyed. dont do it again!
+        OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
+        this.unloadDestroy = null;
+
+        if (this.updateSizeDestroy) {
+            OpenLayers.Event.stopObserving(window, 'resize', 
+                                           this.updateSizeDestroy);
+        }
+        
+        this.paddingForPopups = null;    
+
+        if (this.controls != null) {
+            for (var i = this.controls.length - 1; i>=0; --i) {
+                this.controls[i].destroy();
+            } 
+            this.controls = null;
+        }
+        if (this.layers != null) {
+            for (var i = this.layers.length - 1; i>=0; --i) {
+                //pass 'false' to destroy so that map wont try to set a new 
+                // baselayer after each baselayer is removed
+                this.layers[i].destroy(false);
+            } 
+            this.layers = null;
+        }
+        if (this.viewPortDiv && this.viewPortDiv.parentNode) {
+            this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
+        }
+        this.viewPortDiv = null;
+        
+        if (this.tileManager) {
+            this.tileManager.removeMap(this);
+            this.tileManager = null;
+        }
+
+        if(this.eventListeners) {
+            this.events.un(this.eventListeners);
+            this.eventListeners = null;
+        }
+        this.events.destroy();
+        this.events = null;
+
+        this.options = null;
+    },
+
+    /**
+     * APIMethod: setOptions
+     * Change the map options
+     *
+     * Parameters:
+     * options - {Object} Hashtable of options to tag to the map
+     */
+    setOptions: function(options) {
+        var updatePxExtent = this.minPx &&
+            options.restrictedExtent != this.restrictedExtent;
+        OpenLayers.Util.extend(this, options);
+        // force recalculation of minPx and maxPx
+        updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {
+            forceZoomChange: true
+        });
+    },
+
+    /**
+     * APIMethod: getTileSize
+     * Get the tile size for the map
+     *
+     * Returns:
+     * {<OpenLayers.Size>}
+     */
+     getTileSize: function() {
+         return this.tileSize;
+     },
+
+
+    /**
+     * APIMethod: getBy
+     * Get a list of objects given a property and a match item.
+     *
+     * Parameters:
+     * array - {String} A property on the map whose value is an array.
+     * property - {String} A property on each item of the given array.
+     * match - {String | Object} A string to match.  Can also be a regular
+     *     expression literal or object.  In addition, it can be any object
+     *     with a method named test.  For reqular expressions or other, if
+     *     match.test(map[array][i][property]) evaluates to true, the item will
+     *     be included in the array returned.  If no items are found, an empty
+     *     array is returned.
+     *
+     * Returns:
+     * {Array} An array of items where the given property matches the given
+     *     criteria.
+     */
+    getBy: function(array, property, match) {
+        var test = (typeof match.test == "function");
+        var found = OpenLayers.Array.filter(this[array], function(item) {
+            return item[property] == match || (test && match.test(item[property]));
+        });
+        return found;
+    },
+
+    /**
+     * APIMethod: getLayersBy
+     * Get a list of layers with properties matching the given criteria.
+     *
+     * Parameters:
+     * property - {String} A layer property to be matched.
+     * match - {String | Object} A string to match.  Can also be a regular
+     *     expression literal or object.  In addition, it can be any object
+     *     with a method named test.  For reqular expressions or other, if
+     *     match.test(layer[property]) evaluates to true, the layer will be
+     *     included in the array returned.  If no layers are found, an empty
+     *     array is returned.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
+     *     An empty array is returned if no matches are found.
+     */
+    getLayersBy: function(property, match) {
+        return this.getBy("layers", property, match);
+    },
+
+    /**
+     * APIMethod: getLayersByName
+     * Get a list of layers with names matching the given name.
+     *
+     * Parameters:
+     * match - {String | Object} A layer name.  The name can also be a regular
+     *     expression literal or object.  In addition, it can be any object
+     *     with a method named test.  For reqular expressions or other, if
+     *     name.test(layer.name) evaluates to true, the layer will be included
+     *     in the list of layers returned.  If no layers are found, an empty
+     *     array is returned.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
+     *     An empty array is returned if no matches are found.
+     */
+    getLayersByName: function(match) {
+        return this.getLayersBy("name", match);
+    },
+
+    /**
+     * APIMethod: getLayersByClass
+     * Get a list of layers of a given class (CLASS_NAME).
+     *
+     * Parameters:
+     * match - {String | Object} A layer class name.  The match can also be a
+     *     regular expression literal or object.  In addition, it can be any
+     *     object with a method named test.  For reqular expressions or other,
+     *     if type.test(layer.CLASS_NAME) evaluates to true, the layer will
+     *     be included in the list of layers returned.  If no layers are
+     *     found, an empty array is returned.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
+     *     An empty array is returned if no matches are found.
+     */
+    getLayersByClass: function(match) {
+        return this.getLayersBy("CLASS_NAME", match);
+    },
+
+    /**
+     * APIMethod: getControlsBy
+     * Get a list of controls with properties matching the given criteria.
+     *
+     * Parameters:
+     * property - {String} A control property to be matched.
+     * match - {String | Object} A string to match.  Can also be a regular
+     *     expression literal or object.  In addition, it can be any object
+     *     with a method named test.  For reqular expressions or other, if
+     *     match.test(layer[property]) evaluates to true, the layer will be
+     *     included in the array returned.  If no layers are found, an empty
+     *     array is returned.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Control>)} A list of controls matching the given
+     *     criteria.  An empty array is returned if no matches are found.
+     */
+    getControlsBy: function(property, match) {
+        return this.getBy("controls", property, match);
+    },
+
+    /**
+     * APIMethod: getControlsByClass
+     * Get a list of controls of a given class (CLASS_NAME).
+     *
+     * Parameters:
+     * match - {String | Object} A control class name.  The match can also be a
+     *     regular expression literal or object.  In addition, it can be any
+     *     object with a method named test.  For reqular expressions or other,
+     *     if type.test(control.CLASS_NAME) evaluates to true, the control will
+     *     be included in the list of controls returned.  If no controls are
+     *     found, an empty array is returned.
+     *
+     * Returns:
+     * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
+     *     An empty array is returned if no matches are found.
+     */
+    getControlsByClass: function(match) {
+        return this.getControlsBy("CLASS_NAME", match);
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*                  Layer Functions                     */
+  /*                                                      */
+  /*     The following functions deal with adding and     */
+  /*        removing Layers to and from the Map           */
+  /*                                                      */
+  /********************************************************/         
+
+    /**
+     * APIMethod: getLayer
+     * Get a layer based on its id
+     *
+     * Parameters:
+     * id - {String} A layer id
+     *
+     * Returns:
+     * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's 
+     *                      layer collection, or null if not found.
+     */
+    getLayer: function(id) {
+        var foundLayer = null;
+        for (var i=0, len=this.layers.length; i<len; i++) {
+            var layer = this.layers[i];
+            if (layer.id == id) {
+                foundLayer = layer;
+                break;
+            }
+        }
+        return foundLayer;
+    },
+
+    /**
+    * Method: setLayerZIndex
+    * 
+    * Parameters:
+    * layer - {<OpenLayers.Layer>} 
+    * zIdx - {int} 
+    */    
+    setLayerZIndex: function (layer, zIdx) {
+        layer.setZIndex(
+            this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+            + zIdx * 5 );
+    },
+
+    /**
+     * Method: resetLayersZIndex
+     * Reset each layer's z-index based on layer's array index
+     */
+    resetLayersZIndex: function() {
+        for (var i=0, len=this.layers.length; i<len; i++) {
+            var layer = this.layers[i];
+            this.setLayerZIndex(layer, i);
+        }
+    },
+
+    /**
+    * APIMethod: addLayer
+    *
+    * Parameters:
+    * layer - {<OpenLayers.Layer>} 
+    *
+    * Returns:
+    * {Boolean} True if the layer has been added to the map.
+    */    
+    addLayer: function (layer) {
+        for(var i = 0, len = this.layers.length; i < len; i++) {
+            if (this.layers[i] == layer) {
+                return false;
+            }
+        }
+        if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) {
+            return false;
+        }
+        if(this.allOverlays) {
+            layer.isBaseLayer = false;
+        }
+        
+        layer.div.className = "olLayerDiv";
+        layer.div.style.overflow = "";
+        this.setLayerZIndex(layer, this.layers.length);
+
+        if (layer.isFixed) {
+            this.viewPortDiv.appendChild(layer.div);
+        } else {
+            this.layerContainerDiv.appendChild(layer.div);
+        }
+        this.layers.push(layer);
+        layer.setMap(this);
+
+        if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer))  {
+            if (this.baseLayer == null) {
+                // set the first baselaye we add as the baselayer
+                this.setBaseLayer(layer);
+            } else {
+                layer.setVisibility(false);
+            }
+        } else {
+            layer.redraw();
+        }
+
+        this.events.triggerEvent("addlayer", {layer: layer});
+        layer.events.triggerEvent("added", {map: this, layer: layer});
+        layer.afterAdd();
+
+        return true;
+    },
+
+    /**
+    * APIMethod: addLayers 
+    *
+    * Parameters:
+    * layers - {Array(<OpenLayers.Layer>)} 
+    */    
+    addLayers: function (layers) {
+        for (var i=0, len=layers.length; i<len; i++) {
+            this.addLayer(layers[i]);
+        }
+    },
+
+    /** 
+     * APIMethod: removeLayer
+     * Removes a layer from the map by removing its visual element (the 
+     *   layer.div property), then removing it from the map's internal list 
+     *   of layers, setting the layer's map property to null. 
+     * 
+     *   a "removelayer" event is triggered.
+     * 
+     *   very worthy of mention is that simply removing a layer from a map
+     *   will not cause the removal of any popups which may have been created
+     *   by the layer. this is due to the fact that it was decided at some
+     *   point that popups would not belong to layers. thus there is no way 
+     *   for us to know here to which layer the popup belongs.
+     *    
+     *     A simple solution to this is simply to call destroy() on the layer.
+     *     the default OpenLayers.Layer class's destroy() function
+     *     automatically takes care to remove itself from whatever map it has
+     *     been attached to. 
+     * 
+     *     The correct solution is for the layer itself to register an 
+     *     event-handler on "removelayer" and when it is called, if it 
+     *     recognizes itself as the layer being removed, then it cycles through
+     *     its own personal list of popups, removing them from the map.
+     * 
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * setNewBaseLayer - {Boolean} Default is true
+     */
+    removeLayer: function(layer, setNewBaseLayer) {
+        if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) {
+            return;
+        }
+        if (setNewBaseLayer == null) {
+            setNewBaseLayer = true;
+        }
+
+        if (layer.isFixed) {
+            this.viewPortDiv.removeChild(layer.div);
+        } else {
+            this.layerContainerDiv.removeChild(layer.div);
+        }
+        OpenLayers.Util.removeItem(this.layers, layer);
+        layer.removeMap(this);
+        layer.map = null;
+
+        // if we removed the base layer, need to set a new one
+        if(this.baseLayer == layer) {
+            this.baseLayer = null;
+            if(setNewBaseLayer) {
+                for(var i=0, len=this.layers.length; i<len; i++) {
+                    var iLayer = this.layers[i];
+                    if (iLayer.isBaseLayer || this.allOverlays) {
+                        this.setBaseLayer(iLayer);
+                        break;
+                    }
+                }
+            }
+        }
+
+        this.resetLayersZIndex();
+
+        this.events.triggerEvent("removelayer", {layer: layer});
+        layer.events.triggerEvent("removed", {map: this, layer: layer});
+    },
+
+    /**
+     * APIMethod: getNumLayers
+     * 
+     * Returns:
+     * {Int} The number of layers attached to the map.
+     */
+    getNumLayers: function () {
+        return this.layers.length;
+    },
+
+    /** 
+     * APIMethod: getLayerIndex
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>}
+     *
+     * Returns:
+     * {Integer} The current (zero-based) index of the given layer in the map's
+     *           layer stack. Returns -1 if the layer isn't on the map.
+     */
+    getLayerIndex: function (layer) {
+        return OpenLayers.Util.indexOf(this.layers, layer);
+    },
+    
+    /** 
+     * APIMethod: setLayerIndex
+     * Move the given layer to the specified (zero-based) index in the layer
+     *     list, changing its z-index in the map display. Use
+     *     map.getLayerIndex() to find out the current index of a layer. Note
+     *     that this cannot (or at least should not) be effectively used to
+     *     raise base layers above overlays.
+     *
+     * Parameters:
+     * layer - {<OpenLayers.Layer>} 
+     * idx - {int} 
+     */
+    setLayerIndex: function (layer, idx) {
+        var base = this.getLayerIndex(layer);
+        if (idx < 0) {
+            idx = 0;
+        } else if (idx > this.layers.length) {
+            idx = this.layers.length;
+        }
+        if (base != idx) {
+            this.layers.splice(base, 1);
+            this.layers.splice(idx, 0, layer);
+            for (var i=0, len=this.layers.length; i<len; i++) {
+                this.setLayerZIndex(this.layers[i], i);
+            }
+            this.events.triggerEvent("changelayer", {
+                layer: layer, property: "order"
+            });
+            if(this.allOverlays) {
+                if(idx === 0) {
+                    this.setBaseLayer(layer);
+                } else if(this.baseLayer !== this.layers[0]) {
+                    this.setBaseLayer(this.layers[0]);
+                }
+            }
+        }
+    },
+
+    /** 
+     * APIMethod: raiseLayer
+     * Change the index of the given layer by delta. If delta is positive, 
+     *     the layer is moved up the map's layer stack; if delta is negative,
+     *     the layer is moved down.  Again, note that this cannot (or at least
+     *     should not) be effectively used to raise base layers above overlays.
+     *
+     * Paremeters:
+     * layer - {<OpenLayers.Layer>} 
+     * delta - {int} 
+     */
+    raiseLayer: function (layer, delta) {
+        var idx = this.getLayerIndex(layer) + delta;
+        this.setLayerIndex(layer, idx);
+    },
+    
+    /** 
+     * APIMethod: setBaseLayer
+     * Allows user to specify one of the currently-loaded layers as the Map's
+     *     new base layer.
+     * 
+     * Parameters:
+     * newBaseLayer - {<OpenLayers.Layer>}
+     */
+    setBaseLayer: function(newBaseLayer) {
+        
+        if (newBaseLayer != this.baseLayer) {
+          
+            // ensure newBaseLayer is already loaded
+            if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
+
+                // preserve center and scale when changing base layers
+                var center = this.getCachedCenter();
+                var newResolution = OpenLayers.Util.getResolutionFromScale(
+                    this.getScale(), newBaseLayer.units
+                );
+
+                // make the old base layer invisible 
+                if (this.baseLayer != null && !this.allOverlays) {
+                    this.baseLayer.setVisibility(false);
+                }
+
+                // set new baselayer
+                this.baseLayer = newBaseLayer;
+                
+                if(!this.allOverlays || this.baseLayer.visibility) {
+                    this.baseLayer.setVisibility(true);
+                    // Layer may previously have been visible but not in range.
+                    // In this case we need to redraw it to make it visible.
+                    if (this.baseLayer.inRange === false) {
+                        this.baseLayer.redraw();
+                    }
+                }
+
+                // recenter the map
+                if (center != null) {
+                    // new zoom level derived from old scale
+                    var newZoom = this.getZoomForResolution(
+                        newResolution || this.resolution, true
+                    );
+                    // zoom and force zoom change
+                    this.setCenter(center, newZoom, false, true);
+                }
+
+                this.events.triggerEvent("changebaselayer", {
+                    layer: this.baseLayer
+                });
+            }        
+        }
+    },
+
+
+  /********************************************************/
+  /*                                                      */
+  /*                 Control Functions                    */
+  /*                                                      */
+  /*     The following functions deal with adding and     */
+  /*        removing Controls to and from the Map         */
+  /*                                                      */
+  /********************************************************/         
+
+    /**
+     * APIMethod: addControl
+     * Add the passed over control to the map. Optionally 
+     *     position the control at the given pixel.
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>}
+     * px - {<OpenLayers.Pixel>}
+     */    
+    addControl: function (control, px) {
+        this.controls.push(control);
+        this.addControlToMap(control, px);
+    },
+    
+    /**
+     * APIMethod: addControls
+     * Add all of the passed over controls to the map. 
+     *     You can pass over an optional second array
+     *     with pixel-objects to position the controls.
+     *     The indices of the two arrays should match and
+     *     you can add null as pixel for those controls 
+     *     you want to be autopositioned.   
+     *     
+     * Parameters:
+     * controls - {Array(<OpenLayers.Control>)}
+     * pixels - {Array(<OpenLayers.Pixel>)}
+     */    
+    addControls: function (controls, pixels) {
+        var pxs = (arguments.length === 1) ? [] : pixels;
+        for (var i=0, len=controls.length; i<len; i++) {
+            var ctrl = controls[i];
+            var px = (pxs[i]) ? pxs[i] : null;
+            this.addControl( ctrl, px );
+        }
+    },
+
+    /**
+     * Method: addControlToMap
+     * 
+     * Parameters:
+     * 
+     * control - {<OpenLayers.Control>}
+     * px - {<OpenLayers.Pixel>}
+     */    
+    addControlToMap: function (control, px) {
+        // If a control doesn't have a div at this point, it belongs in the
+        // viewport.
+        control.outsideViewport = (control.div != null);
+        
+        // If the map has a displayProjection, and the control doesn't, set 
+        // the display projection.
+        if (this.displayProjection && !control.displayProjection) {
+            control.displayProjection = this.displayProjection;
+        }    
+        
+        control.setMap(this);
+        var div = control.draw(px);
+        if (div) {
+            if(!control.outsideViewport) {
+                div.style.zIndex = this.Z_INDEX_BASE['Control'] +
+                                    this.controls.length;
+                this.viewPortDiv.appendChild( div );
+            }
+        }
+        if(control.autoActivate) {
+            control.activate();
+        }
+    },
+    
+    /**
+     * APIMethod: getControl
+     * 
+     * Parameters:
+     * id - {String} ID of the control to return.
+     * 
+     * Returns:
+     * {<OpenLayers.Control>} The control from the map's list of controls 
+     *                        which has a matching 'id'. If none found, 
+     *                        returns null.
+     */    
+    getControl: function (id) {
+        var returnControl = null;
+        for(var i=0, len=this.controls.length; i<len; i++) {
+            var control = this.controls[i];
+            if (control.id == id) {
+                returnControl = control;
+                break;
+            }
+        }
+        return returnControl;
+    },
+    
+    /** 
+     * APIMethod: removeControl
+     * Remove a control from the map. Removes the control both from the map 
+     *     object's internal array of controls, as well as from the map's 
+     *     viewPort (assuming the control was not added outsideViewport)
+     * 
+     * Parameters:
+     * control - {<OpenLayers.Control>} The control to remove.
+     */    
+    removeControl: function (control) {
+        //make sure control is non-null and actually part of our map
+        if ( (control) && (control == this.getControl(control.id)) ) {
+            if (control.div && (control.div.parentNode == this.viewPortDiv)) {
+                this.viewPortDiv.removeChild(control.div);
+            }
+            OpenLayers.Util.removeItem(this.controls, control);
+        }
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*                  Popup Functions                     */
+  /*                                                      */
+  /*     The following functions deal with adding and     */
+  /*        removing Popups to and from the Map           */
+  /*                                                      */
+  /********************************************************/         
+
+    /** 
+     * APIMethod: addPopup
+     * 
+     * Parameters:
+     * popup - {<OpenLayers.Popup>}
+     * exclusive - {Boolean} If true, closes all other popups first
+     */
+    addPopup: function(popup, exclusive) {
+
+        if (exclusive) {
+            //remove all other popups from screen
+            for (var i = this.popups.length - 1; i >= 0; --i) {
+                this.removePopup(this.popups[i]);
+            }
+        }
+
+        popup.map = this;
+        this.popups.push(popup);
+        var popupDiv = popup.draw();
+        if (popupDiv) {
+            popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
+                                    this.popups.length;
+            this.layerContainerDiv.appendChild(popupDiv);
+        }
+    },
+    
+    /** 
+    * APIMethod: removePopup
+    * 
+    * Parameters:
+    * popup - {<OpenLayers.Popup>}
+    */
+    removePopup: function(popup) {
+        OpenLayers.Util.removeItem(this.popups, popup);
+        if (popup.div) {
+            try { this.layerContainerDiv.removeChild(popup.div); }
+            catch (e) { } // Popups sometimes apparently get disconnected
+                      // from the layerContainerDiv, and cause complaints.
+        }
+        popup.map = null;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*              Container Div Functions                 */
+  /*                                                      */
+  /*   The following functions deal with the access to    */
+  /*    and maintenance of the size of the container div  */
+  /*                                                      */
+  /********************************************************/     
+
+    /**
+     * APIMethod: getSize
+     * 
+     * Returns:
+     * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the 
+     *                     size, in pixels, of the div into which OpenLayers 
+     *                     has been loaded. 
+     *                     Note - A clone() of this locally cached variable is
+     *                     returned, so as not to allow users to modify it.
+     */
+    getSize: function () {
+        var size = null;
+        if (this.size != null) {
+            size = this.size.clone();
+        }
+        return size;
+    },
+
+    /**
+     * APIMethod: updateSize
+     * This function should be called by any external code which dynamically
+     *     changes the size of the map div (because mozilla wont let us catch 
+     *     the "onresize" for an element)
+     */
+    updateSize: function() {
+        // the div might have moved on the page, also
+        var newSize = this.getCurrentSize();
+        if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {
+            this.events.clearMouseCache();
+            var oldSize = this.getSize();
+            if (oldSize == null) {
+                this.size = oldSize = newSize;
+            }
+            if (!newSize.equals(oldSize)) {
+                
+                // store the new size
+                this.size = newSize;
+    
+                //notify layers of mapresize
+                for(var i=0, len=this.layers.length; i<len; i++) {
+                    this.layers[i].onMapResize();                
+                }
+    
+                var center = this.getCachedCenter();
+    
+                if (this.baseLayer != null && center != null) {
+                    var zoom = this.getZoom();
+                    this.zoom = null;
+                    this.setCenter(center, zoom);
+                }
+    
+            }
+        }
+        this.events.triggerEvent("updatesize");
+    },
+    
+    /**
+     * Method: getCurrentSize
+     * 
+     * Returns:
+     * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions 
+     *                     of the map div
+     */
+    getCurrentSize: function() {
+
+        var size = new OpenLayers.Size(this.div.clientWidth, 
+                                       this.div.clientHeight);
+
+        if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+            size.w = this.div.offsetWidth;
+            size.h = this.div.offsetHeight;
+        }
+        if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
+            size.w = parseInt(this.div.style.width);
+            size.h = parseInt(this.div.style.height);
+        }
+        return size;
+    },
+
+    /** 
+     * Method: calculateBounds
+     * 
+     * Parameters:
+     * center - {<OpenLayers.LonLat>} Default is this.getCenter()
+     * resolution - {float} Default is this.getResolution() 
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} A bounds based on resolution, center, and 
+     *                       current mapsize.
+     */
+    calculateBounds: function(center, resolution) {
+
+        var extent = null;
+        
+        if (center == null) {
+            center = this.getCachedCenter();
+        }                
+        if (resolution == null) {
+            resolution = this.getResolution();
+        }
+    
+        if ((center != null) && (resolution != null)) {
+            var halfWDeg = (this.size.w * resolution) / 2;
+            var halfHDeg = (this.size.h * resolution) / 2;
+        
+            extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+                                           center.lat - halfHDeg,
+                                           center.lon + halfWDeg,
+                                           center.lat + halfHDeg);
+        }
+
+        return extent;
+    },
+
+
+  /********************************************************/
+  /*                                                      */
+  /*            Zoom, Center, Pan Functions               */
+  /*                                                      */
+  /*    The following functions handle the validation,    */
+  /*   getting and setting of the Zoom Level and Center   */
+  /*       as well as the panning of the Map              */
+  /*                                                      */
+  /********************************************************/
+    /**
+     * APIMethod: getCenter
+     * 
+     * Returns:
+     * {<OpenLayers.LonLat>}
+     */
+    getCenter: function () {
+        var center = null;
+        var cachedCenter = this.getCachedCenter();
+        if (cachedCenter) {
+            center = cachedCenter.clone();
+        }
+        return center;
+    },
+
+    /**
+     * Method: getCachedCenter
+     *
+     * Returns:
+     * {<OpenLayers.LonLat>}
+     */
+    getCachedCenter: function() {
+        if (!this.center && this.size) {
+            this.center = this.getLonLatFromViewPortPx({
+                x: this.size.w / 2,
+                y: this.size.h / 2
+            });
+        }
+        return this.center;
+    },
+
+    /**
+     * APIMethod: getZoom
+     * 
+     * Returns:
+     * {Integer}
+     */
+    getZoom: function () {
+        return this.zoom;
+    },
+    
+    /** 
+     * APIMethod: pan
+     * Allows user to pan by a value of screen pixels
+     * 
+     * Parameters:
+     * dx - {Integer}
+     * dy - {Integer}
+     * options - {Object} Options to configure panning:
+     *  - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
+     *  - *dragging* {Boolean} Call setCenter with dragging true.  Default is
+     *    false.
+     */
+    pan: function(dx, dy, options) {
+        options = OpenLayers.Util.applyDefaults(options, {
+            animate: true,
+            dragging: false
+        });
+        if (options.dragging) {
+            if (dx != 0 || dy != 0) {
+                this.moveByPx(dx, dy);
+            }
+        } else {
+            // getCenter
+            var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());
+
+            // adjust
+            var newCenterPx = centerPx.add(dx, dy);
+
+            if (this.dragging || !newCenterPx.equals(centerPx)) {
+                var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
+                if (options.animate) {
+                    this.panTo(newCenterLonLat);
+                } else {
+                    this.moveTo(newCenterLonLat);
+                    if(this.dragging) {
+                        this.dragging = false;
+                        this.events.triggerEvent("moveend");
+                    }
+                }    
+            }
+        }        
+
+   },
+   
+   /** 
+     * APIMethod: panTo
+     * Allows user to pan to a new lonlat
+     * If the new lonlat is in the current extent the map will slide smoothly
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     */
+    panTo: function(lonlat) {
+        if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
+            var center = this.getCachedCenter();
+
+            // center will not change, don't do nothing
+            if (lonlat.equals(center)) {
+                return;
+            }
+
+            var from = this.getPixelFromLonLat(center);
+            var to = this.getPixelFromLonLat(lonlat);
+            var vector = { x: to.x - from.x, y: to.y - from.y };
+            var last = { x: 0, y: 0 };
+
+            this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, {
+                callbacks: {
+                    eachStep: OpenLayers.Function.bind(function(px) {
+                        var x = px.x - last.x,
+                            y = px.y - last.y;
+                        this.moveByPx(x, y);
+                        last.x = Math.round(px.x);
+                        last.y = Math.round(px.y);
+                    }, this),
+                    done: OpenLayers.Function.bind(function(px) {
+                        this.moveTo(lonlat);
+                        this.dragging = false;
+                        this.events.triggerEvent("moveend");
+                    }, this)
+                }
+            });
+        } else {
+            this.setCenter(lonlat);
+        }
+    },
+
+    /**
+     * APIMethod: setCenter
+     * Set the map center (and optionally, the zoom level).
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>|Array} The new center location.
+     *     If provided as array, the first value is the x coordinate,
+     *     and the 2nd value is the y coordinate.
+     * zoom - {Integer} Optional zoom level.
+     * dragging - {Boolean} Specifies whether or not to trigger 
+     *                      movestart/end events
+     * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom 
+     *                             change events (needed on baseLayer change)
+     *
+     * TBD: reconsider forceZoomChange in 3.0
+     */
+    setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
+        if (this.panTween) {
+            this.panTween.stop();
+        }
+        if (this.zoomTween) {
+            this.zoomTween.stop();
+        }            
+        this.moveTo(lonlat, zoom, {
+            'dragging': dragging,
+            'forceZoomChange': forceZoomChange
+        });
+    },
+    
+    /** 
+     * Method: moveByPx
+     * Drag the map by pixels.
+     *
+     * Parameters:
+     * dx - {Number}
+     * dy - {Number}
+     */
+    moveByPx: function(dx, dy) {
+        var hw = this.size.w / 2;
+        var hh = this.size.h / 2;
+        var x = hw + dx;
+        var y = hh + dy;
+        var wrapDateLine = this.baseLayer.wrapDateLine;
+        var xRestriction = 0;
+        var yRestriction = 0;
+        if (this.restrictedExtent) {
+            xRestriction = hw;
+            yRestriction = hh;
+            // wrapping the date line makes no sense for restricted extents
+            wrapDateLine = false;
+        }
+        dx = wrapDateLine ||
+                    x <= this.maxPx.x - xRestriction &&
+                    x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;
+        dy = y <= this.maxPx.y - yRestriction &&
+                    y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;
+        if (dx || dy) {
+            if (!this.dragging) {
+                this.dragging = true;
+                this.events.triggerEvent("movestart");
+            }
+            this.center = null;
+            if (dx) {
+                this.layerContainerOriginPx.x -= dx;
+                this.minPx.x -= dx;
+                this.maxPx.x -= dx;
+            }
+            if (dy) {
+                this.layerContainerOriginPx.y -= dy;
+                this.minPx.y -= dy;
+                this.maxPx.y -= dy;
+            }
+            this.applyTransform();
+            var layer, i, len;
+            for (i=0, len=this.layers.length; i<len; ++i) {
+                layer = this.layers[i];
+                if (layer.visibility &&
+                    (layer === this.baseLayer || layer.inRange)) {
+                    layer.moveByPx(dx, dy);
+                    layer.events.triggerEvent("move");
+                }
+            }
+            this.events.triggerEvent("move");
+        }
+    },
+    
+    /**
+     * Method: adjustZoom
+     *
+     * Parameters:
+     * zoom - {Number} The zoom level to adjust
+     *
+     * Returns:
+     * {Integer} Adjusted zoom level that shows a map not wider than its
+     * <baseLayer>'s maxExtent.
+     */
+    adjustZoom: function(zoom) {
+        if (this.baseLayer && this.baseLayer.wrapDateLine) {
+            var resolution, resolutions = this.baseLayer.resolutions,
+                maxResolution = this.getMaxExtent().getWidth() / this.size.w;
+            if (this.getResolutionForZoom(zoom) > maxResolution) {
+                if (this.fractionalZoom) {
+                    zoom = this.getZoomForResolution(maxResolution);
+                } else {
+                    for (var i=zoom|0, ii=resolutions.length; i<ii; ++i) {
+                        if (resolutions[i] <= maxResolution) {
+                            zoom = i;
+                            break;
+                        }
+                    }
+                } 
+            }
+        }
+        return zoom;
+    },
+    
+    /**
+     * APIMethod: getMinZoom
+     * Returns the minimum zoom level for the current map view. If the base
+     * layer is configured with <wrapDateLine> set to true, this will be the
+     * first zoom level that shows no more than one world width in the current
+     * map viewport. Components that rely on this value (e.g. zoom sliders)
+     * should also listen to the map's "updatesize" event and call this method
+     * in the "updatesize" listener.
+     *
+     * Returns:
+     * {Number} Minimum zoom level that shows a map not wider than its
+     * <baseLayer>'s maxExtent. This is an Integer value, unless the map is
+     * configured with <fractionalZoom> set to true.
+     */
+    getMinZoom: function() {
+        return this.adjustZoom(0);
+    },
+
+    /**
+     * Method: moveTo
+     *
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     * zoom - {Integer}
+     * options - {Object}
+     */
+    moveTo: function(lonlat, zoom, options) {
+        if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {
+            lonlat = new OpenLayers.LonLat(lonlat);
+        }
+        if (!options) { 
+            options = {};
+        }
+        if (zoom != null) {
+            zoom = parseFloat(zoom);
+            if (!this.fractionalZoom) {
+                zoom = Math.round(zoom);
+            }
+        }
+        var requestedZoom = zoom;
+        zoom = this.adjustZoom(zoom);
+        if (zoom !== requestedZoom) {
+            // zoom was adjusted, so keep old lonlat to avoid panning
+            lonlat = this.getCenter();
+        }
+        // dragging is false by default
+        var dragging = options.dragging || this.dragging;
+        // forceZoomChange is false by default
+        var forceZoomChange = options.forceZoomChange;
+
+        if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {
+            lonlat = this.maxExtent.getCenterLonLat();
+            this.center = lonlat.clone();
+        }
+
+        if(this.restrictedExtent != null) {
+            // In 3.0, decide if we want to change interpretation of maxExtent.
+            if(lonlat == null) { 
+                lonlat = this.center; 
+            }
+            if(zoom == null) { 
+                zoom = this.getZoom(); 
+            }
+            var resolution = this.getResolutionForZoom(zoom);
+            var extent = this.calculateBounds(lonlat, resolution); 
+            if(!this.restrictedExtent.containsBounds(extent)) {
+                var maxCenter = this.restrictedExtent.getCenterLonLat(); 
+                if(extent.getWidth() > this.restrictedExtent.getWidth()) { 
+                    lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); 
+                } else if(extent.left < this.restrictedExtent.left) {
+                    lonlat = lonlat.add(this.restrictedExtent.left -
+                                        extent.left, 0); 
+                } else if(extent.right > this.restrictedExtent.right) { 
+                    lonlat = lonlat.add(this.restrictedExtent.right -
+                                        extent.right, 0); 
+                } 
+                if(extent.getHeight() > this.restrictedExtent.getHeight()) { 
+                    lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); 
+                } else if(extent.bottom < this.restrictedExtent.bottom) { 
+                    lonlat = lonlat.add(0, this.restrictedExtent.bottom -
+                                        extent.bottom); 
+                } 
+                else if(extent.top > this.restrictedExtent.top) { 
+                    lonlat = lonlat.add(0, this.restrictedExtent.top -
+                                        extent.top); 
+                } 
+            }
+        }
+        
+        var zoomChanged = forceZoomChange || (
+                            (this.isValidZoomLevel(zoom)) && 
+                            (zoom != this.getZoom()) );
+
+        var centerChanged = (this.isValidLonLat(lonlat)) && 
+                            (!lonlat.equals(this.center));
+
+        // if neither center nor zoom will change, no need to do anything
+        if (zoomChanged || centerChanged || dragging) {
+            dragging || this.events.triggerEvent("movestart", {
+                zoomChanged: zoomChanged
+            });
+
+            if (centerChanged) {
+                if (!zoomChanged && this.center) { 
+                    // if zoom hasnt changed, just slide layerContainer
+                    //  (must be done before setting this.center to new value)
+                    this.centerLayerContainer(lonlat);
+                }
+                this.center = lonlat.clone();
+            }
+
+            var res = zoomChanged ?
+                this.getResolutionForZoom(zoom) : this.getResolution();
+            // (re)set the layerContainerDiv's location
+            if (zoomChanged || this.layerContainerOrigin == null) {
+                this.layerContainerOrigin = this.getCachedCenter();
+                this.layerContainerOriginPx.x = 0;
+                this.layerContainerOriginPx.y = 0;
+                this.applyTransform();
+                var maxExtent = this.getMaxExtent({restricted: true});
+                var maxExtentCenter = maxExtent.getCenterLonLat();
+                var lonDelta = this.center.lon - maxExtentCenter.lon;
+                var latDelta = maxExtentCenter.lat - this.center.lat;
+                var extentWidth = Math.round(maxExtent.getWidth() / res);
+                var extentHeight = Math.round(maxExtent.getHeight() / res);
+                this.minPx = {
+                    x: (this.size.w - extentWidth) / 2 - lonDelta / res,
+                    y: (this.size.h - extentHeight) / 2 - latDelta / res
+                };
+                this.maxPx = {
+                    x: this.minPx.x + Math.round(maxExtent.getWidth() / res),
+                    y: this.minPx.y + Math.round(maxExtent.getHeight() / res)
+                };
+            }
+
+            if (zoomChanged) {
+                this.zoom = zoom;
+                this.resolution = res;
+            }    
+            
+            var bounds = this.getExtent();
+            
+            //send the move call to the baselayer and all the overlays    
+
+            if(this.baseLayer.visibility) {
+                this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);
+                options.dragging || this.baseLayer.events.triggerEvent(
+                    "moveend", {zoomChanged: zoomChanged}
+                );
+            }
+            
+            bounds = this.baseLayer.getExtent();
+            
+            for (var i=this.layers.length-1; i>=0; --i) {
+                var layer = this.layers[i];
+                if (layer !== this.baseLayer && !layer.isBaseLayer) {
+                    var inRange = layer.calculateInRange();
+                    if (layer.inRange != inRange) {
+                        // the inRange property has changed. If the layer is
+                        // no longer in range, we turn it off right away. If
+                        // the layer is no longer out of range, the moveTo
+                        // call below will turn on the layer.
+                        layer.inRange = inRange;
+                        if (!inRange) {
+                            layer.display(false);
+                        }
+                        this.events.triggerEvent("changelayer", {
+                            layer: layer, property: "visibility"
+                        });
+                    }
+                    if (inRange && layer.visibility) {
+                        layer.moveTo(bounds, zoomChanged, options.dragging);
+                        options.dragging || layer.events.triggerEvent(
+                            "moveend", {zoomChanged: zoomChanged}
+                        );
+                    }
+                }                
+            }
+            
+            this.events.triggerEvent("move");
+            dragging || this.events.triggerEvent("moveend");
+
+            if (zoomChanged) {
+                //redraw popups
+                for (var i=0, len=this.popups.length; i<len; i++) {
+                    this.popups[i].updatePosition();
+                }
+                this.events.triggerEvent("zoomend");
+            }
+        }
+    },
+
+    /** 
+     * Method: centerLayerContainer
+     * This function takes care to recenter the layerContainerDiv.
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     */
+    centerLayerContainer: function (lonlat) {
+        var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
+        var newPx = this.getViewPortPxFromLonLat(lonlat);
+
+        if ((originPx != null) && (newPx != null)) {
+            var oldLeft = this.layerContainerOriginPx.x;
+            var oldTop = this.layerContainerOriginPx.y;
+            var newLeft = Math.round(originPx.x - newPx.x);
+            var newTop = Math.round(originPx.y - newPx.y);
+            this.applyTransform(
+                (this.layerContainerOriginPx.x = newLeft),
+                (this.layerContainerOriginPx.y = newTop));
+            var dx = oldLeft - newLeft;
+            var dy = oldTop - newTop;
+            this.minPx.x -= dx;
+            this.maxPx.x -= dx;
+            this.minPx.y -= dy;
+            this.maxPx.y -= dy;
+        }        
+    },
+
+    /**
+     * Method: isValidZoomLevel
+     * 
+     * Parameters:
+     * zoomLevel - {Integer}
+     * 
+     * Returns:
+     * {Boolean} Whether or not the zoom level passed in is non-null and 
+     *           within the min/max range of zoom levels.
+     */
+    isValidZoomLevel: function(zoomLevel) {
+        return ( (zoomLevel != null) &&
+                 (zoomLevel >= 0) && 
+                 (zoomLevel < this.getNumZoomLevels()) );
+    },
+    
+    /**
+     * Method: isValidLonLat
+     * 
+     * Parameters:
+     * lonlat - {<OpenLayers.LonLat>}
+     * 
+     * Returns:
+     * {Boolean} Whether or not the lonlat passed in is non-null and within
+     *           the maxExtent bounds
+     */
+    isValidLonLat: function(lonlat) {
+        var valid = false;
+        if (lonlat != null) {
+            var maxExtent = this.getMaxExtent();
+            var worldBounds = this.baseLayer.wrapDateLine && maxExtent;
+            valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds});
+        }
+        return valid;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*                 Layer Options                        */
+  /*                                                      */
+  /*    Accessor functions to Layer Options parameters    */
+  /*                                                      */
+  /********************************************************/
+    
+    /**
+     * APIMethod: getProjection
+     * This method returns a string representing the projection. In 
+     *     the case of projection support, this will be the srsCode which
+     *     is loaded -- otherwise it will simply be the string value that
+     *     was passed to the projection at startup.
+     *
+     * FIXME: In 3.0, we will remove getProjectionObject, and instead
+     *     return a Projection object from this function. 
+     * 
+     * Returns:
+     * {String} The Projection string from the base layer or null. 
+     */
+    getProjection: function() {
+        var projection = this.getProjectionObject();
+        return projection ? projection.getCode() : null;
+    },
+    
+    /**
+     * APIMethod: getProjectionObject
+     * Returns the projection obect from the baselayer.
+     *
+     * Returns:
+     * {<OpenLayers.Projection>} The Projection of the base layer.
+     */
+    getProjectionObject: function() {
+        var projection = null;
+        if (this.baseLayer != null) {
+            projection = this.baseLayer.projection;
+        }
+        return projection;
+    },
+    
+    /**
+     * APIMethod: getMaxResolution
+     * 
+     * Returns:
+     * {String} The Map's Maximum Resolution
+     */
+    getMaxResolution: function() {
+        var maxResolution = null;
+        if (this.baseLayer != null) {
+            maxResolution = this.baseLayer.maxResolution;
+        }
+        return maxResolution;
+    },
+        
+    /**
+     * APIMethod: getMaxExtent
+     *
+     * Parameters:
+     * options - {Object} 
+     * 
+     * Allowed Options:
+     * restricted - {Boolean} If true, returns restricted extent (if it is 
+     *     available.)
+     *
+     * Returns:
+     * {<OpenLayers.Bounds>} The maxExtent property as set on the current 
+     *     baselayer, unless the 'restricted' option is set, in which case
+     *     the 'restrictedExtent' option from the map is returned (if it
+     *     is set).
+     */
+    getMaxExtent: function (options) {
+        var maxExtent = null;
+        if(options && options.restricted && this.restrictedExtent){
+            maxExtent = this.restrictedExtent;
+        } else if (this.baseLayer != null) {
+            maxExtent = this.baseLayer.maxExtent;
+        }        
+        return maxExtent;
+    },
+    
+    /**
+     * APIMethod: getNumZoomLevels
+     * 
+     * Returns:
+     * {Integer} The total number of zoom levels that can be displayed by the 
+     *           current baseLayer.
+     */
+    getNumZoomLevels: function() {
+        var numZoomLevels = null;
+        if (this.baseLayer != null) {
+            numZoomLevels = this.baseLayer.numZoomLevels;
+        }
+        return numZoomLevels;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*                 Baselayer Functions                  */
+  /*                                                      */
+  /*    The following functions, all publicly exposed     */
+  /*       in the API?, are all merely wrappers to the    */
+  /*       the same calls on whatever layer is set as     */
+  /*                the current base layer                */
+  /*                                                      */
+  /********************************************************/
+
+    /**
+     * APIMethod: getExtent
+     * 
+     * Returns:
+     * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
+     *                       bounds of the current viewPort. 
+     *                       If no baselayer is set, returns null.
+     */
+    getExtent: function () {
+        var extent = null;
+        if (this.baseLayer != null) {
+            extent = this.baseLayer.getExtent();
+        }
+        return extent;
+    },
+
+    /**
+     * APIMethod: getResolution
+     * 
+     * Returns:
+     * {Float} The current resolution of the map. 
+     *         If no baselayer is set, returns null.
+     */
+    getResolution: function () {
+        var resolution = null;
+        if (this.baseLayer != null) {
+            resolution = this.baseLayer.getResolution();
+        } else if(this.allOverlays === true && this.layers.length > 0) {
+            // while adding the 1st layer to the map in allOverlays mode,
+            // this.baseLayer is not set yet when we need the resolution
+            // for calculateInRange.
+            resolution = this.layers[0].getResolution();
+        }
+        return resolution;
+    },
+
+    /**
+     * APIMethod: getUnits
+     * 
+     * Returns:
+     * {Float} The current units of the map. 
+     *         If no baselayer is set, returns null.
+     */
+    getUnits: function () {
+        var units = null;
+        if (this.baseLayer != null) {
+            units = this.baseLayer.units;
+        }
+        return units;
+    },
+
+     /**
+      * APIMethod: getScale
+      * 
+      * Returns:
+      * {Float} The current scale denominator of the map. 
+      *         If no baselayer is set, returns null.
+      */
+    getScale: function () {
+        var scale = null;
+        if (this.baseLayer != null) {
+            var res = this.getResolution();
+            var units = this.baseLayer.units;
+            scale = OpenLayers.Util.getScaleFromResolution(res, units);
+        }
+        return scale;
+    },
+
+
+    /**
+     * APIMethod: getZoomForExtent
+     * 
+     * Parameters: 
+     * bounds - {<OpenLayers.Bounds>}
+     * closest - {Boolean} Find the zoom level that most closely fits the 
+     *     specified bounds. Note that this may result in a zoom that does 
+     *     not exactly contain the entire extent.
+     *     Default is false.
+     * 
+     * Returns:
+     * {Integer} A suitable zoom level for the specified bounds.
+     *           If no baselayer is set, returns null.
+     */
+    getZoomForExtent: function (bounds, closest) {
+        var zoom = null;
+        if (this.baseLayer != null) {
+            zoom = this.baseLayer.getZoomForExtent(bounds, closest);
+        }
+        return zoom;
+    },
+
+    /**
+     * APIMethod: getResolutionForZoom
+     * 
+     * Parameters:
+     * zoom - {Float}
+     * 
+     * Returns:
+     * {Float} A suitable resolution for the specified zoom.  If no baselayer
+     *     is set, returns null.
+     */
+    getResolutionForZoom: function(zoom) {
+        var resolution = null;
+        if(this.baseLayer) {
+            resolution = this.baseLayer.getResolutionForZoom(zoom);
+        }
+        return resolution;
+    },
+
+    /**
+     * APIMethod: getZoomForResolution
+     * 
+     * Parameters:
+     * resolution - {Float}
+     * closest - {Boolean} Find the zoom level that corresponds to the absolute 
+     *     closest resolution, which may result in a zoom whose corresponding
+     *     resolution is actually smaller than we would have desired (if this
+     *     is being called from a getZoomForExtent() call, then this means that
+     *     the returned zoom index might not actually contain the entire 
+     *     extent specified... but it'll be close).
+     *     Default is false.
+     * 
+     * Returns:
+     * {Integer} A suitable zoom level for the specified resolution.
+     *           If no baselayer is set, returns null.
+     */
+    getZoomForResolution: function(resolution, closest) {
+        var zoom = null;
+        if (this.baseLayer != null) {
+            zoom = this.baseLayer.getZoomForResolution(resolution, closest);
+        }
+        return zoom;
+    },
+
+  /********************************************************/
+  /*                                                      */
+  /*                  Zooming Functions                   */
+  /*                                                      */
+  /*    The following functions, all publicly exposed     */
+  /*       in the API, are all merely wrappers to the     */
+  /*               the setCenter() function               */
+  /*                                                      */
+  /********************************************************/
+  
+    /** 
+     * APIMethod: zoomTo
+     * Zoom to a specific zoom level. Zooming will be animated unless the map
+     * is configured with {zoomMethod: null}. To zoom without animation, use
+     * <setCenter> without a lonlat argument.
+     * 
+     * Parameters:
+     * zoom - {Integer}
+     */
+    zoomTo: function(zoom, xy) {
+        // non-API arguments:
+        // xy - {<OpenLayers.Pixel>} optional zoom origin
+        
+        var map = this;
+        if (map.isValidZoomLevel(zoom)) {
+            if (map.baseLayer.wrapDateLine) {
+                zoom = map.adjustZoom(zoom);
+            }
+            if (map.zoomTween) {
+                var currentRes = map.getResolution(),
+                    targetRes = map.getResolutionForZoom(zoom),
+                    start = {scale: 1},
+                    end = {scale: currentRes / targetRes};
+                if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {
+                    // update the end scale, and reuse the running zoomTween
+                    map.zoomTween.finish = {
+                        scale: map.zoomTween.finish.scale * end.scale
+                    };
+                } else {
+                    if (!xy) {
+                        var size = map.getSize();
+                        xy = {x: size.w / 2, y: size.h / 2};
+                    }
+                    map.zoomTween.start(start, end, map.zoomDuration, {
+                        minFrameRate: 50, // don't spend much time zooming
+                        callbacks: {
+                            eachStep: function(data) {
+                                var containerOrigin = map.layerContainerOriginPx,
+                                    scale = data.scale,
+                                    dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,
+                                    dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;
+                                map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);
+                            },
+                            done: function(data) {
+                                map.applyTransform();
+                                var resolution = map.getResolution() / data.scale,
+                                    zoom = map.getZoomForResolution(resolution, true)
+                                map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);
+                            }
+                        }
+                    });
+                }
+            } else {
+                var center = xy ?
+                    map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :
+                    null;
+                map.setCenter(center, zoom);
+            }
+        }
+    },
+        
+    /**
+     * APIMethod: zoomIn
+     * 
+     */
+    zoomIn: function() {
+        this.zoomTo(this.getZoom() + 1);
+    },
+    
+    /**
+     * APIMethod: zoomOut
+     * 
+     */
+    zoomOut: function() {
+        this.zoomTo(this.getZoom() - 1);
+    },
+
+    /**
+     * APIMethod: zoomToExtent
+     * Zoom to the passed in bounds, recenter
+     * 
+     * Parameters:
+     * bounds - {<OpenLayers.Bounds>|Array} If provided as an array, the array
+     *     should consist of four values (left, bottom, right, top).
+     * closest - {Boolean} Find the zoom level that most closely fits the 
+     *     specified bounds. Note that this may result in a zoom that does 
+     *     not exactly contain the entire extent.
+     *     Default is false.
+     * 
+     */
+    zoomToExtent: function(bounds, closest) {
+        if (!(bounds instanceof OpenLayers.Bounds)) {
+            bounds = new OpenLayers.Bounds(bounds);
+        }
+        var center = bounds.getCenterLonLat();
+        if (this.baseLayer.wrapDateLine) {
+            var maxExtent = this.getMaxExtent();
+
+            //fix straddling bounds (in the case of a bbox that straddles the 
+            // dateline, it's left and right boundaries will appear backwards. 
+            // we fix this by allowing a right value that is greater than the
+            // max value at the dateline -- this allows us to pass a valid 
+            // bounds to calculate zoom)
+            //
+            bounds = bounds.clone();
+            while (bounds.right < bounds.left) {
+                bounds.right += maxExtent.getWidth();
+            }
+            //if the bounds was straddling (see above), then the center point 
+            // we got from it was wrong. So we take our new bounds and ask it
+            // for the center.
+            //
+            center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
+        }
+        this.setCenter(center, this.getZoomForExtent(bounds, closest));
+    },
+
+    /** 
+     * APIMethod: zoomToMaxExtent
+     * Zoom to the full extent and recenter.
+     *
+     * Parameters:
+     * options - {Object}
+     * 
+     * Allowed Options:
+     * restricted - {Boolean} True to zoom to restricted extent if it is 
+     *     set. Defaults to true.
+     */
+    zoomToMaxExtent: function(options) {
+        //restricted is true by default
+        var restricted = (options) ? options.restricted : true;
+
+        var maxExtent = this.getMaxExtent({
+            'restricted': restricted 
+        });
+        this.zoomToExtent(maxExtent);
+    },
+
+    /** 
+     * APIMethod: zoomToScale
+     * Zoom to a specified scale 
+     * 
+     * Parameters:
+     * scale - {float}
+     * closest - {Boolean} Find the zoom level that most closely fits the 
+     *     specified scale. Note that this may result in a zoom that does 
+     *     not exactly contain the entire extent.
+     *     Default is false.
+     * 
+     */
+    zoomToScale: function(scale, closest) {
+        var res = OpenLayers.Util.getResolutionFromScale(scale, 
+                                                         this.baseLayer.units);
+
+        var halfWDeg = (this.size.w * res) / 2;
+        var halfHDeg = (this.size.h * res) / 2;
+        var center = this.getCachedCenter();
+
+        var extent = new OpenLayers.Bounds(center.lon - halfWDeg,
+                        &nbs